@saltcorn/server 1.1.2-beta.4 → 1.1.2-beta.6
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/package.json +9 -9
- package/public/saltcorn-common.js +31 -2
- package/public/saltcorn.js +32 -4
- package/routes/actions.js +5 -2
- package/routes/fields.js +10 -2
- package/tests/view.test.js +38 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.1.2-beta.
|
|
3
|
+
"version": "1.1.2-beta.6",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@aws-sdk/client-s3": "^3.735.0",
|
|
10
10
|
"@dr.pogodin/csurf": "^1.14.1",
|
|
11
|
-
"@saltcorn/base-plugin": "1.1.2-beta.
|
|
12
|
-
"@saltcorn/builder": "1.1.2-beta.
|
|
13
|
-
"@saltcorn/data": "1.1.2-beta.
|
|
14
|
-
"@saltcorn/admin-models": "1.1.2-beta.
|
|
15
|
-
"@saltcorn/filemanager": "1.1.2-beta.
|
|
16
|
-
"@saltcorn/markup": "1.1.2-beta.
|
|
17
|
-
"@saltcorn/plugins-loader": "1.1.2-beta.
|
|
18
|
-
"@saltcorn/sbadmin2": "1.1.2-beta.
|
|
11
|
+
"@saltcorn/base-plugin": "1.1.2-beta.6",
|
|
12
|
+
"@saltcorn/builder": "1.1.2-beta.6",
|
|
13
|
+
"@saltcorn/data": "1.1.2-beta.6",
|
|
14
|
+
"@saltcorn/admin-models": "1.1.2-beta.6",
|
|
15
|
+
"@saltcorn/filemanager": "1.1.2-beta.6",
|
|
16
|
+
"@saltcorn/markup": "1.1.2-beta.6",
|
|
17
|
+
"@saltcorn/plugins-loader": "1.1.2-beta.6",
|
|
18
|
+
"@saltcorn/sbadmin2": "1.1.2-beta.6",
|
|
19
19
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
20
20
|
"@socket.io/sticky": "^1.0.1",
|
|
21
21
|
"adm-zip": "0.5.16",
|
|
@@ -920,6 +920,27 @@ function initialize_page() {
|
|
|
920
920
|
if (e.keyCode === 13) e.target.blur();
|
|
921
921
|
});
|
|
922
922
|
|
|
923
|
+
const validate_identifier_elem = (target) => {
|
|
924
|
+
const next = target.next();
|
|
925
|
+
if (next.hasClass("expr-error")) next.remove();
|
|
926
|
+
const val = target.val();
|
|
927
|
+
if (!val) return;
|
|
928
|
+
try {
|
|
929
|
+
Function(val, "return 1");
|
|
930
|
+
} catch (error) {
|
|
931
|
+
target.after(`<small class="text-danger font-monospace d-block expr-error">
|
|
932
|
+
Invalid identifier
|
|
933
|
+
</small>`);
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
$(".validate-identifier").attr("spellcheck", false);
|
|
937
|
+
$(".validate-expression").attr("spellcheck", false);
|
|
938
|
+
|
|
939
|
+
$(".validate-identifier").bind("input", function (e) {
|
|
940
|
+
const target = $(e.target);
|
|
941
|
+
validate_identifier_elem(target);
|
|
942
|
+
});
|
|
943
|
+
|
|
923
944
|
const validate_expression_elem = (target) => {
|
|
924
945
|
const next = target.next();
|
|
925
946
|
if (next.hasClass("expr-error")) next.remove();
|
|
@@ -932,7 +953,10 @@ function initialize_page() {
|
|
|
932
953
|
}
|
|
933
954
|
if (!val) return;
|
|
934
955
|
try {
|
|
935
|
-
|
|
956
|
+
const AsyncFunction = Object.getPrototypeOf(
|
|
957
|
+
async function () {}
|
|
958
|
+
).constructor;
|
|
959
|
+
AsyncFunction("return " + val);
|
|
936
960
|
} catch (error) {
|
|
937
961
|
target.after(`<small class="text-danger font-monospace d-block expr-error">
|
|
938
962
|
${error.message}
|
|
@@ -943,6 +967,7 @@ function initialize_page() {
|
|
|
943
967
|
const target = $(e.target);
|
|
944
968
|
validate_expression_elem(target);
|
|
945
969
|
});
|
|
970
|
+
|
|
946
971
|
$(".validate-expression-conditional").each(function () {
|
|
947
972
|
const theInput = $(this);
|
|
948
973
|
theInput
|
|
@@ -1573,7 +1598,11 @@ function restore_old_button(btnId) {
|
|
|
1573
1598
|
btn.removeData("old-text");
|
|
1574
1599
|
}
|
|
1575
1600
|
|
|
1576
|
-
async function common_done(res,
|
|
1601
|
+
async function common_done(res, viewnameOrElem0, isWeb = true) {
|
|
1602
|
+
const viewnameOrElem =
|
|
1603
|
+
viewnameOrElem0 === "undefined"
|
|
1604
|
+
? last_route_viewname
|
|
1605
|
+
: viewnameOrElem0 || last_route_viewname;
|
|
1577
1606
|
const viewname =
|
|
1578
1607
|
typeof viewnameOrElem === "string"
|
|
1579
1608
|
? viewnameOrElem
|
package/public/saltcorn.js
CHANGED
|
@@ -34,10 +34,7 @@ function updateQueryStringParameter(uri1, key, value) {
|
|
|
34
34
|
uri = uris[0];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
var re = new RegExp(
|
|
38
|
-
"([?&])" + escapeRegExp(key) + "=.*?(&|$)",
|
|
39
|
-
"i"
|
|
40
|
-
);
|
|
37
|
+
var re = new RegExp("([?&])" + escapeRegExp(key) + "=.*?(&|$)", "i");
|
|
41
38
|
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
|
42
39
|
if (uri.match(re)) {
|
|
43
40
|
if (Array.isArray(value)) {
|
|
@@ -267,6 +264,8 @@ function reset_spinners() {
|
|
|
267
264
|
});
|
|
268
265
|
}
|
|
269
266
|
|
|
267
|
+
let last_route_viewname;
|
|
268
|
+
|
|
270
269
|
function view_post(viewnameOrElem, route, data, onDone, sendState) {
|
|
271
270
|
const viewname =
|
|
272
271
|
typeof viewnameOrElem === "string"
|
|
@@ -274,6 +273,7 @@ function view_post(viewnameOrElem, route, data, onDone, sendState) {
|
|
|
274
273
|
: $(viewnameOrElem)
|
|
275
274
|
.closest("[data-sc-embed-viewname]")
|
|
276
275
|
.attr("data-sc-embed-viewname");
|
|
276
|
+
last_route_viewname = viewname;
|
|
277
277
|
const query = sendState
|
|
278
278
|
? `?${new URL(get_current_state_url()).searchParams.toString()}`
|
|
279
279
|
: "";
|
|
@@ -1322,6 +1322,34 @@ function check_delete_unsaved(tablename, script_tag) {
|
|
|
1322
1322
|
}
|
|
1323
1323
|
}
|
|
1324
1324
|
|
|
1325
|
+
function handle_identical_fields(event) {
|
|
1326
|
+
let form = null;
|
|
1327
|
+
if (event.currentTarget.tagName === "FORM") form = event.currentTarget;
|
|
1328
|
+
else form = $(event.currentTarget).closest("form")[0];
|
|
1329
|
+
if (!form) {
|
|
1330
|
+
console.warn("No form found");
|
|
1331
|
+
} else {
|
|
1332
|
+
const name = event.target.name;
|
|
1333
|
+
const newValue = event.target.value;
|
|
1334
|
+
const tagName = event.target.tagName;
|
|
1335
|
+
const isRadio = event.target.type === "radio";
|
|
1336
|
+
if (tagName === "SELECT" || isRadio) {
|
|
1337
|
+
form.querySelectorAll(`select[name="${name}"]`).forEach((select) => {
|
|
1338
|
+
$(select).val(newValue); //.trigger("change");
|
|
1339
|
+
});
|
|
1340
|
+
form
|
|
1341
|
+
.querySelectorAll(`input[type="radio"][name="${name}"]`)
|
|
1342
|
+
.forEach((input) => {
|
|
1343
|
+
input.checked = input.value === newValue;
|
|
1344
|
+
});
|
|
1345
|
+
} else if (tagName === "INPUT") {
|
|
1346
|
+
form.querySelectorAll(`input[name="${name}"]`).forEach((input) => {
|
|
1347
|
+
input.value = newValue;
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1325
1353
|
(() => {
|
|
1326
1354
|
const e = document.querySelector("[data-sidebar-toggler]");
|
|
1327
1355
|
let closed = localStorage.getItem("sidebarClosed") === "true";
|
package/routes/actions.js
CHANGED
|
@@ -730,6 +730,7 @@ const getWorkflowStepForm = async (
|
|
|
730
730
|
label: req.__("Step name"),
|
|
731
731
|
type: "String",
|
|
732
732
|
required: true,
|
|
733
|
+
class: "validate-identifier",
|
|
733
734
|
sublabel: "An identifier by which this step can be referred to.",
|
|
734
735
|
validator: jsIdentifierValidator,
|
|
735
736
|
},
|
|
@@ -742,6 +743,7 @@ const getWorkflowStepForm = async (
|
|
|
742
743
|
{
|
|
743
744
|
name: "wf_only_if",
|
|
744
745
|
label: req.__("Only if..."),
|
|
746
|
+
class: "validate-expression",
|
|
745
747
|
sublabel:
|
|
746
748
|
"Optional JavaScript expression based on the run context. If given, the chosen action will only be executed if evaluates to true",
|
|
747
749
|
type: "String",
|
|
@@ -1716,6 +1718,7 @@ const getWorkflowStepUserForm = async (run, trigger, step, req) => {
|
|
|
1716
1718
|
const form = new Form({
|
|
1717
1719
|
action: `/actions/fill-workflow-form/${run.id}`,
|
|
1718
1720
|
submitLabel: run.wait_info.output ? req.__("OK") : req.__("Submit"),
|
|
1721
|
+
onSubmit: "press_store_button(this)",
|
|
1719
1722
|
blurb,
|
|
1720
1723
|
formStyle: run.wait_info.output || req.xhr ? "vert" : undefined,
|
|
1721
1724
|
fields: await run.userFormFields(step),
|
|
@@ -1788,8 +1791,8 @@ router.post(
|
|
|
1788
1791
|
if (req.xhr) {
|
|
1789
1792
|
const retDirs = await run.popReturnDirectives();
|
|
1790
1793
|
|
|
1791
|
-
if (runres?.popup) retDirs.popup = runres.popup;
|
|
1792
|
-
res.json({ success: "ok", ...retDirs });
|
|
1794
|
+
//if (runres?.popup) retDirs.popup = runres.popup;
|
|
1795
|
+
res.json({ success: "ok", ...runres, ...retDirs });
|
|
1793
1796
|
} else {
|
|
1794
1797
|
if (run.context.goto) res.redirect(run.context.goto);
|
|
1795
1798
|
else res.redirect("/");
|
package/routes/fields.js
CHANGED
|
@@ -1378,6 +1378,7 @@ router.post(
|
|
|
1378
1378
|
agg_outcome_type,
|
|
1379
1379
|
agg_fieldview,
|
|
1380
1380
|
agg_field,
|
|
1381
|
+
mode,
|
|
1381
1382
|
_columndef,
|
|
1382
1383
|
} = req.body || {};
|
|
1383
1384
|
const table = Table.findOne({ name: tableName });
|
|
@@ -1389,7 +1390,9 @@ router.post(
|
|
|
1389
1390
|
return;
|
|
1390
1391
|
}
|
|
1391
1392
|
const field = table.getField(agg_field);
|
|
1392
|
-
const cfgfields = await applyAsync(fv.configFields, field || { table }
|
|
1393
|
+
const cfgfields = await applyAsync(fv.configFields, field || { table }, {
|
|
1394
|
+
mode,
|
|
1395
|
+
});
|
|
1393
1396
|
res.json(cfgfields);
|
|
1394
1397
|
return;
|
|
1395
1398
|
}
|
|
@@ -1412,7 +1415,12 @@ router.post(
|
|
|
1412
1415
|
res.send(req.query?.accept == "json" ? "[]" : "");
|
|
1413
1416
|
return;
|
|
1414
1417
|
}
|
|
1415
|
-
const fieldViewConfigForms = await calcfldViewConfig(
|
|
1418
|
+
const fieldViewConfigForms = await calcfldViewConfig(
|
|
1419
|
+
[field],
|
|
1420
|
+
false,
|
|
1421
|
+
0,
|
|
1422
|
+
mode
|
|
1423
|
+
);
|
|
1416
1424
|
const formFields = fieldViewConfigForms[field.name][fv_name];
|
|
1417
1425
|
if (!formFields) {
|
|
1418
1426
|
res.send(req.query?.accept == "json" ? "[]" : "");
|
package/tests/view.test.js
CHANGED
|
@@ -1134,3 +1134,41 @@ describe("legacy relations with relation path", () => {
|
|
|
1134
1134
|
.expect(toInclude("Delete"));
|
|
1135
1135
|
});
|
|
1136
1136
|
});
|
|
1137
|
+
|
|
1138
|
+
describe("identical fields", () => {
|
|
1139
|
+
it("runs a post with an author array ", async () => {
|
|
1140
|
+
const app = await getApp({ disableCsrf: true });
|
|
1141
|
+
const loginCookie = await getAdminLoginCookie();
|
|
1142
|
+
await request(app)
|
|
1143
|
+
.post("/view/authoredit_identicals")
|
|
1144
|
+
.set("Cookie", loginCookie)
|
|
1145
|
+
.send("author=Charles&author=Charles")
|
|
1146
|
+
.expect(toRedirect("/view/authorlist"));
|
|
1147
|
+
const table = Table.findOne({ name: "books" });
|
|
1148
|
+
const rows = await table.getRows();
|
|
1149
|
+
expect(rows).toContainEqual({
|
|
1150
|
+
id: 3,
|
|
1151
|
+
author: "Charles",
|
|
1152
|
+
pages: 678,
|
|
1153
|
+
publisher: null,
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it("runs a post with only one author", async () => {
|
|
1158
|
+
const app = await getApp({ disableCsrf: true });
|
|
1159
|
+
const loginCookie = await getAdminLoginCookie();
|
|
1160
|
+
await request(app)
|
|
1161
|
+
.post("/view/authoredit_identicals")
|
|
1162
|
+
.set("Cookie", loginCookie)
|
|
1163
|
+
.send("author=Fjodor")
|
|
1164
|
+
.expect(toRedirect("/view/authorlist"));
|
|
1165
|
+
const table = Table.findOne({ name: "books" });
|
|
1166
|
+
const rows = await table.getRows();
|
|
1167
|
+
expect(rows).toContainEqual({
|
|
1168
|
+
id: 4,
|
|
1169
|
+
author: "Fjodor",
|
|
1170
|
+
pages: 678,
|
|
1171
|
+
publisher: null,
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
});
|