@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 +2 -1
- package/markup/admin.js +1 -0
- package/package.json +8 -8
- package/public/gridedit.js +11 -8
- package/public/saltcorn-common.js +16 -0
- package/public/saltcorn.js +29 -17
- package/routes/actions.js +8 -2
- package/routes/admin.js +4 -1
- package/routes/api.js +7 -30
- package/routes/files.js +1 -1
- package/routes/menu.js +9 -1
- package/routes/sync.js +3 -2
- package/routes/viewedit.js +7 -0
- package/wrapper.js +1 -1
package/locales/en.json
CHANGED
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.
|
|
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.
|
|
10
|
-
"@saltcorn/builder": "0.8.7-beta.
|
|
11
|
-
"@saltcorn/data": "0.8.7-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.7-beta.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.7-beta.
|
|
14
|
-
"@saltcorn/markup": "0.8.7-beta.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.7-beta.
|
|
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",
|
package/public/gridedit.js
CHANGED
|
@@ -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 =
|
|
9
|
-
|
|
10
|
-
|
|
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 =
|
|
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
|
|
package/public/saltcorn.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
395
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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 {
|
package/routes/viewedit.js
CHANGED
|
@@ -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
|
{
|