ported legacy functions

This commit is contained in:
2025-07-04 14:00:46 +02:00
parent e48ddfdb2c
commit 3c898f36de
32 changed files with 2642 additions and 35 deletions

View File

@@ -3,7 +3,6 @@ package de.srsoftware.umbrella.backend;
import static de.srsoftware.umbrella.core.Constants.UMBRELLA;
import static de.srsoftware.umbrella.core.Util.mapLogLevel;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.INFO;
import com.sun.net.httpserver.HttpServer;
@@ -16,11 +15,10 @@ import de.srsoftware.umbrella.translations.Translations;
import de.srsoftware.umbrella.user.UserModule;
import de.srsoftware.umbrella.user.sqlite.SqliteDB;
import de.srsoftware.umbrella.web.WebHandler;
import org.json.JSONObject;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import org.json.JSONObject;
public class Application {
private static final System.Logger LOG = System.getLogger("Umbrella");

View File

@@ -1,19 +1,18 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.core;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.PathHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import static de.srsoftware.tools.MimeType.MIME_JSON;
import static de.srsoftware.tools.Optionals.nullable;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.WARNING;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.PathHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
public abstract class BaseHandler extends PathHandler {
private static HttpExchange addCors(HttpExchange ex){

View File

@@ -6,6 +6,7 @@ public class Paths {
public static final String ADD = "add";
public static final String CSS = "css";
public static final String COMMON_TEMPLATES = "common_templates";
public static final String JSON = "json";
public static final String LEGACY = "legacy";
public static final String LIST = "list";

View File

@@ -4,7 +4,6 @@ package de.srsoftware.umbrella.core;
import static de.srsoftware.umbrella.core.Constants.TOKEN;
import de.srsoftware.tools.SessionToken;
import java.util.UUID;
public class Token implements CharSequence{

View File

@@ -32,6 +32,7 @@
<!-- https://github.com/notnotsamuel/svelte-tiny-router -->
<Menu />
<Route path="/user" component={User} />
<Route path="/user/login" component={User} />
<Route path="/user/:user_id/edit" component={UserEdit} />
<Route path="/user/oidc/add" component={EditService} />
<Route path="/user/oidc/edit/:serviceName" component={EditService} />

View File

@@ -6,6 +6,12 @@
import LoginServiceList from './LoginServices.svelte';
import Profile from './Profile.svelte';
import UserList from './List.svelte';
let params = new URLSearchParams(location.search);
let redirect = params.get('returnTo');
if (redirect && user.name){
location.href = redirect;
}
</script>
<h1>{t('user.user_module')}</h1>

14
legacy/build.gradle.kts Normal file
View File

@@ -0,0 +1,14 @@
description = "Umbrella : Legacy API"
dependencies{
implementation(project(":core"))
implementation(project(":user"))
implementation("de.srsoftware:configuration.api:1.0.2")
implementation("de.srsoftware:tools.jdbc:1.3.2")
implementation("de.srsoftware:tools.mime:1.1.2")
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("de.srsoftware:tools.util:2.0.3")
implementation("org.bitbucket.b_c:jose4j:0.9.6")
implementation("org.json:json:20240303")
implementation("org.xerial:sqlite-jdbc:3.49.0.0")
}

View File

@@ -1,5 +1,20 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.legacy;
import static de.srsoftware.tools.MimeType.MIME_FORM_URL;
import static de.srsoftware.tools.Query.decode;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Constants.TOKEN;
import static de.srsoftware.umbrella.core.Paths.*;
import static de.srsoftware.umbrella.core.Util.request;
import static de.srsoftware.umbrella.user.Paths.NOTIFY;
import static de.srsoftware.umbrella.user.Paths.VALIDATE_TOKEN;
import static java.lang.Long.parseLong;
import static java.lang.System.Logger.Level.*;
import static java.lang.System.Logger.Level.DEBUG;
import static java.net.HttpURLConnection.*;
import static java.time.temporal.ChronoUnit.DAYS;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.configuration.Configuration;
import de.srsoftware.tools.Path;
@@ -7,39 +22,36 @@ import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.core.UmbrellaException;
import de.srsoftware.umbrella.user.model.Session;
import de.srsoftware.umbrella.user.model.UmbrellaUser;
import de.srsoftware.umbrella.user.sqlite.SqliteDB;
import static de.srsoftware.tools.MimeType.MIME_FORM_URL;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Constants.KEY;
import static de.srsoftware.umbrella.core.Paths.LOGOUT;
import static de.srsoftware.umbrella.core.Paths.SEARCH;
import static de.srsoftware.umbrella.core.Util.request;
import static java.net.HttpURLConnection.*;
import static java.time.temporal.ChronoUnit.DAYS;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.json.JSONObject;
public class LegacyApi extends BaseHandler {
private final SqliteDB users;
private final Configuration config;
private final String messageUrl;
public LegacyApi(SqliteDB userDb, Configuration config) {
users = userDb;
this.config = config.subset("umbrella.modules").orElseThrow(() -> new RuntimeException("Missing configuration: umbrella.modules"));
this.messageUrl = null;
}
@Override
public boolean doGet(Path path, HttpExchange ex) throws IOException {
var head = path.pop();
return switch (head){
case null -> sendRedirect(ex, url(ex).replaceAll("/api/.*",""));
case "common_templates" -> {
case null -> sendRedirect(ex, url(ex).replaceAll("/legacy/.*",""));
case COMMON_TEMPLATES -> {
allowOrigin(ex, "*"); // add CORS header
yield load(path,ex);
}
@@ -109,7 +121,7 @@ public class LegacyApi extends BaseHandler {
var token = SessionToken.from(ex).map(SessionToken::sessionId)
.or(() -> getBearer(ex))
.map(Token::of);
if (token.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
if (token.isEmpty()) return sendRedirect(ex,"/user/login?returnTo="+url);
return sendRedirect(ex, url + (url.contains("?") ? "&" : "?") + "token=" + token.get());
}
@@ -121,6 +133,162 @@ public class LegacyApi extends BaseHandler {
@Override
public boolean doPost(Path path, HttpExchange ex) throws IOException {
return super.doPost(path, ex);
try {
return switch (path.pop()) {
case JSON -> legacyJson(ex);
case NOTIFY -> legacyNotify(ex);
case VALIDATE_TOKEN -> validateToken(ex);
default -> super.doPost(path,ex);
};
} catch (UmbrellaException e){
return send(ex,e);
}
}
private static String stripTrailingSlash(Object o){
String url = o.toString();
if (url.endsWith("/")) return url.substring(0,url.length()-1);
return url;
}
private boolean validateToken(HttpExchange ex) throws UmbrellaException, IOException {
String body;
try {
body = body(ex);
} catch (Exception e){
throw new UmbrellaException(400,"Failed to read request body").causedBy(e);
}
var map = decode(body);
LOG.log(DEBUG,"validateToken(…, {0}), data: {1}",map);
String domain = stripTrailingSlash(map.get(DOMAIN) instanceof String s ? s : "");
var keys = config.keys();
var match = false;
for (var key : keys){
var baseUrl = config.get(key + ".baseUrl").map(LegacyApi::stripTrailingSlash).orElse(null);
if (domain.equals(baseUrl)){
match = true;
break;
}
}
if (!match) throw new UmbrellaException(500,"Failed to verify request domain!");
var o = map.get(TOKEN);
if (!(o instanceof String token)) throw new UmbrellaException(500,"Request did not contain token!");
var session = users.load(Token.of(token));
var user = users.load(session);
var userMap = user.toMap();
userMap.put(TOKEN,Map.of(TOKEN,token,EXPIRATION,session.expiration().getEpochSecond()));
return sendContent(ex,userMap);
}
private boolean legacyJson(HttpExchange ex) throws UmbrellaException, IOException {
Map<String, Object> data = null;
try {
data = decode(body(ex));
} catch (IOException e) {
LOG.log(ERROR,"Failed to extract body of request");
throw new UmbrellaException(400,"Failed to extract body of request").causedBy(e);
}
var arrayPassed = false;
var ids = new ArrayList<Long>();
for (var entry : data.entrySet()){
var key = entry.getKey();
var val = entry.getValue();
if (key.startsWith("ids[") && (arrayPassed = true)) ids.add(parseLong(entry.getValue().toString()));
if ("ids".equals(key) && val instanceof Map<?,?> idMap) {
for (var o : idMap.values()){
ids.add(Long.parseLong(o.toString()));
}
arrayPassed = true;
}
}
if (ids.isEmpty() && data.get("id") instanceof String idString) ids.add(parseLong(idString));
var related = "true".equals(data.get("related"));
if (related) {
LOG.log(WARNING,"Fetching related users not implemented, yet!");
throw new UmbrellaException(400,"Fetching related users not implemented, yet!");
}
List<UmbrellaUser> userList = new ArrayList<>();
if (ids.isEmpty()) {
userList = users.list(0, null);
} else {
for (var id : ids){
try {
userList.add(users.load(id));
} catch (UmbrellaException ignored) {
}
}
}
if (arrayPassed || userList.size() != 1) {
Map<Long, Map<String, Object>> userData = userList.stream().collect(Collectors.toMap(UmbrellaUser::id, UmbrellaUser::toMap));
return sendContent(ex,userData);
}
return sendContent(ex, userList.getFirst().toMap());
}
protected Session requestSession(Token token) throws UmbrellaException {
var session = users.load(token);
session = users.extend(session);
return session;
}
private boolean legacyNotify(HttpExchange ex) throws UmbrellaException, IOException {
if (messageUrl == null) throw new UmbrellaException(500,"missing configuration: umbrella.modules.message.baseUrl");
var mime = contentType(ex).orElse(null);
JSONObject data;
try {
data = switch (mime) {
case MIME_FORM_URL -> new JSONObject(decode(body(ex)));
case null, default -> json(ex);
};
} catch (Exception e){
throw new UmbrellaException(400,"Failed to fetch content of request").causedBy(e);
}
if (!data.has(TOKEN)) throw new UmbrellaException(400,"token missing in request data!");
var dropKeys = new HashSet<String>();
dropKeys.add(TOKEN);
var token = Token.of(data.getString(TOKEN));
var userSession = requestSession(token);
var user = userSession.user();
LOG.log(DEBUG,"Token belongs to {0}.",user);
// recipients is a field used by legacy code.
// it may be a single user id or a list of user ids.
var recipients = new HashSet<Long>();
for (var key : data.keySet()){
if (key.startsWith("recipients[")) {
recipients.add(data.getLong(key));
dropKeys.add(key);
break;
}
if (key.equals("recipients")){
var list = data.getJSONObject(key);
for (var idx : list.keySet()){
var id = list.getLong(idx);
recipients.add(id);
}
dropKeys.add(key);
break;
}
}
if (!recipients.isEmpty()){ // replace legacy user ids by user objects in receivers field
List<UmbrellaUser> resp = users.list(0, null, recipients.toArray(new Long[0]));
data.put("receivers",resp.stream().map(UmbrellaUser::toMap).toList());
}
if (!data.has(SENDER)) data.put(SENDER,user.toMap());
dropKeys.forEach(data::remove);
LOG.log(DEBUG,"received legacy notification: {0}",data);
var messageList = Map.of("messages",List.of(data.toMap()));
var resp = request(messageUrl, messageList, null, token.asBearer());
if (!(resp instanceof JSONObject json)) throw new UmbrellaException(500,"{0} did not return JSON!",messageUrl);
// TODO: should we return json?
return sendEmptyResponse(HTTP_OK,ex);
}
}

View File

@@ -0,0 +1,19 @@
@font-face {
font-family: "awesome";
src: url("../fontawesome-webfont.woff");
}
.symbol{
font-family: awesome;
text-decoration: none;
}
.completed > a::before {
content: " ✓";
}
.canceled a,
.children .inactive,
.requirements .inactive,
.tasks .inactive{
text-decoration: line-through;
}

View File

@@ -0,0 +1,84 @@
body{
color: red;
background-color: black;
}
a{
color: red;
}
.tasks .pending>a{
color: gray;
}
.completed>a,
.started>a{
color: #5bc500;
}
.canceled>a{
color: gray;
}
tr{
background-color: #52525270;
}
tr:nth-child(2n+1){
background-color: #a7a7a740;
}
fieldset {
border-color: red;
}
.button,
input,
button {
background-color: red;
color: #303030;
}
.hover{
background-color: black;
border-color: red;
}
form.invoice textarea {
background-color: red;
color: black;
}
.infos span{
background-color: #00ad00;
color: black;
}
.errors span{
background-color: #ff9847;
color: black;
}
.warnings span{
background-color: #ffff00;
color: black;
}
#announce{
background-color: black;
}
#main_menu .button:hover {
color: red;
background-color: gray;
}
tr:hover td{
background-color: #160202;
}
code {
background-color: black;
color: orange;
}
.description img,
#preview img{
background: lightcyan;
}

View File

@@ -0,0 +1,584 @@
@font-face {
font-family: "awesome";
src: url("../fontawesome-webfont.woff");
}
*{
max-width:100%;
min-width:auto;
}
a{
text-decoration: none;
}
body{
font-family: sans-serif;
}
code {
display: inline-block;
padding: 5px;
margin: 5px 0;
}
textarea {
min-height: 60px;
width: 100%;
font-size: 16px;
}
textarea:hover{
min-height: 400px;
width: 100%;
}
input[type=text],input[type=email]{
width: 100%;
}
td.connectors form input{
width: 200px;
}
img#logo{
position: fixed;
top: 10px;
right: 10px;
z-index: -1;
}
blockquote{
font-style: italic;
}
#main_menu,
#main_menu form{
display: inline;
}
#main_menu .button {
font-size: 12px;
font-weight: normal;
margin-top: 7px;
vertical-align: bottom;
}
.hidden{
display: none !important;
}
.hover:hover .hidden{
display: inherit !important;
}
.emphasized{
font-weight: bold;
}
.add_positions,
.requirements{
max-height: 60px;
max-width: 66%;
overflow: hidden;
}
.add_positions:hover,
.requirements:hover{
max-height: 999999px;
max-width: 999999px;
}
.add_positions > ul > li{
max-height: 20px;
overflow: hidden;
padding-top: 20px;
}
.add_positions > ul > li:hover{
max-height: 999999px;
}
fieldset.add.document:hover {
max-height: unset;
}
fieldset.add.document {
max-height: 5px;
overflow: hidden;
}
fieldset.options label,
.add_positions label span,
.poll_status label,
.requirements label{
display: block;
}
.tasks .canceled a,
.children .inactive,
.requirements .inactive,
.tasks .inactive{
text-decoration: line-through;
}
.tasks .navi a{
display: inline-table;
}
blockquote a,
p a,
.description a{
text-decoration: underline;
}
table {
border-collapse: collapse;
}
tr > *{
padding: 5px 10px;
}
label.street:after,
label.location::after {
content: "\a ";
white-space: pre;
}
.poll.evaluate table tr:nth-child(n+2) th,
.poll.evaluate table td{
text-align: right;
}
.poll .disabled{
text-decoration: line-through;
}
.company div > fieldset,
.company > table{
margin-right: 10px;
float: left;
}
table.time h2{
margin-right: 60px;
}
.file label,
.document .dates label{
display: block
}
.document .header,
.document .tags{
clear: both;
}
fieldset {
border-radius: 10px;
border-width: 1px;
}
.contacts fieldset,
.document .customer,
.document .document_type,
.document .sender,
.document .court,
.document .dates,
.document .template,
fieldset.del_note,
fieldset.login_service_list,
fieldset.userlist,
fieldset.document.list{
float: left;
}
.document .dates label{
text-align: right;
}
.button,
input,
button {
border: 0 none;
font-weight: bold;
margin: 0 2px;
padding: 3px;
display: inline-block;
}
.change_state,
td.connectors button[type="submit"],
.prop_action,
input[type="submit"] {
clear: both;
float: right;
}
.symbol{
font-family: awesome;
font-size: 20px;
font-weight: normal;
}
#main_menu .symbol{
font-size: 14px;
}
.right{
float: right;
margin: 0 0 0 5px;
}
.tasks .project {
font-weight: bold;
}
.right-abs {
position: absolute;
right: 5px;
top: 5px;
}
.right-fix {
position: fixed;
right: 5px;
top: 5px;
}
.hover {
border-width: 1px;
border-style: solid;
border-radius: 5px;
max-height: 35px;
overflow: hidden;
z-index: 10;
}
.hover:hover {
max-height: unset;
}
.hover_h > a:nth-child(n+2){
display: none;
}
.hover_h:hover {
padding: 5px 0 20px;
}
.hover_h:hover > a:nth-child(n+2){
display: inherit;
}
form.document textarea {
font-weight: bold;
min-height: 90px;
width: 100%;
}
.pos_price,
form.document input.price,
form.document input.amount{
max-width: 60px;
text-align: right;
}
form.document .tax{
min-width: 70px;
}
form.document .tax input{
max-width: 40px;
text-align: right;
}
td > h1{
display: inline-block;
margin: 0 10px 0 0;
}
.center,
.infos,
.errors,
.warnings{
text-align: center;
}
.infos span,
.errors span,
.warnings span{
margin: 5px 0 0;
padding: 5px;
border-radius: 10px;
display: inline-block;
}
.tags span{
display: inline-block;
vertical-align: top;
}
.bookmark .share,
.bookmark .tags{
display: inline-block;
}
.bookmark form .share{
height: 20px;
overflow: hidden;
}
.bookmark form .share:hover{
height: initial;
}
fieldset.bookmark > fieldset{
max-height: 14px;
overflow: hidden;
}
fieldset.bookmark > fieldset.tags{
max-height: unset;
}
fieldset.bookmark > fieldset:hover{
max-height: unset;
}
.bookmark>fieldset>a{
word-wrap: break-word;
display: block;
min-width: auto;
}
.bookmark>fieldset>a.button{
display: inline-block;
}
.copytext{
position:fixed;
width: 100px;
left: -1000px;
}
img[src*="pos=right"] {
float:right;
margin: 5px 0 5px 5px;
}
img[src*="pos=left"] {
float:left;
margin: 5px 5px 5px 0;
}
img[src*="width=100"]{
max-width: 100px;
}
img[src*="width=200"]{
max-width: 200px;
}
img[src*="width=300"]{
max-width: 300px;
}
img[src*="width=400"]{
max-width: 400px;
}
img[src*="width=500"]{
max-width: 500px;
}
img[src*="width=600"]{
max-width: 600px;
}
img[src*="width=700"]{
max-width: 700px;
}
img[src*="width=800"]{
max-width: 800px;
}
img[src*="width=900"]{
max-width: 900px;
}
img[src*="width=50%"]{
max-width: 50%;
}
img[src*="width=33%"]{
max-width: 33%;
}
img[src$="width=25%"]{
max-width: 25%;
}
#announce{
position: fixed;
bottom: 0;
right: 0;
padding: 3px 5px 0;
border-top-left-radius: 7px;
}
fieldset.scrolling{
overflow: scroll;
max-height: 80%;
}
svg#gantt{
max-width: unset;
}
svg .row{
fill: none;
pointer-events: all;
}
svg .row:hover{
fill: #333333;
}
svg .duration{
fill:rgba(255,0,255,0.4);
stroke:none;
}
svg .schedule{
fill:none;
stroke-width:1;
stroke: yellow;
}
svg .start{
stroke-dasharray: 10,30,40;
}
svg .stop{
stroke-dasharray: 50,30;
}
svg text{
stroke: none;
fill: red;
}
div.search{
display: inline-block;
max-width: 150px;
}
div.search input,
div.search label{
display: none;
}
div.search:hover input,
div.search:hover label{
display: initial;
}
.note td.code {
width: 50%;
}
.note td.code textarea {
min-height: 200px;
}
.project-index td .users{
max-height: 30px;
overflow: hidden;
}
.project-index tr:hover td .users{
max-height: none;
}
.easylist fieldset a.button {
width: 80%;
padding: 30px;
text-align: center;
}
#preview,
#preview-source{
display: inline-block;
max-width: calc(50% - 20px);
vertical-align: bottom;
}
table #preview,
table #preview-source{
display: inherit;
max-width: unset;
vertical-align: inherit;
}
#preview.loading{
opacity: 0.2;
}
.completed > a::before {
content: " ✓";
}
@media (max-width:1199px) {
*[hide="12"]{
display: none;
}
table.document input{
max-width: 100px;
}
}
@media (max-width: 900px){
*[hide="9"]{
display: none;
}
table.document input{
max-width: 60px;
}
table.document .tax input,
table.document input.amount{
max-width: 25px;
}
}
@media (max-width: 800px){
*[hide="8"]{
display: none;
}
}
@media (max-width: 700px){
*[hide="7"]{
display: none;
}
fieldset.bookmark > fieldset{
max-height: 50px;
}
#preview,
#preview-source{
display: block;
max-width: unset;
}
}
@media (max-width: 600px){
*[hide="6"]{
display: none;
}
.easylist fieldset a.button {
width: 60%;
}
}
@media (max-width: 500px){
*[hide="5"]{
display: none;
}
}
@media (max-width: 400px){
*[hide="4"]{
display: none;
}
}
@media print{
fieldset.note span.right,
#logo, #main_menu {
display: none;
}
fieldset.process, fieldset.database{
page-break-after: always;
}
.export>fieldset{
border: 0 none;
}
}

