@saltcorn/server 0.8.7-beta.1 → 0.8.7-beta.2

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 CHANGED
@@ -1174,5 +1174,6 @@
1174
1174
  "Splash Page": "Splash Page",
1175
1175
  "App version": "App version",
1176
1176
  "Forgot password?": "Forgot password?",
1177
- "Details": "Details"
1177
+ "Details": "Details",
1178
+ "URL is a formula?": "URL is a formula?"
1178
1179
  }
package/markup/admin.js CHANGED
@@ -81,6 +81,7 @@ const add_edit_bar = ({
81
81
  }) => {
82
82
  if (role > 1 && req && req.xhr) return { above: [contents] }; //make sure not put in card
83
83
  if (role > 1) return contents;
84
+ if (req && req.headers.localizedstate) return { above: [contents] };
84
85
  let viewSpec = "";
85
86
  if (viewtemplate) viewSpec = viewtemplate;
86
87
  if (table) {
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.8.7-beta.1",
3
+ "version": "0.8.7-beta.2",
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.7-beta.1",
10
- "@saltcorn/builder": "0.8.7-beta.1",
11
- "@saltcorn/data": "0.8.7-beta.1",
12
- "@saltcorn/admin-models": "0.8.7-beta.1",
13
- "@saltcorn/filemanager": "0.8.7-beta.1",
14
- "@saltcorn/markup": "0.8.7-beta.1",
15
- "@saltcorn/sbadmin2": "0.8.7-beta.1",
9
+ "@saltcorn/base-plugin": "0.8.7-beta.2",
10
+ "@saltcorn/builder": "0.8.7-beta.2",
11
+ "@saltcorn/data": "0.8.7-beta.2",
12
+ "@saltcorn/admin-models": "0.8.7-beta.2",
13
+ "@saltcorn/filemanager": "0.8.7-beta.2",
14
+ "@saltcorn/markup": "0.8.7-beta.2",
15
+ "@saltcorn/sbadmin2": "0.8.7-beta.2",
16
16
  "@socket.io/cluster-adapter": "^0.2.1",
17
17
  "@socket.io/sticky": "^1.0.1",
18
18
  "adm-zip": "0.5.10",
@@ -4,10 +4,11 @@ function showHideCol(nm, e) {
4
4
  }
5
5
 
6
6
  function lookupIntToString(cell, formatterParams, onRendered) {
7
- const cellVal = cell.getValue()
8
- const val = typeof cellVal === "object" && cellVal !== null
9
- ? `${cellVal.id}`
10
- : `${cellVal}`;
7
+ const cellVal = cell.getValue();
8
+ const val =
9
+ typeof cellVal === "object" && cellVal !== null
10
+ ? `${cellVal.id}`
11
+ : `${cellVal}`;
11
12
  const res = formatterParams.values[val];
12
13
  return res;
13
14
  }
@@ -19,10 +20,11 @@ function deleteIcon() {
19
20
  function flatpickerEditor(cell, onRendered, success, cancel, editorParams) {
20
21
  var input = $("<input type='text'/>");
21
22
  const dayOnly = editorParams && editorParams.dayOnly;
22
- let defaultDate = cell.getValue()
23
+ let defaultDate = cell.getValue();
23
24
 
24
- if (!defaultDate) defaultDate = new Date()
25
+ if (!defaultDate) defaultDate = new Date();
25
26
  input.flatpickr({
27
+ disableMobile: true, // the native picker has problems combined with tabulator
26
28
  enableTime: !dayOnly,
27
29
  dateFormat: dayOnly ? "Y-m-d" : "Z",
28
30
  time_24hr: true,
@@ -30,7 +32,7 @@ function flatpickerEditor(cell, onRendered, success, cancel, editorParams) {
30
32
  defaultDate,
31
33
  onClose: function (selectedDates, dateStr, instance) {
32
34
  evt = window.event;
33
- var isEscape = false;
35
+ var isEscape = false;
34
36
  if ("key" in evt) {
35
37
  isEscape = evt.key === "Escape" || evt.key === "Esc";
36
38
  } else {
@@ -156,7 +158,8 @@ function delete_tabulator_row(e, cell) {
156
158
  if (def && def.formatterParams && def.formatterParams.confirm) {
157
159
  if (!confirm("Are you sure you want to delete this row?")) return;
158
160
  }
159
- const tableName = def?.formatterParams?.tableName || window.tabulator_table_name
161
+ const tableName =
162
+ def?.formatterParams?.tableName || window.tabulator_table_name;
160
163
 
161
164
  const row = cell.getRow().getData();
162
165
  if (!row.id) {
@@ -568,6 +568,22 @@ function initialize_page() {
568
568
  </form>`
569
569
  );
570
570
  });
571
+ $("[mobile-img-path]").each(async function () {
572
+ if (parent.loadEncodedFile) {
573
+ const fileId = $(this).attr("mobile-img-path");
574
+ const base64Encoded = await parent.loadEncodedFile(fileId);
575
+ this.src = base64Encoded;
576
+ }
577
+ });
578
+ $("[mobile-bg-img-path]").each(async function () {
579
+ if (parent.loadEncodedFile) {
580
+ const fileId = $(this).attr("mobile-bg-img-path");
581
+ if (fileId) {
582
+ const base64Encoded = await parent.loadEncodedFile(fileId);
583
+ this.style.backgroundImage = `url("${base64Encoded}")`;
584
+ }
585
+ }
586
+ });
571
587
  function setExplainer(that) {
572
588
  var id = $(that).attr("id") + "_explainer";
573
589
 
@@ -61,9 +61,13 @@ function removeQueryStringParameter(uri1, key) {
61
61
  return uri + hash;
62
62
  }
63
63
 
64
- function get_current_state_url() {
64
+ function get_current_state_url(e) {
65
+ const localizer = e ? $(e).closest("[data-sc-local-state]") : [];
65
66
  let $modal = $("#scmodal");
66
- if ($modal.length === 0 || !$modal.hasClass("show"))
67
+ if (localizer.length) {
68
+ const localState = localizer.attr("data-sc-local-state") || "";
69
+ return localState;
70
+ } else if ($modal.length === 0 || !$modal.hasClass("show"))
67
71
  return window.location.href;
68
72
  else return $modal.prop("data-modal-state");
69
73
  }
@@ -72,8 +76,8 @@ function select_id(id) {
72
76
  pjax_to(updateQueryStringParameter(get_current_state_url(), "id", id));
73
77
  }
74
78
 
75
- function set_state_field(key, value) {
76
- pjax_to(updateQueryStringParameter(get_current_state_url(), key, value));
79
+ function set_state_field(key, value, e) {
80
+ pjax_to(updateQueryStringParameter(get_current_state_url(e), key, value), e);
77
81
  }
78
82
 
79
83
  function check_state_field(that) {
@@ -97,8 +101,8 @@ function invalidate_pagings(href) {
97
101
  return newhref;
98
102
  }
99
103
 
100
- function set_state_fields(kvs, disable_pjax) {
101
- let newhref = get_current_state_url();
104
+ function set_state_fields(kvs, disable_pjax, e) {
105
+ let newhref = get_current_state_url(e);
102
106
  if (Object.keys(kvs).some((k) => !is_paging_param(k))) {
103
107
  newhref = invalidate_pagings(newhref);
104
108
  }
@@ -108,10 +112,10 @@ function set_state_fields(kvs, disable_pjax) {
108
112
  else newhref = updateQueryStringParameter(newhref, kv[0], kv[1]);
109
113
  });
110
114
  if (disable_pjax) href_to(newhref.replace("&&", "&").replace("?&", "?"));
111
- else pjax_to(newhref.replace("&&", "&").replace("?&", "?"));
115
+ else pjax_to(newhref.replace("&&", "&").replace("?&", "?"), e);
112
116
  }
113
- function unset_state_field(key) {
114
- pjax_to(removeQueryStringParameter(get_current_state_url(), key));
117
+ function unset_state_field(key, e) {
118
+ pjax_to(removeQueryStringParameter(get_current_state_url(e), key), e);
115
119
  }
116
120
 
117
121
  let loadPage = true;
@@ -124,29 +128,37 @@ $(function () {
124
128
  });
125
129
  });
126
130
 
127
- function pjax_to(href) {
131
+ function pjax_to(href, e) {
128
132
  let $modal = $("#scmodal");
129
133
  const inModal = $modal.length && $modal.hasClass("show");
130
- let $dest = inModal ? $("#scmodal .modal-body") : $("#page-inner-content");
131
-
134
+ const localizer = e ? $(e).closest("[data-sc-local-state]") : [];
135
+ let $dest = localizer.length
136
+ ? localizer
137
+ : inModal
138
+ ? $("#scmodal .modal-body")
139
+ : $("#page-inner-content");
132
140
  if (!$dest.length) window.location.href = href;
133
141
  else {
134
142
  loadPage = false;
143
+ const headers = {
144
+ pjaxpageload: "true",
145
+ };
146
+ if (localizer.length) headers.localizedstate = "true";
135
147
  $.ajax(href, {
136
- headers: {
137
- pjaxpageload: "true",
138
- },
148
+ headers,
139
149
  success: function (res, textStatus, request) {
140
- if (!inModal) window.history.pushState({ url: href }, "", href);
150
+ if (!inModal && !localizer.length)
151
+ window.history.pushState({ url: href }, "", href);
141
152
  setTimeout(() => {
142
153
  loadPage = true;
143
154
  }, 0);
144
- if (!inModal && res.includes("<!--SCPT:")) {
155
+ if (!inModal && !localizer.length && res.includes("<!--SCPT:")) {
145
156
  const start = res.indexOf("<!--SCPT:");
146
157
  const end = res.indexOf("-->", start);
147
158
  document.title = res.substring(start + 9, end);
148
159
  }
149
160
  $dest.html(res);
161
+ if (localizer.length) localizer.attr("data-sc-local-state", href);
150
162
  initialize_page();
151
163
  },
152
164
  error: function (res) {
package/routes/actions.js CHANGED
@@ -577,17 +577,23 @@ router.get(
577
577
  const { id } = req.params;
578
578
  const trigger = await Trigger.findOne({ id });
579
579
  const output = [];
580
+ const ppVal = (x) =>
581
+ typeof x === "string"
582
+ ? x
583
+ : typeof x === "function"
584
+ ? x.toString()
585
+ : JSON.stringify(x, null, 2);
580
586
  const fakeConsole = {
581
587
  log(...s) {
582
588
  console.log(...s);
583
- output.push(div(code(pre(text(s.join(" "))))));
589
+ output.push(div(code(pre(text(s.map(ppVal).join(" "))))));
584
590
  },
585
591
  error(...s) {
586
592
  output.push(
587
593
  div(
588
594
  code(
589
595
  { style: "color:red;font-weight:bold;" },
590
- pre(text(s.join(" ")))
596
+ pre(text(s.map(ppVal).join(" ")))
591
597
  )
592
598
  )
593
599
  );
package/routes/admin.js CHANGED
@@ -451,7 +451,10 @@ router.get(
451
451
  "/snapshot-list",
452
452
  isAdmin,
453
453
  error_catcher(async (req, res) => {
454
- const snaps = await Snapshot.find();
454
+ const snaps = await Snapshot.find(
455
+ {},
456
+ { orderBy: "created", orderDesc: true, fields: ["id", "created", "hash"] }
457
+ );
455
458
  send_admin_page({
456
459
  res,
457
460
  req,
package/routes/api.js CHANGED
@@ -19,7 +19,10 @@ const Router = require("express-promise-router");
19
19
  const { error_catcher } = require("./utils.js");
20
20
  //const { mkTable, renderForm, link, post_btn } = require("@saltcorn/markup");
21
21
  const { getState } = require("@saltcorn/data/db/state");
22
- const { prepare_update_row } = require("@saltcorn/data/web-mobile-commons");
22
+ const {
23
+ prepare_update_row,
24
+ prepare_insert_row,
25
+ } = require("@saltcorn/data/web-mobile-commons");
23
26
  const Table = require("@saltcorn/data/models/table");
24
27
  const View = require("@saltcorn/data/models/view");
25
28
  //const Field = require("@saltcorn/data/models/field");
@@ -28,9 +31,9 @@ const Trigger = require("@saltcorn/data/models/trigger");
28
31
  const passport = require("passport");
29
32
 
30
33
  const {
31
- stateFieldsToWhere,
32
34
  readState,
33
35
  strictParseInt,
36
+ stateFieldsToWhere,
34
37
  } = require("@saltcorn/data/plugin-helper");
35
38
  const Crash = require("@saltcorn/data/models/crash");
36
39
 
@@ -391,34 +394,8 @@ router.post(
391
394
  const { _versions, ...row } = req.body;
392
395
  const fields = table.getFields();
393
396
  readState(row, fields, req);
394
- let errors = [];
395
- let hasErrors = false;
396
- Object.keys(row).forEach((k) => {
397
- const field = fields.find((f) => f.name === k);
398
- if (!field || field.calculated || row[k] === undefined) {
399
- delete row[k];
400
- return;
401
- }
402
- if (field.type && field.type.validate) {
403
- const vres = field.type.validate(field.attributes || {})(row[k]);
404
- if (vres.error) {
405
- hasErrors = true;
406
- errors.push(`${k}: ${vres.error}`);
407
- }
408
- }
409
- });
410
- fields.forEach((field) => {
411
- if (
412
- field.required &&
413
- !field.primary_key &&
414
- typeof row[field.name] === "undefined" &&
415
- !field.attributes.default
416
- ) {
417
- hasErrors = true;
418
- errors.push(`${field.name}: required`);
419
- }
420
- });
421
- if (hasErrors) {
397
+ const errors = await prepare_insert_row(row, fields);
398
+ if (errors.length > 0) {
422
399
  getState().log(
423
400
  2,
424
401
  `API POST ${table.name} error: ${errors.join(", ")}`
package/routes/files.js CHANGED
@@ -90,7 +90,7 @@ router.get(
90
90
  for (const file of rows) {
91
91
  file.location = file.path_to_serve;
92
92
  }
93
- const directories = await File.allDirectories();
93
+ const directories = await File.allDirectories(true);
94
94
  for (const file of directories) {
95
95
  file.location = file.path_to_serve;
96
96
  }
package/routes/menu.js CHANGED
@@ -151,10 +151,18 @@ const menuForm = async (req) => {
151
151
  {
152
152
  name: "url",
153
153
  label: req.__("URL"),
154
- class: "item-menu",
154
+ class: "item-menu validate-expression validate-expression-conditional",
155
155
  input_type: "text",
156
156
  showIf: { type: "Link" },
157
157
  },
158
+ {
159
+ name: "url_formula",
160
+ label: req.__("URL is a formula?"),
161
+ type: "Bool",
162
+ class: "item-menu",
163
+ required: false,
164
+ showIf: { type: "Link" },
165
+ },
158
166
  {
159
167
  name: "pagename",
160
168
  label: req.__("Page"),
package/routes/sync.js CHANGED
@@ -9,8 +9,9 @@ module.exports = router;
9
9
 
10
10
  const pickFields = (table, row) => {
11
11
  const result = {};
12
- for (const { name, type } of table.getFields()) {
13
- if (name === "id") continue;
12
+ const fields = table.getFields();
13
+ for (const { name, type, calculated } of table.getFields()) {
14
+ if (name === "id" || calculated) continue;
14
15
  if (type?.name === "Date") {
15
16
  result[name] = row[name] ? new Date(row[name]) : undefined;
16
17
  } else {
@@ -22,6 +22,7 @@ const View = require("@saltcorn/data/models/view");
22
22
  const Workflow = require("@saltcorn/data/models/workflow");
23
23
  const User = require("@saltcorn/data/models/user");
24
24
  const Page = require("@saltcorn/data/models/page");
25
+ const File = require("@saltcorn/data/models/file");
25
26
  const db = require("@saltcorn/data/db");
26
27
  const { sleep } = require("@saltcorn/data/utils");
27
28
 
@@ -580,6 +581,12 @@ router.get(
580
581
  "/config/:name",
581
582
  isAdmin,
582
583
  error_catcher(async (req, res) => {
584
+ req.socket.on("close", () => {
585
+ File.destroyDirCache();
586
+ });
587
+ req.socket.on("timeout", () => {
588
+ File.destroyDirCache();
589
+ });
583
590
  const { name } = req.params;
584
591
  const { step } = req.query;
585
592
  const [view] = await View.find({ name });
package/wrapper.js CHANGED
@@ -34,7 +34,7 @@ const get_menu = (req) => {
34
34
  const login_menu = state.getConfig("login_menu");
35
35
  const locale = req.getLocale();
36
36
  const __ = (s) => state.i18n.__({ phrase: s, locale }) || s;
37
- const extra_menu = get_extra_menu(role, __);
37
+ const extra_menu = get_extra_menu(role, __, req.user || {}, locale);
38
38
  const authItems = isAuth
39
39
  ? [
40
40
  {