@saltcorn/server 0.8.7-beta.6 → 0.8.8-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth/admin.js +20 -16
- package/auth/routes.js +12 -8
- package/load_plugins.js +14 -1
- package/locales/en.json +29 -1
- package/locales/si.json +1197 -0
- package/package.json +8 -8
- package/public/diagram_utils.js +21 -1
- package/public/relationship_diagram_utils.js +32 -10
- package/public/saltcorn-common.js +40 -19
- package/public/saltcorn.js +11 -5
- package/routes/admin.js +2 -0
- package/routes/common_lists.js +34 -19
- package/routes/diagram.js +214 -199
- package/routes/fields.js +113 -6
- package/routes/index.js +2 -0
- package/routes/models.js +492 -0
- package/routes/page.js +10 -6
- package/routes/tables.js +105 -47
- package/routes/view.js +10 -6
- package/routes/viewedit.js +27 -5
- package/tests/plugins.test.js +103 -2
- package/tests/view.test.js +217 -0
- package/public/vis-network.min.js +0 -49
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8-beta.0",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@saltcorn/base-plugin": "0.8.
|
|
10
|
-
"@saltcorn/builder": "0.8.
|
|
11
|
-
"@saltcorn/data": "0.8.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.
|
|
14
|
-
"@saltcorn/markup": "0.8.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.8-beta.0",
|
|
10
|
+
"@saltcorn/builder": "0.8.8-beta.0",
|
|
11
|
+
"@saltcorn/data": "0.8.8-beta.0",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.8-beta.0",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.8-beta.0",
|
|
14
|
+
"@saltcorn/markup": "0.8.8-beta.0",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.8-beta.0",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
package/public/diagram_utils.js
CHANGED
|
@@ -49,7 +49,7 @@ function buildCard(node) {
|
|
|
49
49
|
<h5 class="card-title">${type}</h5>
|
|
50
50
|
<h6 class="card-subtitle text-muted">${label}</h6>
|
|
51
51
|
</div>
|
|
52
|
-
<div class="card-body">
|
|
52
|
+
<div class="card-body pt-1">
|
|
53
53
|
${!isVirtual ? buildTagBadges(node) : "<h5>virtual</h5>"}
|
|
54
54
|
${buildCardBody(node)}
|
|
55
55
|
<div>
|
|
@@ -198,6 +198,7 @@ function buildTagBadges(node) {
|
|
|
198
198
|
id="_${type}_${objectId}_badges_id"
|
|
199
199
|
class="mb-3"
|
|
200
200
|
>
|
|
201
|
+
<h6 class="text-muted mb-0">Tags:</h6>
|
|
201
202
|
${existingTagBadges(node)}
|
|
202
203
|
<button
|
|
203
204
|
class="badge bg-primary"
|
|
@@ -486,6 +487,25 @@ function reloadCy(keepViewPos) {
|
|
|
486
487
|
});
|
|
487
488
|
}
|
|
488
489
|
|
|
490
|
+
function takePicture() {
|
|
491
|
+
const base64 = window.cy.png({ bg: "white" }).substr(22);
|
|
492
|
+
const decoded = window.atob(base64);
|
|
493
|
+
const bytes = new Uint8Array(decoded.length);
|
|
494
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
495
|
+
bytes[i] = decoded.charCodeAt(i);
|
|
496
|
+
}
|
|
497
|
+
const blob = new Blob([bytes], { type: "image/png" });
|
|
498
|
+
const DOMURL = self.URL || self.webkitURL || self;
|
|
499
|
+
const url = DOMURL.createObjectURL(blob);
|
|
500
|
+
const link = document.createElement("a");
|
|
501
|
+
link.href = url;
|
|
502
|
+
link.download = "app-diagram.png";
|
|
503
|
+
link.click();
|
|
504
|
+
setTimeout(() => {
|
|
505
|
+
URL.revokeObjectURL(url);
|
|
506
|
+
}, 2000);
|
|
507
|
+
}
|
|
508
|
+
|
|
489
509
|
function toggleEntityFilter(type) {
|
|
490
510
|
switch (type) {
|
|
491
511
|
case "views": {
|
|
@@ -39,7 +39,8 @@ var erHelper = (() => {
|
|
|
39
39
|
}px) scale(${scale || 1.0})`
|
|
40
40
|
);
|
|
41
41
|
};
|
|
42
|
-
|
|
42
|
+
let mouseDown = false;
|
|
43
|
+
let isMoving = false;
|
|
43
44
|
return {
|
|
44
45
|
translateY: (val) => {
|
|
45
46
|
const parsed = parseTransform();
|
|
@@ -54,22 +55,43 @@ var erHelper = (() => {
|
|
|
54
55
|
zoom: (val) => {
|
|
55
56
|
const parsed = parseTransform();
|
|
56
57
|
parsed.scale += val;
|
|
58
|
+
if (parsed.scale < 0.1) parsed.scale = 0.1;
|
|
59
|
+
else if (parsed.scale > 20) parsed.scale = 20;
|
|
57
60
|
buildTransform(parsed);
|
|
58
61
|
},
|
|
59
62
|
reset: () => {
|
|
60
63
|
buildTransform();
|
|
61
64
|
},
|
|
62
65
|
takePicture: () => {
|
|
63
|
-
// TODO as png, so that you can download it via right click
|
|
64
|
-
// right now, you can only print it to pdf
|
|
65
66
|
const svg = $("svg[aria-roledescription='er']")[0];
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const link = document.createElement("a");
|
|
68
|
+
link.href = `data:image/svg+xml;base64,${btoa(
|
|
69
|
+
new XMLSerializer().serializeToString(svg)
|
|
70
|
+
)}`;
|
|
71
|
+
link.download = "er-diagram.svg";
|
|
72
|
+
link.click();
|
|
73
|
+
},
|
|
74
|
+
onWheel: (event) => {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
erHelper.zoom(-0.001 * event.deltaY);
|
|
77
|
+
},
|
|
78
|
+
onMouseDown: () => {
|
|
79
|
+
mouseDown = true;
|
|
80
|
+
isMoving = false;
|
|
81
|
+
},
|
|
82
|
+
onMouseUp: () => {
|
|
83
|
+
mouseDown = false;
|
|
84
|
+
},
|
|
85
|
+
onMouseMove: (event) => {
|
|
86
|
+
if (mouseDown) {
|
|
87
|
+
isMoving = true;
|
|
88
|
+
document.getSelection().removeAllRanges();
|
|
89
|
+
erHelper.translateX(event.movementX);
|
|
90
|
+
erHelper.translateY(event.movementY);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
isTranslating: () => {
|
|
94
|
+
return isMoving;
|
|
73
95
|
},
|
|
74
96
|
};
|
|
75
97
|
})();
|
|
@@ -122,9 +122,11 @@ function apply_showif() {
|
|
|
122
122
|
if (currentOptionsSet === qs) return;
|
|
123
123
|
|
|
124
124
|
const activate = (success, qs) => {
|
|
125
|
+
if (e.prop("data-fetch-options-current-set") === qs) return;
|
|
125
126
|
e.empty();
|
|
126
127
|
e.prop("data-fetch-options-current-set", qs);
|
|
127
|
-
|
|
128
|
+
const toAppend = [];
|
|
129
|
+
if (!dynwhere.required) toAppend.push(`<option></option>`);
|
|
128
130
|
let currentDataOption = undefined;
|
|
129
131
|
const dataOptions = [];
|
|
130
132
|
success.forEach((r) => {
|
|
@@ -141,8 +143,10 @@ function apply_showif() {
|
|
|
141
143
|
const html = `<option ${
|
|
142
144
|
selected ? "selected" : ""
|
|
143
145
|
} value="${value}">${label}</option>`;
|
|
144
|
-
|
|
146
|
+
toAppend.push(html);
|
|
145
147
|
});
|
|
148
|
+
e.html(toAppend.join(""));
|
|
149
|
+
|
|
146
150
|
//TODO: also sort inserted HTML options
|
|
147
151
|
dataOptions.sort((a, b) =>
|
|
148
152
|
(a.text?.toLowerCase?.() || a.text) >
|
|
@@ -853,30 +857,41 @@ function emptyAlerts() {
|
|
|
853
857
|
}
|
|
854
858
|
|
|
855
859
|
function press_store_button(clicked) {
|
|
856
|
-
|
|
857
|
-
$(clicked).
|
|
860
|
+
let btn = clicked;
|
|
861
|
+
if ($(clicked).is("form")) btn = $(clicked).find("button[type=submit]");
|
|
862
|
+
|
|
863
|
+
const width = $(btn).width();
|
|
864
|
+
$(btn).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
|
858
865
|
}
|
|
859
866
|
|
|
860
867
|
function common_done(res, isWeb = true) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
868
|
+
const handle = (element, fn) => {
|
|
869
|
+
if (Array.isArray(element)) for (const current of element) fn(current);
|
|
870
|
+
else fn(element);
|
|
871
|
+
};
|
|
872
|
+
if (res.notify) handle(res.notify, notifyAlert);
|
|
873
|
+
if (res.error)
|
|
874
|
+
handle(res.error, (text) => notifyAlert({ type: "danger", text: text }));
|
|
875
|
+
if (res.eval_js) handle(res.eval_js, eval);
|
|
876
|
+
|
|
864
877
|
if (res.reload_page) {
|
|
865
878
|
(isWeb ? location : parent.location).reload(); //TODO notify to cookie if reload or goto
|
|
866
879
|
}
|
|
867
880
|
if (res.download) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
881
|
+
handle(res.download, (download) => {
|
|
882
|
+
const dataurl = `data:${
|
|
883
|
+
download.mimetype || "application/octet-stream"
|
|
884
|
+
};base64,${download.blob}`;
|
|
885
|
+
fetch(dataurl)
|
|
886
|
+
.then((res) => res.blob())
|
|
887
|
+
.then((blob) => {
|
|
888
|
+
const link = document.createElement("a");
|
|
889
|
+
link.href = window.URL.createObjectURL(blob);
|
|
890
|
+
if (download.filename) link.download = download.filename;
|
|
891
|
+
else link.target = "_blank";
|
|
892
|
+
link.click();
|
|
893
|
+
});
|
|
894
|
+
});
|
|
880
895
|
}
|
|
881
896
|
if (res.goto && !isWeb)
|
|
882
897
|
// TODO ch
|
|
@@ -901,6 +916,12 @@ function common_done(res, isWeb = true) {
|
|
|
901
916
|
if (res.popup) {
|
|
902
917
|
ajax_modal(res.popup);
|
|
903
918
|
}
|
|
919
|
+
if (res.suppressed) {
|
|
920
|
+
notifyAlert({
|
|
921
|
+
type: "warning",
|
|
922
|
+
text: res.suppressed,
|
|
923
|
+
});
|
|
924
|
+
}
|
|
904
925
|
}
|
|
905
926
|
|
|
906
927
|
const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
|
package/public/saltcorn.js
CHANGED
|
@@ -195,8 +195,11 @@ function ajax_done(res) {
|
|
|
195
195
|
common_done(res);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
function view_post(viewname, route, data, onDone) {
|
|
199
|
-
|
|
198
|
+
function view_post(viewname, route, data, onDone, sendState) {
|
|
199
|
+
const query = sendState
|
|
200
|
+
? `?${new URL(get_current_state_url()).searchParams.toString()}`
|
|
201
|
+
: "";
|
|
202
|
+
$.ajax("/view/" + viewname + "/" + route + query, {
|
|
200
203
|
dataType: "json",
|
|
201
204
|
type: "POST",
|
|
202
205
|
headers: {
|
|
@@ -250,12 +253,15 @@ function close_saltcorn_modal() {
|
|
|
250
253
|
var myModalEl = document.getElementById("scmodal");
|
|
251
254
|
if (!myModalEl) return;
|
|
252
255
|
var modal = bootstrap.Modal.getInstance(myModalEl);
|
|
253
|
-
if (modal)
|
|
256
|
+
if (modal) {
|
|
257
|
+
if (modal.hide) modal.hide();
|
|
258
|
+
if (modal.dispose) modal.dispose();
|
|
259
|
+
}
|
|
254
260
|
}
|
|
255
261
|
|
|
256
262
|
function ensure_modal_exists_and_closed() {
|
|
257
263
|
if ($("#scmodal").length === 0) {
|
|
258
|
-
$("body").append(`<div id="scmodal"
|
|
264
|
+
$("body").append(`<div id="scmodal" class="modal">
|
|
259
265
|
<div class="modal-dialog">
|
|
260
266
|
<div class="modal-content">
|
|
261
267
|
<div class="modal-header">
|
|
@@ -264,7 +270,7 @@ function ensure_modal_exists_and_closed() {
|
|
|
264
270
|
<span class="sc-ajax-indicator-wrapper">
|
|
265
271
|
<span class="sc-ajax-indicator ms-2" style="display: none;"><i class="fas fa-save"></i></span>
|
|
266
272
|
</span>
|
|
267
|
-
<a class="sc-modal-linkout ms-2" href="" target="_blank"><i class="fas fa-expand-alt"></i></a>
|
|
273
|
+
<a class="sc-modal-linkout ms-2" onclick="close_saltcorn_modal()" href="" target="_blank"><i class="fas fa-expand-alt"></i></a>
|
|
268
274
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
|
|
269
275
|
</button>
|
|
270
276
|
</div>
|
package/routes/admin.js
CHANGED
package/routes/common_lists.js
CHANGED
|
@@ -101,9 +101,9 @@ const tablesList = async (tables, req, { tagId, domId, showList } = {}) => {
|
|
|
101
101
|
* @param {object} req
|
|
102
102
|
* @returns {Form}
|
|
103
103
|
*/
|
|
104
|
-
const editViewRoleForm = (view, roles, req) =>
|
|
104
|
+
const editViewRoleForm = (view, roles, req, on_done_redirect_str) =>
|
|
105
105
|
editRoleForm({
|
|
106
|
-
url: `/viewedit/setrole/${view.id}`,
|
|
106
|
+
url: `/viewedit/setrole/${view.id}${on_done_redirect_str || ""}`,
|
|
107
107
|
current_role: view.min_role,
|
|
108
108
|
roles,
|
|
109
109
|
req,
|
|
@@ -114,7 +114,7 @@ const editViewRoleForm = (view, roles, req) =>
|
|
|
114
114
|
* @param {object} req
|
|
115
115
|
* @returns {div}
|
|
116
116
|
*/
|
|
117
|
-
const view_dropdown = (view, req) =>
|
|
117
|
+
const view_dropdown = (view, req, on_done_redirect_str = "") =>
|
|
118
118
|
settingsDropdown(`dropdownMenuButton${view.id}`, [
|
|
119
119
|
a(
|
|
120
120
|
{
|
|
@@ -126,17 +126,19 @@ const view_dropdown = (view, req) =>
|
|
|
126
126
|
a(
|
|
127
127
|
{
|
|
128
128
|
class: "dropdown-item",
|
|
129
|
-
href: `/viewedit/edit/${encodeURIComponent(
|
|
129
|
+
href: `/viewedit/edit/${encodeURIComponent(
|
|
130
|
+
view.name
|
|
131
|
+
)}${on_done_redirect_str}`,
|
|
130
132
|
},
|
|
131
133
|
'<i class="fas fa-edit"></i> ' + req.__("Edit")
|
|
132
134
|
),
|
|
133
135
|
post_dropdown_item(
|
|
134
|
-
`/viewedit/add-to-menu/${view.id}`,
|
|
136
|
+
`/viewedit/add-to-menu/${view.id}${on_done_redirect_str}`,
|
|
135
137
|
'<i class="fas fa-bars"></i> ' + req.__("Add to menu"),
|
|
136
138
|
req
|
|
137
139
|
),
|
|
138
140
|
post_dropdown_item(
|
|
139
|
-
`/viewedit/clone/${view.id}`,
|
|
141
|
+
`/viewedit/clone/${view.id}${on_done_redirect_str}`,
|
|
140
142
|
'<i class="far fa-copy"></i> ' + req.__("Duplicate"),
|
|
141
143
|
req
|
|
142
144
|
),
|
|
@@ -149,7 +151,7 @@ const view_dropdown = (view, req) =>
|
|
|
149
151
|
),
|
|
150
152
|
div({ class: "dropdown-divider" }),
|
|
151
153
|
post_dropdown_item(
|
|
152
|
-
`/viewedit/delete/${view.id}`,
|
|
154
|
+
`/viewedit/delete/${view.id}${on_done_redirect_str}`,
|
|
153
155
|
'<i class="far fa-trash-alt"></i> ' + req.__("Delete"),
|
|
154
156
|
req,
|
|
155
157
|
true,
|
|
@@ -169,9 +171,15 @@ const setTableRefs = async (views) => {
|
|
|
169
171
|
return views;
|
|
170
172
|
};
|
|
171
173
|
|
|
172
|
-
const viewsList = async (
|
|
174
|
+
const viewsList = async (
|
|
175
|
+
views,
|
|
176
|
+
req,
|
|
177
|
+
{ tagId, domId, showList, on_done_redirect, notable } = {}
|
|
178
|
+
) => {
|
|
173
179
|
const roles = await User.get_roles();
|
|
174
|
-
|
|
180
|
+
const on_done_redirect_str = on_done_redirect
|
|
181
|
+
? `?on_done_redirect=${on_done_redirect}`
|
|
182
|
+
: "";
|
|
175
183
|
return views.length > 0
|
|
176
184
|
? mkTable(
|
|
177
185
|
[
|
|
@@ -200,29 +208,36 @@ const viewsList = async (views, req, { tagId, domId, showList } = {}) => {
|
|
|
200
208
|
? `javascript:set_state_field('_sortby', 'viewtemplate')`
|
|
201
209
|
: undefined,
|
|
202
210
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
...(notable
|
|
212
|
+
? []
|
|
213
|
+
: [
|
|
214
|
+
{
|
|
215
|
+
label: req.__("Table"),
|
|
216
|
+
key: (r) => link(`/table/${r.table}`, r.table),
|
|
217
|
+
sortlink: !tagId
|
|
218
|
+
? `javascript:set_state_field('_sortby', 'table')`
|
|
219
|
+
: undefined,
|
|
220
|
+
},
|
|
221
|
+
]),
|
|
210
222
|
{
|
|
211
223
|
label: req.__("Role to access"),
|
|
212
|
-
key: (row) =>
|
|
224
|
+
key: (row) =>
|
|
225
|
+
editViewRoleForm(row, roles, req, on_done_redirect_str),
|
|
213
226
|
},
|
|
214
227
|
{
|
|
215
228
|
label: "",
|
|
216
229
|
key: (r) =>
|
|
217
230
|
link(
|
|
218
|
-
`/viewedit/config/${encodeURIComponent(
|
|
231
|
+
`/viewedit/config/${encodeURIComponent(
|
|
232
|
+
r.name
|
|
233
|
+
)}${on_done_redirect_str}`,
|
|
219
234
|
req.__("Configure")
|
|
220
235
|
),
|
|
221
236
|
},
|
|
222
237
|
!tagId
|
|
223
238
|
? {
|
|
224
239
|
label: "",
|
|
225
|
-
key: (r) => view_dropdown(r, req),
|
|
240
|
+
key: (r) => view_dropdown(r, req, on_done_redirect_str),
|
|
226
241
|
}
|
|
227
242
|
: {
|
|
228
243
|
label: req.__("Remove From Tag"),
|