View File

@@ -0,0 +1,41 @@
.arrow{
stroke: red;
}
circle, ellipse, rect{
stroke: black;
}
ellipse,
circle.process{
fill: rgba(255,255,255,0.5);
}
rect.terminal{
fill: white;
}
circle.connector{
fill: rgba(0,0,0,0.05);
stroke: none;
}
circle.connector:hover{
fill: rgba(0,0,0,0.6);
}
#backdrop{
fill: transparent;
}
.arrow circle{
stroke: none;
fill: rgba(128,128,128,0.5);
}
.arrow text{
fill: black;
stroke: none;
}
.arrow:hover circle{
fill: rgba(255,0,0,0.3);
}
.process text{
fill: black;
}

View File

@@ -0,0 +1,76 @@
body{
background: #0f3a09;
}
a{
color: #0c0061;
}
code{
background: #cccccc;
border-color: #0f3a09;
}
.pending a{
color: #45513a;
}
.open>a{
color: #0f3a09;
}
.completed>a:after{
content: " ✓";
}
.canceled>a{
color: #414141;
}
li:hover > a,
a:hover{
color: red;
}
fieldset,table{
background: rgba(255,255,255,0.49);
}
legend{
background: #82987f;
}
.share, .share>legend{
background:#8cff7e;
}
.button{
background: white;
}
#announce a{
color:black;
}
table.diagram .step,
body > a.symbol {
background: white;
}
.right a {
background: white;
color: red;
}
.infos span{
background: lime;
}
.warnings span{
background: yellow;
}
.errors span{
background: orange;
}
.search label{
color: white;
}

