@saltcorn/server 1.1.2-beta.5 → 1.1.2-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "1.1.2-beta.5",
3
+ "version": "1.1.2-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",
@@ -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.5",
12
- "@saltcorn/builder": "1.1.2-beta.5",
13
- "@saltcorn/data": "1.1.2-beta.5",
14
- "@saltcorn/admin-models": "1.1.2-beta.5",
15
- "@saltcorn/filemanager": "1.1.2-beta.5",
16
- "@saltcorn/markup": "1.1.2-beta.5",
17
- "@saltcorn/plugins-loader": "1.1.2-beta.5",
18
- "@saltcorn/sbadmin2": "1.1.2-beta.5",
11
+ "@saltcorn/base-plugin": "1.1.2-beta.7",
12
+ "@saltcorn/builder": "1.1.2-beta.7",
13
+ "@saltcorn/data": "1.1.2-beta.7",
14
+ "@saltcorn/admin-models": "1.1.2-beta.7",
15
+ "@saltcorn/filemanager": "1.1.2-beta.7",
16
+ "@saltcorn/markup": "1.1.2-beta.7",
17
+ "@saltcorn/plugins-loader": "1.1.2-beta.7",
18
+ "@saltcorn/sbadmin2": "1.1.2-beta.7",
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
- Function("return " + val);
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, viewnameOrElem, isWeb = true) {
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
@@ -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("/");
@@ -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
+ });