@saltcorn/server 0.8.8-beta.5 → 0.8.8-beta.7
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/locales/en.json +3 -1
- package/package.json +8 -8
- package/public/saltcorn-common.js +74 -13
- package/public/saltcorn.css +5 -0
- package/public/saltcorn.js +29 -22
- package/routes/admin.js +1 -1
- package/routes/common_lists.js +3 -3
- package/routes/crashlog.js +1 -1
- package/routes/eventlog.js +1 -1
- package/routes/menu.js +17 -1
- package/routes/search.js +1 -1
- package/routes/viewedit.js +1 -22
- package/tests/table.test.js +5 -2
package/locales/en.json
CHANGED
|
@@ -1248,5 +1248,7 @@
|
|
|
1248
1248
|
"include": "include",
|
|
1249
1249
|
"Auto public login": "Auto public login",
|
|
1250
1250
|
"New user view": "New user view",
|
|
1251
|
-
"A view to show to new users, to finalise registration (if Edit) or as a welcome view": "A view to show to new users, to finalise registration (if Edit) or as a welcome view"
|
|
1251
|
+
"A view to show to new users, to finalise registration (if Edit) or as a welcome view": "A view to show to new users, to finalise registration (if Edit) or as a welcome view",
|
|
1252
|
+
"View decoration": "View decoration",
|
|
1253
|
+
"Title formula": "Title formula"
|
|
1252
1254
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.8-beta.
|
|
3
|
+
"version": "0.8.8-beta.7",
|
|
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.8-beta.
|
|
10
|
-
"@saltcorn/builder": "0.8.8-beta.
|
|
11
|
-
"@saltcorn/data": "0.8.8-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.8-beta.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.8-beta.
|
|
14
|
-
"@saltcorn/markup": "0.8.8-beta.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.8-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.8-beta.7",
|
|
10
|
+
"@saltcorn/builder": "0.8.8-beta.7",
|
|
11
|
+
"@saltcorn/data": "0.8.8-beta.7",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.8-beta.7",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.8-beta.7",
|
|
14
|
+
"@saltcorn/markup": "0.8.8-beta.7",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.8-beta.7",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
|
@@ -702,6 +702,11 @@ function initialize_page() {
|
|
|
702
702
|
}
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
|
+
setTimeout(() => {
|
|
706
|
+
$("#toasts-area")
|
|
707
|
+
.find(".show[rendered='server-side'][type='success']")
|
|
708
|
+
.removeClass("show");
|
|
709
|
+
}, 5000);
|
|
705
710
|
}
|
|
706
711
|
|
|
707
712
|
$(initialize_page);
|
|
@@ -852,6 +857,68 @@ function tristateClick(nm) {
|
|
|
852
857
|
}
|
|
853
858
|
}
|
|
854
859
|
|
|
860
|
+
function buildToast(txt, type, spin) {
|
|
861
|
+
const realtype = type === "error" ? "danger" : type;
|
|
862
|
+
const icon =
|
|
863
|
+
realtype === "success"
|
|
864
|
+
? "fa-check-circle"
|
|
865
|
+
: realtype === "danger"
|
|
866
|
+
? "fa-times-circle"
|
|
867
|
+
: realtype === "warning"
|
|
868
|
+
? "fa-exclamation-triangle"
|
|
869
|
+
: "";
|
|
870
|
+
const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
|
|
871
|
+
const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
|
|
872
|
+
return {
|
|
873
|
+
id: rndid,
|
|
874
|
+
html: `
|
|
875
|
+
<div
|
|
876
|
+
class="toast show"
|
|
877
|
+
id="${rndid}"
|
|
878
|
+
rendered="client-side",
|
|
879
|
+
role="alert"
|
|
880
|
+
aria-live="assertive"
|
|
881
|
+
aria-atomic="true"
|
|
882
|
+
style="min-width: 350px; max-width: 50vw; width: auto; z-index: 999; ${
|
|
883
|
+
!isNode ? "transform: translateX(-50%);" : ""
|
|
884
|
+
}"
|
|
885
|
+
>
|
|
886
|
+
<div class="toast-header bg-${realtype} text-white py-1 ">
|
|
887
|
+
<i class="fas ${icon} me-2"></i>
|
|
888
|
+
<strong class="me-auto" >
|
|
889
|
+
${type}
|
|
890
|
+
</strong>
|
|
891
|
+
${
|
|
892
|
+
spin
|
|
893
|
+
? ""
|
|
894
|
+
: `<button
|
|
895
|
+
type="button"
|
|
896
|
+
class="btn-close btn-close-white"
|
|
897
|
+
data-bs-dismiss="toast"
|
|
898
|
+
aria-label="Close"
|
|
899
|
+
style="font-size: 12px;"
|
|
900
|
+
></button>`
|
|
901
|
+
}
|
|
902
|
+
</div>
|
|
903
|
+
<div
|
|
904
|
+
class="toast-body py-2 fs-6 fw-bold d-flex align-items-center"
|
|
905
|
+
>
|
|
906
|
+
<strong>${txt}</strong>
|
|
907
|
+
${
|
|
908
|
+
spin
|
|
909
|
+
? `<span
|
|
910
|
+
class="spinner-border ms-auto"
|
|
911
|
+
role="status"
|
|
912
|
+
aria-hidden="true"
|
|
913
|
+
style="width: 1.5rem; height: 1.5rem"></span>`
|
|
914
|
+
: ""
|
|
915
|
+
}
|
|
916
|
+
</div>
|
|
917
|
+
</div>
|
|
918
|
+
`,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
855
922
|
function notifyAlert(note, spin) {
|
|
856
923
|
if (Array.isArray(note)) {
|
|
857
924
|
note.forEach(notifyAlert);
|
|
@@ -865,23 +932,17 @@ function notifyAlert(note, spin) {
|
|
|
865
932
|
txt = note.text;
|
|
866
933
|
type = note.type;
|
|
867
934
|
}
|
|
868
|
-
|
|
869
|
-
$("#
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
${
|
|
875
|
-
spin
|
|
876
|
-
? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
|
|
877
|
-
: `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
878
|
-
</button>`
|
|
935
|
+
const { id, html } = buildToast(txt, type, spin);
|
|
936
|
+
$("#toasts-area").append(html);
|
|
937
|
+
if (type === "success") {
|
|
938
|
+
setTimeout(() => {
|
|
939
|
+
$(`#${id}`).removeClass("show");
|
|
940
|
+
}, 5000);
|
|
879
941
|
}
|
|
880
|
-
</div>`);
|
|
881
942
|
}
|
|
882
943
|
|
|
883
944
|
function emptyAlerts() {
|
|
884
|
-
$("#
|
|
945
|
+
$("#toasts-area").html("");
|
|
885
946
|
}
|
|
886
947
|
|
|
887
948
|
function press_store_button(clicked) {
|
package/public/saltcorn.css
CHANGED
package/public/saltcorn.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
function sortby(k, desc, viewIdentifier) {
|
|
2
|
-
set_state_fields(
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
? "
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
function sortby(k, desc, viewIdentifier, e) {
|
|
2
|
+
set_state_fields(
|
|
3
|
+
{
|
|
4
|
+
[viewIdentifier ? `_${viewIdentifier}_sortby` : "_sortby"]: k,
|
|
5
|
+
[viewIdentifier ? `_${viewIdentifier}_sortdesc` : "_sortdesc"]: desc
|
|
6
|
+
? "on"
|
|
7
|
+
: { unset: true },
|
|
8
|
+
},
|
|
9
|
+
false,
|
|
10
|
+
e
|
|
11
|
+
);
|
|
8
12
|
}
|
|
9
|
-
function gopage(n, pagesize, viewIdentifier, extra = {}) {
|
|
13
|
+
function gopage(n, pagesize, viewIdentifier, extra = {}, e) {
|
|
10
14
|
const cfg = {
|
|
11
15
|
...extra,
|
|
12
16
|
[viewIdentifier ? `_${viewIdentifier}_page` : "_page"]: n,
|
|
13
17
|
[viewIdentifier ? `_${viewIdentifier}_pagesize` : "_pagesize"]: pagesize,
|
|
14
18
|
};
|
|
15
|
-
set_state_fields(cfg);
|
|
19
|
+
set_state_fields(cfg, false, e);
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
if (localStorage.getItem("reload_on_init")) {
|
|
@@ -72,28 +76,31 @@ function get_current_state_url(e) {
|
|
|
72
76
|
else return $modal.prop("data-modal-state");
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
function select_id(id) {
|
|
76
|
-
pjax_to(updateQueryStringParameter(get_current_state_url(), "id", id));
|
|
79
|
+
function select_id(id, e) {
|
|
80
|
+
pjax_to(updateQueryStringParameter(get_current_state_url(e), "id", id), e);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
function set_state_field(key, value, e) {
|
|
80
84
|
pjax_to(updateQueryStringParameter(get_current_state_url(e), key, value), e);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
function check_state_field(that) {
|
|
87
|
+
function check_state_field(that, e) {
|
|
84
88
|
const checked = that.checked;
|
|
85
89
|
const name = that.name;
|
|
86
90
|
const value = encodeURIComponent(that.value);
|
|
87
|
-
var separator =
|
|
91
|
+
var separator = get_current_state_url(e).indexOf("?") !== -1 ? "&" : "?";
|
|
88
92
|
let dest;
|
|
89
|
-
if (checked) dest = get_current_state_url() + `${separator}${name}=${value}`;
|
|
90
|
-
else dest = get_current_state_url().replace(`${name}=${value}`, "");
|
|
91
|
-
pjax_to(dest.replace("&&", "&").replace("?&", "?"));
|
|
93
|
+
if (checked) dest = get_current_state_url(e) + `${separator}${name}=${value}`;
|
|
94
|
+
else dest = get_current_state_url(e).replace(`${name}=${value}`, "");
|
|
95
|
+
pjax_to(dest.replace("&&", "&").replace("?&", "?"), e);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
function invalidate_pagings(href) {
|
|
95
99
|
let newhref = href;
|
|
96
|
-
const
|
|
100
|
+
const prev = new URL(window.location.href);
|
|
101
|
+
const queryObj = Object.fromEntries(
|
|
102
|
+
new URL(newhref, prev.origin).searchParams.entries()
|
|
103
|
+
);
|
|
97
104
|
const toRemove = Object.keys(queryObj).filter((val) => is_paging_param(val));
|
|
98
105
|
for (const k of toRemove) {
|
|
99
106
|
newhref = removeQueryStringParameter(newhref, k);
|
|
@@ -173,12 +180,12 @@ function pjax_to(href, e) {
|
|
|
173
180
|
function href_to(href) {
|
|
174
181
|
window.location.href = href;
|
|
175
182
|
}
|
|
176
|
-
function clear_state(omit_fields_str) {
|
|
177
|
-
let newUrl = get_current_state_url().split("?")[0];
|
|
178
|
-
const hash = get_current_state_url().split("#")[1];
|
|
183
|
+
function clear_state(omit_fields_str, e) {
|
|
184
|
+
let newUrl = get_current_state_url(e).split("?")[0];
|
|
185
|
+
const hash = get_current_state_url(e).split("#")[1];
|
|
179
186
|
if (omit_fields_str) {
|
|
180
187
|
const omit_fields = omit_fields_str.split(",").map((s) => s.trim());
|
|
181
|
-
let qs = (get_current_state_url().split("?")[1] || "").split("#")[0];
|
|
188
|
+
let qs = (get_current_state_url(e).split("?")[1] || "").split("#")[0];
|
|
182
189
|
let params = new URLSearchParams(qs);
|
|
183
190
|
newUrl = newUrl + "?";
|
|
184
191
|
omit_fields.forEach((f) => {
|
|
@@ -188,7 +195,7 @@ function clear_state(omit_fields_str) {
|
|
|
188
195
|
}
|
|
189
196
|
if (hash) newUrl += "#" + hash;
|
|
190
197
|
|
|
191
|
-
pjax_to(newUrl);
|
|
198
|
+
pjax_to(newUrl, e);
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
function ajax_done(res) {
|
package/routes/admin.js
CHANGED
|
@@ -1464,7 +1464,7 @@ const buildDialogScript = () => {
|
|
|
1464
1464
|
}
|
|
1465
1465
|
|
|
1466
1466
|
function handleMessages() {
|
|
1467
|
-
notifyAlert("Building the app, please wait.")
|
|
1467
|
+
notifyAlert("Building the app, please wait.", true)
|
|
1468
1468
|
${
|
|
1469
1469
|
getState().getConfig("apple_team_id") &&
|
|
1470
1470
|
getState().getConfig("apple_team_id") !== "null"
|
package/routes/common_lists.js
CHANGED
|
@@ -187,7 +187,7 @@ const viewsList = async (
|
|
|
187
187
|
label: req.__("Name"),
|
|
188
188
|
key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
|
|
189
189
|
sortlink: !tagId
|
|
190
|
-
? `
|
|
190
|
+
? `set_state_field('_sortby', 'name', this)`
|
|
191
191
|
: undefined,
|
|
192
192
|
},
|
|
193
193
|
// description - currently I dont want to show description in view list
|
|
@@ -205,7 +205,7 @@ const viewsList = async (
|
|
|
205
205
|
label: req.__("Pattern"),
|
|
206
206
|
key: "viewtemplate",
|
|
207
207
|
sortlink: !tagId
|
|
208
|
-
? `
|
|
208
|
+
? `set_state_field('_sortby', 'viewtemplate', this)`
|
|
209
209
|
: undefined,
|
|
210
210
|
},
|
|
211
211
|
...(notable
|
|
@@ -215,7 +215,7 @@ const viewsList = async (
|
|
|
215
215
|
label: req.__("Table"),
|
|
216
216
|
key: (r) => link(`/table/${r.table}`, r.table),
|
|
217
217
|
sortlink: !tagId
|
|
218
|
-
? `
|
|
218
|
+
? `set_state_field('_sortby', 'table', this)`
|
|
219
219
|
: undefined,
|
|
220
220
|
},
|
|
221
221
|
]),
|
package/routes/crashlog.js
CHANGED
package/routes/eventlog.js
CHANGED
|
@@ -332,7 +332,7 @@ router.get(
|
|
|
332
332
|
page_opts.pagination = {
|
|
333
333
|
current_page,
|
|
334
334
|
pages: Math.ceil(nrows / rows_per_page),
|
|
335
|
-
get_page_link: (n) => `
|
|
335
|
+
get_page_link: (n) => `gopage(${n}, ${rows_per_page})`,
|
|
336
336
|
};
|
|
337
337
|
}
|
|
338
338
|
}
|
package/routes/menu.js
CHANGED
|
@@ -70,7 +70,7 @@ const menuForm = async (req) => {
|
|
|
70
70
|
.map(([k, v]) => k),
|
|
71
71
|
];
|
|
72
72
|
const triggers = Trigger.find({
|
|
73
|
-
when_trigger: {or: ["API call", "Never"]},
|
|
73
|
+
when_trigger: { or: ["API call", "Never"] },
|
|
74
74
|
});
|
|
75
75
|
triggers.forEach((tr) => {
|
|
76
76
|
actions.push(tr.name);
|
|
@@ -155,6 +155,22 @@ const menuForm = async (req) => {
|
|
|
155
155
|
class: "item-menu",
|
|
156
156
|
required: false,
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
name: "target_blank",
|
|
160
|
+
label: req.__("Open in new tab"),
|
|
161
|
+
type: "Bool",
|
|
162
|
+
required: false,
|
|
163
|
+
class: "item-menu",
|
|
164
|
+
showIf: { type: ["View", "Page", "Link"] },
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "in_modal",
|
|
168
|
+
label: req.__("Open in popup modal?"),
|
|
169
|
+
type: "Bool",
|
|
170
|
+
required: false,
|
|
171
|
+
class: "item-menu",
|
|
172
|
+
showIf: { type: ["View", "Page", "Link"] },
|
|
173
|
+
},
|
|
158
174
|
{
|
|
159
175
|
name: "url",
|
|
160
176
|
label: req.__("URL"),
|
package/routes/search.js
CHANGED
|
@@ -201,7 +201,7 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
201
201
|
pages: current_page + (vresps.length === page_size ? 1 : 0),
|
|
202
202
|
trailing_ellipsis: vresps.length === page_size,
|
|
203
203
|
get_page_link: (n) =>
|
|
204
|
-
`
|
|
204
|
+
`gopage(${n}, ${page_size}, undefined, {table:'${tableName}'}, this)`,
|
|
205
205
|
});
|
|
206
206
|
}
|
|
207
207
|
|
package/routes/viewedit.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
|
|
10
|
-
const { renderForm, renderBuilder,
|
|
10
|
+
const { renderForm, renderBuilder, toast } = require("@saltcorn/markup");
|
|
11
11
|
const tags = require("@saltcorn/markup/tags");
|
|
12
12
|
const { p, a, div, script, text, domReady, code, pre, tbody, tr, th, td } =
|
|
13
13
|
tags;
|
|
@@ -65,27 +65,7 @@ router.get(
|
|
|
65
65
|
|
|
66
66
|
const viewMarkup = await viewsList(views, req);
|
|
67
67
|
const tables = await Table.find();
|
|
68
|
-
const viewAccessWarning = (view) => {
|
|
69
|
-
const table = tables.find((t) => t.name === view.table);
|
|
70
|
-
if (!table) return false;
|
|
71
|
-
if (table.name === "users") return false;
|
|
72
|
-
if (table.ownership_field_id || table.ownership_formula) return false;
|
|
73
68
|
|
|
74
|
-
return table.min_role_read < view.min_role;
|
|
75
|
-
};
|
|
76
|
-
const hasAccessWarning = views.filter(viewAccessWarning);
|
|
77
|
-
const accessWarning =
|
|
78
|
-
hasAccessWarning.length > 0
|
|
79
|
-
? alert(
|
|
80
|
-
"danger",
|
|
81
|
-
`<p>${req.__(
|
|
82
|
-
`You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.`
|
|
83
|
-
)}</p>
|
|
84
|
-
${req.__("Views potentially affected")}: ${hasAccessWarning
|
|
85
|
-
.map((v) => v.name)
|
|
86
|
-
.join(", ")}`
|
|
87
|
-
)
|
|
88
|
-
: "";
|
|
89
69
|
res.sendWrap(req.__(`Views`), {
|
|
90
70
|
above: [
|
|
91
71
|
{
|
|
@@ -97,7 +77,6 @@ router.get(
|
|
|
97
77
|
class: "mt-0",
|
|
98
78
|
title: req.__("Your views"),
|
|
99
79
|
contents: [
|
|
100
|
-
accessWarning,
|
|
101
80
|
viewMarkup,
|
|
102
81
|
tables.length > 0
|
|
103
82
|
? a(
|
package/tests/table.test.js
CHANGED
|
@@ -250,8 +250,11 @@ Gordon Kane, 218`;
|
|
|
250
250
|
await request(app)
|
|
251
251
|
.get("/table/")
|
|
252
252
|
.set("Cookie", loginCookie)
|
|
253
|
-
.expect(
|
|
254
|
-
|
|
253
|
+
.expect(
|
|
254
|
+
toInclude(
|
|
255
|
+
"cannot drop table books because other objects depend on it"
|
|
256
|
+
)
|
|
257
|
+
);
|
|
255
258
|
});
|
|
256
259
|
});
|
|
257
260
|
describe("deletion to table with row ownership", () => {
|