View File

@@ -0,0 +1,526 @@
@font-face {
font-family: "awesome";
src: url("../fontawesome-webfont.woff");
}
a{
text-decoration:none;
}
body{
margin-left: 160px;
margin-top: 25px;
font-family: sans-serif;
position: relative;
}
code{
display: inline-block;
padding: 5px;
border: 1px solid;
border-radius: 5px;
}
body > a.symbol {
padding: 5px;
border-radius: 5px;
}
body > h2{
color: #67a05f;
}
body.user.login{
width: 100%;
max-width: 400px;
margin: 0 auto;
text-align: center;
}
img#logo{
position: fixed;
top: 10px;
right: 10px;
z-index: -1;
}
blockquote{
font-style: italic;
}
#main_menu{
position: fixed;
top:0;
left:0;
bottom:0;
width: 150px;
}
#main_menu a{
display: block;
text-align: center;
overflow: hidden;
}
.hidden{
display: none;
}
td{
position: relative;
}
fieldset{
position: relative;
margin-top: 5px;
vertical-align: top;
}
.hover:hover .hidden{
display: inherit;
}
.button{
padding:2px;
margin: 1px 0;
border-radius: 5px;
border: 1px solid black;
white-space: pre;
display: inline-block;
}
.symbol{
font-family: awesome;
font-size: 22px;
}
.right{
position: absolute;
right: 0;
top: 0;
}
.right a {
border: 1px solid;
border-radius: 5px;
padding: 0 5px;
}
.right-fix{
position: fixed;
right: 0;
top: 0;
overflow: hidden;
max-height: 25px;
border: 1px solid red;
border-radius: 5px;
background: white;
z-index: 10;
}
.canceled{
text-decoration: line-through
}
.hover:hover{
max-height: unset !important;
max-width: unset !important;
}
fieldset textarea{
width: 100%;
height: 200px;
font-size: 16px;
}
img{
max-width: 100%;
}
.infos {
margin: 5px;
}
.errors span,
.infos span{
margin: 2px;
padding: 5px;
}
.copytext{
position: fixed;
width: 90px;
left: -100px;
}
.poll td>*{
margin: 15px;
padding: 15px;
}
.poll td.poll_status>*{
margin: 5px;
padding: 5px;
}
.poll td.poll_status>a{
display: inline-block;
}
.poll table{
border-spacing: 0;
}
.poll tr{
border-spacing: 0;
}
.poll td{
border-style: solid;
border-width: 1px 0 0 0;
border-color: black;
}
.poll .disabled{
text-decoration: line-through;
}
img[src*="pos=right"] {
float:right;
margin: 5px 0 5px 5px;
}
img[src*="pos=left"] {
float:left;
margin: 5px 5px 5px 0;
}
img[src*="width=100"]{
max-width: 100px;
}
img[src*="width=200"]{
max-width: 200px;
}
img[src*="width=300"]{
max-width: 300px;
}
img[src*="width=400"]{
max-width: 400px;
}
img[src*="width=500"]{
max-width: 500px;
}
img[src*="width=600"]{
max-width: 600px;
}
img[src*="width=700"]{
max-width: 700px;
}
img[src*="width=800"]{
max-width: 800px;
}
img[src*="width=900"]{
max-width: 900px;
}
img[src*="width=50%"]{
max-width: 50%;
}
img[src*="width=33%"]{
max-width: 33%;
}
img[src$="width=25%"]{
max-width: 25%;
}
#announce{
position: absolute;
right: 3px;
bottom: -40px;
}
span.hover_h {
display: block;
}
label{
display: block;
position: relative;
}
legend{
padding: 0 5px;
border-radius: 5px;
}
.search input {
margin: 5px 0 0 5px;
width: 62%;
}
.search label input{
width: 10px;
}
.search label{
display: none;
}
.search:hover label{
display: block;
}
.search button{
width: 40px;
font-size: 14px;
padding: 0;
}
.share{
height: 35px;
overflow: hidden;
}
.share:hover{
height: inherit;
}
.bookmark textarea[name=comment]{
max-height: 50px;
}
.bookmark textarea[name=comment]:hover{
max-height: unset;
}
.bookmark fieldset input{
width: 100%;
}
.notes .note{
border: 1px solid;
border-radius: 5px;
}
.warnings span{
background: yellow;
}
.errors span{
background: orange;
}
.tasks li{
margin: 5px;
padding-right: 80px;
}
.project-view{
min-width: 100%;
}
.project-view span.hover_h{
position: absolute;
right: 0;
}
.note td.code {
width: 50%;
}
.note td.code textarea {
min-height: 200px;
width: 100%;
}
.add_positions > ul,
.requirements > ul {
max-height: 400px;
overflow: auto;
}
td input,
table.vertical{
width: 100%;
}
.document td label{
width: unset;
}
.document.view .add_positions img{
display: none;
}
td input[type=checkbox]{
width: unset;
}
table.vertical span.right{
position: fixed;
top: 25px;
left: 60px;
right: 5px;
text-align: right;
}
img[src*="pos=right"] {
float:right;
margin: 5px 0 5px 5px;
}
img[src*="pos=left"] {
float:left;
margin: 5px 5px 5px 0;
}
img[src*="width=100"]{
max-width: 100px;
}
img[src*="width=200"]{
max-width: 200px;
}
img[src*="width=300"]{
max-width: 300px;
}
img[src*="width=400"]{
max-width: 400px;
}
img[src*="width=500"]{
max-width: 500px;
}
img[src*="width=600"]{
max-width: 600px;
}
img[src*="width=700"]{
max-width: 700px;
}
img[src*="width=800"]{
max-width: 800px;
}
img[src*="width=900"]{
max-width: 900px;
}
img[src*="width=50%"]{
max-width: 50%;
}
img[src*="width=33%"]{
max-width: 33%;
}
img[src$="width=25%"]{
max-width: 25%;
}
#preview,
#preview-source{
display: inline-block;
max-width: calc(50% - 20px);
vertical-align: bottom;
}
table #preview,
table #preview-source{
display: inherit;
max-width: unset;
vertical-align: inherit;
}
#preview.loading{
opacity: 0.2;
}
.easylist fieldset a.button {
width: 70%;
display: inline-block;
text-align: center;
padding: 10px;
}
@media (min-width: 1100px){
.share{
position: absolute;
right: 0;
top: 8px;
}
}
@media (max-width: 1000px){
*[hide="12"]{
display: none;
}
}
@media (max-width: 900px){
*[hide="11"]{
display: none;
}
#preview,
#preview-source{
display: block;
max-width: unset;
}
}
@media (max-width: 800px){
*[hide="10"]{
display: none;
}
}
@media (max-width: 700px){
*[hide="9"]{
display: none;
}
body{
margin-left: 50px;
margin-top: 60px;
}
#main_menu{
max-width: 50px;
z-index: 10;
overflow: hidden;
background: inherit;
}
#main_menu:hover{
max-width: unset;
}
#main_menu .button{
padding: 6px 0;
}
.search button{
display: none;
}
.search:hover button{
display: inline;
}
}
@media (max-width: 600px){
*[hide="8"]{
display: none;
}
}
@media (max-width: 500px){
*[hide="7"]{
display: none;
}
}
@media (max-width: 400px){
*[hide="6"]{
display: none;
}
}
@media print{
fieldset.note span.right,
#logo, #main_menu {
display: none;
}
fieldset.process, fieldset.database{
page-break-after: always;
}
.export>fieldset{
border: 0 none;
}
}

View File

@@ -0,0 +1,37 @@
.arrow{
stroke: blue;
}
circle, ellipse, rect{
stroke: black;
}
ellipse,
circle.process{
fill: rgba(255,255,255,0.5);
}
rect.terminal{
fill: white;
}
circle.connector{
fill: rgba(0,0,0,0.05);
stroke: none;
}
circle.connector:hover{
fill: rgba(0,0,0,0.6);
}
#backdrop{
fill: transparent;
}
.arrow circle{
stroke: none;
fill: white;
}
.arrow text{
fill: blue;
stroke: none;
}
.arrow:hover circle{
fill: rgba(0,0,255,0.3);
}

Binary file not shown.

View File

@@ -0,0 +1,23 @@
a {
color: palegreen;
}
body {
background: black;
color: white;
}
.error{
background: red;
color: black;
padding: 3px;
}
.error button{
margin: 2px 5px;
}
#navi .button{
border: 1px solid red;
padding: 2px;
margin: 2px;
text-decoration: none;
}

View File

@@ -0,0 +1,21 @@
.model svg{
max-width: 100%;
max-height: 1000px;
}
.arrow{
stroke-width: 2;
}
circle, ellipse, rect{
stroke-width: 2;
}
circle.connector:hover{
r: 30;
}
text{
text-anchor:middle;
alignment-baseline:middle;
font-size:12;
}
.arrow:hover circle{
r: 50;
}

View File

@@ -0,0 +1,80 @@
body{
color: black;
background-color: white;
}
a{
color: #005678;
}
.tasks .pending>a{
color: #888888;
}
.completed>a,
.started>a{
color: #53822c;
}
.canceled>a{
color: gray;
}
tr{
background-color: #dadada91;
}
tr:nth-child(2n+1){
background-color: #b9b9b982;
}
fieldset {
border-color: black;
}
.button,
input,
button {
background-color: black;
color: #d3d3d3;
}
.hover{
background-color: white;
border-color: black;
}
form.invoice textarea {
background-color: black;
color: white;
}
.infos span{
background-color: #00ad00;
color: white;
}
.errors span{
background-color: #ff9847;
color: white;
}
.hit,
.warnings span{
background-color: #ffff00;
color: black;
}
#announce{
background-color: white;
}
#main_menu .button:hover {
color: white;
background-color: gray;
}
tr:hover td{
background-color: #edf9f9;
}
code {
background-color: black;
color: lime;
}

View File

@@ -0,0 +1,597 @@
@font-face {
font-family: "awesome";
src: url("../fontawesome-webfont.woff");
}
*{
max-width:100%;
min-width:auto;
}
a{
text-decoration: none;
}
body{
font-family: sans-serif;
}
code {
display: inline-block;
padding: 5px;
margin: 5px 0;
}
textarea {
min-height: 60px;
width: 100%;
font-size: 16px;
}
textarea:hover{
min-height: 400px;
width: 100%;
}
input[type=text]{
width: 100%;
}
td.connectors form input{
width: 200px;
}
img#logo{
position: fixed;
top: 10px;
right: 10px;
z-index: -1;
}
blockquote{
font-style: italic;
}
#main_menu,
#main_menu form{
display: block;
}
#main_menu .button {
font-size: 12px;
font-weight: normal;
margin-top: 7px;
vertical-align: bottom;
}
.hidden{
display: none !important;
}
.hover:hover .hidden{
display: inherit !important;
}
.emphasized{
font-weight: bold;
}
.add_positions,
.requirements{
max-height: 60px;
max-width: 66%;
overflow: hidden;
}
.add_positions:hover,
.requirements:hover{
max-height: 999999px;
max-width: 999999px;
}
.add_positions > ul > li{
max-height: 20px;
overflow: hidden;
padding-top: 20px;
}
.add_positions > ul > li:hover{
max-height: 999999px;
}
fieldset.add.document:hover {
max-height: unset;
}
fieldset.add.document {
max-height: 5px;
overflow: hidden;
}
fieldset.options label,
.add_positions label span,
.poll_status label,
.requirements label{
display: block;
}
.tasks .canceled a,
.children .inactive,
.requirements .inactive,
.tasks .inactive{
text-decoration: line-through;
}
.tasks .navi a{
display: inline-table;
}
blockquote a,
p a,
.description a{
text-decoration: underline;
}
table {
border-collapse: collapse;
}
tr > *{
padding: 5px 10px;
}
label.street:after,
label.location::after {
content: "\a ";
white-space: pre;
}
.poll.evaluate table tr:nth-child(n+2) th,
.poll.evaluate table td{
text-align: right;
}
.poll .disabled{
text-decoration: line-through;
}
.company div > fieldset,
.company > table{
margin-right: 10px;
float: left;
}
table.time h2{
margin-right: 60px;
}
.file label,
.document .dates label{
display: block
}
.clear,
.document .header,
.document .tags{
clear: both;
}
fieldset {
border-radius: 10px;
border-width: 1px;
}
.contacts fieldset,
.document .customer,
.document .document_type,
.document .sender,
.document .court,
.document .dates,
.document .template,
fieldset.del_note,
fieldset.login_service_list,
fieldset.userlist,
fieldset.document.list{
float: left;
}
.document .dates label{
text-align: right;
}
.button,
input,
button {
border: 0 none;
font-weight: bold;
margin: 0 2px;
padding: 3px;
display: inline-block;
}
.change_state,
td.connectors button[type="submit"],
.prop_action,
input[type="submit"] {
clear: both;
float: right;
}
.symbol{
font-family: awesome;
font-size: 20px;
font-weight: normal;
}
#main_menu .symbol{
font-size: 14px;
}
.right{
float: right;
margin: 0 0 0 5px;
}
.tasks .project {
font-weight: bold;
}
.right-abs {
position: absolute;
right: 5px;
top: 5px;
}
.right-fix {
position: fixed;
right: 5px;
top: 5px;
}
.hover {
border-width: 1px;
border-style: solid;
border-radius: 5px;
max-height: 35px;
overflow: hidden;
z-index: 10;
}
.hover:hover {
max-height: unset;
}
.hover_h > a:nth-child(n+2){
display: none;
}
.hover_h:hover {
padding: 5px 0 20px;
}
.hover_h:hover > a:nth-child(n+2){
display: inherit;
}
form.document textarea {
font-weight: bold;
min-height: 90px;
width: 100%;
}
.pos_price,
form.document input.price,
form.document input.amount{
max-width: 60px;
text-align: right;
}
form.document .tax{
min-width: 70px;
}
form.document .tax input{
max-width: 40px;
text-align: right;
}
td > h1{
display: inline-block;
margin: 0 10px 0 0;
}
.center,
.infos,
.errors,
.warnings{
text-align: center;
}
.infos span,
.errors span,
.warnings span{
margin: 5px 0 0;
padding: 5px;
border-radius: 10px;
display: inline-block;
}
.tags span{
display: inline-block;
vertical-align: top;
}
.bookmark .share,
.bookmark .tags{
display: inline-block;
}
.bookmark form .share{
height: 20px;
overflow: hidden;
}
.bookmark form .share:hover{
height: initial;
}
fieldset.bookmark > fieldset{
max-height: 14px;
overflow: hidden;
}
fieldset.bookmark > fieldset.tags{
max-height: unset;
}
fieldset.bookmark > fieldset:hover{
max-height: unset;
}
.bookmark>fieldset>a{
word-wrap: break-word;
display: block;
min-width: auto;
}
.bookmark>fieldset>a.button{
display: inline-block;
}
.copytext{
position:fixed;
width: 100px;
left: -1000px;
}
img[src*="pos=right"] {
float:right;
margin: 5px 0 5px 5px;
}
img[src*="pos=left"] {
float:left;
margin: 5px 5px 5px 0;
}
img[src*="width=100"]{
max-width: 100px;
}
img[src*="width=200"]{
max-width: 200px;
}
img[src*="width=300"]{
max-width: 300px;
}
img[src*="width=400"]{
max-width: 400px;
}
img[src*="width=500"]{
max-width: 500px;
}
img[src*="width=600"]{
max-width: 600px;
}
img[src*="width=700"]{
max-width: 700px;
}
img[src*="width=800"]{
max-width: 800px;
}
img[src*="width=900"]{
max-width: 900px;
}
img[src*="width=50%"]{
max-width: 50%;
}
img[src*="width=33%"]{
max-width: 33%;
}
img[src$="width=25%"]{
max-width: 25%;
}
#announce{
position: fixed;
bottom: 0;
right: 0;
padding: 3px 5px 0;
border-top-left-radius: 7px;
}
fieldset.scrolling{
overflow: scroll;
max-height: 80%;
}
svg#gantt{
max-width: unset;
}
svg .row{
fill: none;
pointer-events: all;
}
svg .row:hover{
fill: #333333;
}
svg .duration{
fill:rgba(255,0,255,0.4);
stroke:none;
}
svg .schedule{
fill:none;
stroke-width:1;
stroke: yellow;
}
svg .start{
stroke-dasharray: 10,30,40;
}
svg .stop{
stroke-dasharray: 50,30;
}
svg text{
stroke: none;
fill: red;
}
div.search{
display: inline-block;
max-width: 150px;
}
div.search input,
div.search label{
display: none;
}
div.search:hover input,
div.search:hover label{
display: initial;
}
.note td.code {
width: 50%;
}
.note td.code textarea {
min-height: 200px;
}
.project-index td .users{
max-height: 30px;
overflow: hidden;
}
.project-index tr:hover td .users{
max-height: none;
}
.easylist fieldset a.button {
width: 80%;
padding: 30px;
text-align: center;
}
#preview,
#preview-source{
display: inline-block;
max-width: calc(50% - 20px);
vertical-align: bottom;
}
table #preview,
table #preview-source{
display: inherit;
max-width: unset;
vertical-align: inherit;
}
#preview.loading{
opacity: 0.2;
}
.completed > a::before {
content: " ✓";
}
@media (max-width:1199px) {
*[hide="12"]{
display: none;
}
table.document input{
max-width: 100px;
}
}
@media (max-width: 1100px){
*[hide="11"]{
display: none;
}
}
@media (max-width: 1000px){
*[hide="10"]{
display: none;
}
}
@media (max-width: 900px){
*[hide="9"]{
display: none;
}
table.document input{
max-width: 60px;
}
table.document .tax input,
table.document input.amount{
max-width: 25px;
}
}
@media (max-width: 800px){
*[hide="8"]{
display: none;
}
}
@media (max-width: 700px){
*[hide="7"]{
display: none;
}
fieldset.bookmark > fieldset{
max-height: 50px;
}
#preview,
#preview-source{
display: block;
max-width: unset;
}
}
@media (max-width: 600px){
*[hide="6"]{
display: none;
}
.easylist fieldset a.button {
width: 60%;
}
}
@media (max-width: 500px){
*[hide="5"]{
display: none;
}
}
@media (max-width: 400px){
*[hide="4"]{
display: none;
}
}
@media print{
fieldset.note span.right,
#logo, #main_menu {
display: none;
}
fieldset.process, fieldset.database{
page-break-after: always;
}
.export>fieldset{
border: 0 none;
}
}

View File

@@ -0,0 +1,37 @@
.arrow{
stroke: blue;
}
circle, ellipse, rect{
stroke: black;
}
ellipse,
circle.process{
fill: rgba(255,255,255,0.5);
}
rect.terminal{
fill: white;
}
circle.connector{
fill: rgba(0,0,0,0.05);
stroke: none;
}
circle.connector:hover{
fill: rgba(0,0,0,0.6);
}
#backdrop{
fill: transparent;
}
.arrow circle{
stroke: none;
fill: white;
}
.arrow text{
fill: blue;
stroke: none;
}
.arrow:hover circle{
fill: rgba(0,0,255,0.3);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,171 @@
const DEL = 'DELETE';
const PATCH = 'PATCH';
const POST = 'POST';
// remove all children from element, leave only the first <keep> children
function clearElement(key,keep = 1){
var elem = key instanceof Element ? key : get(key);
while (elem.childElementCount > keep) elem.removeChild(elem.lastChild);
return elem;
}
function create(type,code){
var elem = document.createElement(type);
if (code) elem.innerHTML = code;
return elem;
}
function disable(id){
var elem = get(id);
elem.setAttribute('disabled','disabled');
return elem;
}
function enable(id){
var elem = get(id);
elem.removeAttribute('disabled');
return elem;
}
function get(id){
return document.getElementById(id);
}
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let arr = decodedCookie.split(';');
for(let i = 0; i <arr.length; i++) {
let c = arr[i];
while (c.charAt(0) == ' ') c = c.substring(1);
if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
}
return "";
}
function getValue(id){
return get(id).value;
}
function hide(id){
var elem = get(id);
if (elem) elem.style.display = 'none';
return elem;
}
function hideAll(clazz){
var elems = document.getElementsByTagName('*'), i;
for (i in elems) {
if((' ' + elems[i].className + ' ').indexOf(' ' + clazz + ' ') > -1) elems[i].style.display = 'none';
}
}
function isChecked(id){
return get(id).checked;
}
function menu(){
fetch('<? user.api.menu ?>').then(showMenu);
}
// Replacement for Object.toEntries(…)
function paramsToObject(entries) {
const result = {};
for(var key of entries) { // each 'entry' is a [key, value] tupple
result[key[0]] = key[1];
}
return result;
}
function redirect(page){
window.location.href = page;
}
function setText(id, text){
var elem = get(id);
elem.innerHTML = text;
return elem;
}
function setValue(id,newVal){
var elem = get(id);
if (elem) elem.value = newVal;
return elem;
}
function show(id){
var elem = get(id);
if (elem) elem.style.display = '';
return elem;
}
function showAll(clazz){
var elems = document.getElementsByTagName('*'), i;
for (i in elems) {
if((' ' + elems[i].className + ' ').indexOf(' ' + clazz + ' ') > -1) elems[i].style.display = '';
}
}
function showError(json){
var message = json.message;
var box = show('error');
box.innerHTML = message;
var button = document.createElement('button');
button.innerText = 'ok';
button.addEventListener('click',() => hide('error'))
box.appendChild(button);
box.scrollIntoView();
}
async function showMenu(resp){
if (resp.ok){
var menu = get('main_menu');
var json = await resp.json();
var entries = json.entries;
entries.sort((a,b) => a.index - b.index);
for (var idx in entries){
var entry = entries[idx];
menu.insertAdjacentHTML('beforeend','<a class="button" href="'+entry.url+'">'+entry.caption+'</a>');
}
}
}
function sortTableColumn(column){
var table = column;
var columnIndex = [...column.parentNode.children].indexOf(column);
while (table.localName != 'table'){
if (!table.parentNode) return;
table = table.parentNode;
}
var rows = Array.from(table.tBodies[0].rows);
var asc = table.getAttribute("data-sort-dir") !== "desc";
table.setAttribute("data-sort-dir", asc ? "desc" : "asc");
rows.sort(function(a, b) {
var cellA = a.cells[columnIndex].innerText.trim();
var cellB = b.cells[columnIndex].innerText.trim();
var numA = parseFloat(cellA);
var numB = parseFloat(cellB);
if (!isNaN(numA) && !isNaN(numB)) return asc ? numA - numB : numB - numA;
return asc ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA);
});
// Append sorted rows back to the tbody
rows.forEach(function(row) {
table.tBodies[0].appendChild(row);
});
}
function spread(json,prefix){
for (var key in json){
var val = json[key];
var id = prefix+"."+key;
if (typeof val === 'object'){
spread(val,id);
continue;
}
if (typeof val === 'string') val = val.replaceAll("\n","<br/>\n");
var tag = get(id);
if (tag) tag.innerHTML = val;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,124 @@
const POST = 'POST';
function toggle(selector){
$(selector).toggle("slow");
}
function addDescriptionOption(text){
if (text.trim() == '') return;
var select = $('select[name=alt_comment]');
if (select.length == 0){
var area=$('textarea[name=comment]');
var hint= area.attr('descr');
$('<select/>',{name: 'alt_comment'})
.append($('<option/>',{value: '',text: hint}))
.insertBefore(area);
select = $('select[name=alt_comment]');
select.on('change',function(){
$('textarea[name=comment]').val(select.find('option:selected').text());
});
}
select.append($('<option/>',{text: text}));
}
function getHeadings(elem){
$('select[name=alt_comment]').remove();
$('textarea[name=comment]').val('');
var url=window.location.href.replace(/\/([^\/]*)$/,'/headings')+'?page='+encodeURIComponent(elem.value);
$.ajax({
url: url,
dataType: "json",
success: function(data){
for (var index in data.headings) addDescriptionOption(data.headings[index]);
}
});
}
var preview_timer = 0;
function keyEvent(e){
if (e.ctrlKey){
if (e.key === 'f') { // display search form on Ctrl+F
e.preventDefault();
$('.search *').show();
$('.search form>input').focus();
} else
if (e.key === 's') { // save current element on Ctrl+S
var saveBtn = $('form[method=POST] button[type="submit"]')[0];
if (saveBtn) {
e.preventDefault();
saveBtn.click();
}
}
return;
}
if (e.altKey){
switch (e.keyCode){
case 38:
$('a.parent').each(function(){this.click()});
return;
case 39:
$('a.next').each(function(){this.click()});
return
default:
console.log(e.keyCode);
}
}
switch (e.keyCode){
case 27: // ESC
if (document.location.href.includes("/add")) window.history.back();
if (document.location.href.includes("/edit")) window.history.back();
return;
case 107: // +
var active = document.activeElement.tagName;
switch (active){
case 'TEXTAREA':
// do not activate add-link when in these fields
return;
}
var addLink = $('a[href^="add"]')[0];
if (addLink) addLink.click();
return;
}
if (e.target.id == 'preview-source') {
clearTimeout(preview_timer);
preview_timer = setTimeout(preview,750,e.target);
}
console.log(e);
}
getHeadingsTimer = null;
function getHeadings_delayed(){
if (getHeadingsTimer != null) clearTimeout(getHeadingsTimer);
getHeadingsTimer = window.setTimeout(getHeadings,200,this);
}
async function handlePreview(resp,txt){
const target = document.getElementById('preview');
if (resp.ok){
const content = await resp.text();
$(target).removeClass('loading').html(content);
setTimeout(() => {
$(txt).css('height',Math.max(target.clientHeight,200));
},200);
} else {
$(target).removeClass('loading').html('Preview failed…');
console.log("preview request failed!");
}
}
function preview(txt){
let target = document.getElementById('preview');
if (!target) return;
$(target).addClass('loading');
$(target).html('loading…');
let url = '<? base ?>/api/preview';
fetch(url,{
method: POST,
body: txt.value,
credentials: 'include'
}).then(resp => handlePreview(resp,txt));
}
document.addEventListener('keydown',keyEvent);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -2,6 +2,7 @@ description = "Umbrella : User"
dependencies{
implementation(project(":core"))
implementation("de.srsoftware:configuration.api:1.0.2")
implementation("de.srsoftware:tools.jdbc:1.3.2")
implementation("de.srsoftware:tools.mime:1.1.2")
implementation("de.srsoftware:tools.optionals:1.0.0")

View File

@@ -35,8 +35,6 @@ public class Constants {
public static final String PASS = "pass";
public static final String REDIRECT_URI = "redirect_uri";
public static final String RESPONSE_TYPE = "response_type";
public static final String SCOPE = "scope";

View File

@@ -23,7 +23,6 @@ import static java.time.temporal.ChronoUnit.DAYS;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.PathHandler;
import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.Token;

View File

@@ -1,11 +1,11 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.user.api;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.core.UmbrellaException;
import de.srsoftware.umbrella.user.model.DbUser;
import de.srsoftware.umbrella.user.model.Password;
import de.srsoftware.umbrella.user.model.Session;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.user.model.UmbrellaUser;
import java.util.List;

View File

@@ -3,7 +3,6 @@ package de.srsoftware.umbrella.user.model;
import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.Token;
import java.time.Instant;
/* © SRSoftware 2025 */

View File

@@ -11,12 +11,12 @@ import static java.lang.System.Logger.Level.*;
import static java.text.MessageFormat.format;
import de.srsoftware.tools.jdbc.Query;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.core.UmbrellaException;
import de.srsoftware.umbrella.user.api.LoginServiceDb;
import de.srsoftware.umbrella.user.api.UserDb;
import de.srsoftware.umbrella.user.model.*;
import de.srsoftware.umbrella.user.model.Session;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.user.model.UmbrellaUser;
import java.sql.Connection;
import java.sql.ResultSet;