@saltcorn/server 0.7.1-beta.2 → 0.7.2-beta.0
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 +7 -1
- package/locales/zh.json +888 -0
- package/package.json +10 -8
- package/public/jquery-menu-editor.min.js +5 -0
- package/public/saltcorn.css +9 -0
- package/public/saltcorn.js +71 -7
- package/routes/fields.js +13 -1
- package/routes/files.js +51 -0
- package/routes/plugins.js +26 -13
- package/routes/viewedit.js +45 -0
- package/tests/clientjs.test.js +55 -0
- package/tests/plugins.test.js +2 -2
- package/tests/view.test.js +1 -1
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2-beta.0",
|
|
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.7.
|
|
10
|
-
"@saltcorn/builder": "0.7.
|
|
11
|
-
"@saltcorn/data": "0.7.
|
|
12
|
-
"@saltcorn/admin-models": "0.7.
|
|
13
|
-
"@saltcorn/markup": "0.7.
|
|
14
|
-
"@saltcorn/sbadmin2": "0.7.
|
|
9
|
+
"@saltcorn/base-plugin": "0.7.2-beta.0",
|
|
10
|
+
"@saltcorn/builder": "0.7.2-beta.0",
|
|
11
|
+
"@saltcorn/data": "0.7.2-beta.0",
|
|
12
|
+
"@saltcorn/admin-models": "0.7.2-beta.0",
|
|
13
|
+
"@saltcorn/markup": "0.7.2-beta.0",
|
|
14
|
+
"@saltcorn/sbadmin2": "0.7.2-beta.0",
|
|
15
15
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
16
16
|
"@socket.io/sticky": "^1.0.1",
|
|
17
17
|
"aws-sdk": "^2.1037.0",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"pg": "^8.2.1",
|
|
47
47
|
"pluralize": "^8.0.0",
|
|
48
48
|
"qrcode": "1.5.0",
|
|
49
|
+
"sharp": "0.30.3",
|
|
49
50
|
"socket.io": "4.2.0",
|
|
50
51
|
"thirty-two": "1.0.2",
|
|
51
52
|
"tmp-promise": "^3.0.2",
|
|
@@ -57,7 +58,8 @@
|
|
|
57
58
|
},
|
|
58
59
|
"repository": "github:saltcorn/saltcorn",
|
|
59
60
|
"devDependencies": {
|
|
60
|
-
"jest": "
|
|
61
|
+
"jest": "26.6.3",
|
|
62
|
+
"jest-environment-jsdom": "^26.6.2",
|
|
61
63
|
"supertest": "^4.0.2"
|
|
62
64
|
},
|
|
63
65
|
"scripts": {
|
|
@@ -117,6 +117,7 @@ function MenuEditor(e, t) {
|
|
|
117
117
|
(n.prev("div").children(".sortableListsOpener").first().remove(),
|
|
118
118
|
n.remove()),
|
|
119
119
|
MenuEditor.updateButtons(s);
|
|
120
|
+
l.onUpdate();
|
|
120
121
|
}
|
|
121
122
|
}),
|
|
122
123
|
$(document).on("click", ".btnEdit", function (e) {
|
|
@@ -145,11 +146,13 @@ function MenuEditor(e, t) {
|
|
|
145
146
|
e.preventDefault();
|
|
146
147
|
var t = $(this).closest("li");
|
|
147
148
|
t.prev("li").before(t), MenuEditor.updateButtons(s);
|
|
149
|
+
l.onUpdate();
|
|
148
150
|
}),
|
|
149
151
|
s.on("click", ".btnDown", function (e) {
|
|
150
152
|
e.preventDefault();
|
|
151
153
|
var t = $(this).closest("li");
|
|
152
154
|
t.next("li").after(t), MenuEditor.updateButtons(s);
|
|
155
|
+
l.onUpdate();
|
|
153
156
|
}),
|
|
154
157
|
s.on("click", ".btnOut", function (e) {
|
|
155
158
|
e.preventDefault();
|
|
@@ -161,6 +164,7 @@ function MenuEditor(e, t) {
|
|
|
161
164
|
t.remove()),
|
|
162
165
|
MenuEditor.updateButtons(s),
|
|
163
166
|
s.updateLevels();
|
|
167
|
+
l.onUpdate();
|
|
164
168
|
}),
|
|
165
169
|
s.on("click", ".btnIn", function (e) {
|
|
166
170
|
e.preventDefault();
|
|
@@ -174,6 +178,7 @@ function MenuEditor(e, t) {
|
|
|
174
178
|
l.append(n), n.append(t), l.addClass("sortableListsOpen"), f(l);
|
|
175
179
|
}
|
|
176
180
|
MenuEditor.updateButtons(s), s.updateLevels();
|
|
181
|
+
l.onUpdate();
|
|
177
182
|
}),
|
|
178
183
|
(this.setForm = function (e) {
|
|
179
184
|
i = e;
|
package/public/saltcorn.css
CHANGED
package/public/saltcorn.js
CHANGED
|
@@ -329,17 +329,36 @@ function enable_codemirror(f) {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
//https://stackoverflow.com/a/6021027
|
|
332
|
-
function updateQueryStringParameter(
|
|
332
|
+
function updateQueryStringParameter(uri1, key, value) {
|
|
333
|
+
let hash = "";
|
|
334
|
+
let uri = uri1;
|
|
335
|
+
if (uri && uri.includes("#")) {
|
|
336
|
+
let uris = uri1.split("#");
|
|
337
|
+
hash = "#" + uris[1];
|
|
338
|
+
uri = uris[0];
|
|
339
|
+
}
|
|
340
|
+
|
|
333
341
|
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
|
|
334
342
|
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
|
335
343
|
if (uri.match(re)) {
|
|
336
|
-
return
|
|
344
|
+
return (
|
|
345
|
+
uri.replace(re, "$1" + key + "=" + encodeURIComponent(value) + "$2") +
|
|
346
|
+
hash
|
|
347
|
+
);
|
|
337
348
|
} else {
|
|
338
|
-
return uri + separator + key + "=" + encodeURIComponent(value);
|
|
349
|
+
return uri + separator + key + "=" + encodeURIComponent(value) + hash;
|
|
339
350
|
}
|
|
340
351
|
}
|
|
341
352
|
|
|
342
|
-
function removeQueryStringParameter(
|
|
353
|
+
function removeQueryStringParameter(uri1, key) {
|
|
354
|
+
let hash = "";
|
|
355
|
+
let uri = uri1;
|
|
356
|
+
if (uri && uri.includes("#")) {
|
|
357
|
+
let uris = uri1.split("#");
|
|
358
|
+
hash = "#" + uris[1];
|
|
359
|
+
uri = uris[0];
|
|
360
|
+
}
|
|
361
|
+
|
|
343
362
|
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
|
|
344
363
|
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
|
345
364
|
if (uri.match(re)) {
|
|
@@ -347,7 +366,7 @@ function removeQueryStringParameter(uri, key) {
|
|
|
347
366
|
}
|
|
348
367
|
if (uri[uri.length - 1] === "?" || uri[uri.length - 1] === "&")
|
|
349
368
|
uri = uri.substring(0, uri.length - 1);
|
|
350
|
-
return uri;
|
|
369
|
+
return uri + hash;
|
|
351
370
|
}
|
|
352
371
|
|
|
353
372
|
function select_id(id) {
|
|
@@ -548,6 +567,10 @@ function ajax_modal(url, opts = {}) {
|
|
|
548
567
|
</div>
|
|
549
568
|
</div>
|
|
550
569
|
</div>`);
|
|
570
|
+
} else if ($("#scmodal").hasClass("show")) {
|
|
571
|
+
var myModalEl = document.getElementById("scmodal");
|
|
572
|
+
var modal = bootstrap.Modal.getInstance(myModalEl);
|
|
573
|
+
modal.dispose();
|
|
551
574
|
}
|
|
552
575
|
if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
|
|
553
576
|
else $("#scmodal").removeClass("no-submit-reload");
|
|
@@ -595,6 +618,47 @@ function saveAndContinue(e, k) {
|
|
|
595
618
|
return false;
|
|
596
619
|
}
|
|
597
620
|
|
|
621
|
+
function applyViewConfig(e, url) {
|
|
622
|
+
var form = $(e).closest("form");
|
|
623
|
+
var form_data = form.serializeArray();
|
|
624
|
+
const cfg = {};
|
|
625
|
+
form_data.forEach((item) => {
|
|
626
|
+
cfg[item.name] = item.value;
|
|
627
|
+
});
|
|
628
|
+
$.ajax(url, {
|
|
629
|
+
type: "POST",
|
|
630
|
+
dataType: "json",
|
|
631
|
+
contentType: "application/json",
|
|
632
|
+
headers: {
|
|
633
|
+
"CSRF-Token": _sc_globalCsrf,
|
|
634
|
+
},
|
|
635
|
+
data: JSON.stringify(cfg),
|
|
636
|
+
error: function (request) {},
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const repeaterCopyValuesToForm = (form, editor) => {
|
|
643
|
+
const vs = JSON.parse(editor.getString());
|
|
644
|
+
//console.log(vs)
|
|
645
|
+
const setVal = (k, ix, v) => {
|
|
646
|
+
const $e = form.find(`input[name="${k}_${ix}"]`);
|
|
647
|
+
if ($e.length) $e.val(v);
|
|
648
|
+
else
|
|
649
|
+
form.append(
|
|
650
|
+
`<input type="hidden" name="${k}_${ix}" value="${v}"></input>`
|
|
651
|
+
);
|
|
652
|
+
};
|
|
653
|
+
vs.forEach((v, ix) => {
|
|
654
|
+
Object.entries(v).forEach(([k, v]) => {
|
|
655
|
+
//console.log(ix, k, typeof v, v)
|
|
656
|
+
if (typeof v === "boolean") setVal(k, ix, v ? "on" : "");
|
|
657
|
+
else setVal(k, ix, v);
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
};
|
|
661
|
+
|
|
598
662
|
function ajaxSubmitForm(e) {
|
|
599
663
|
var form = $(e).closest("form");
|
|
600
664
|
var url = form.attr("action");
|
|
@@ -811,13 +875,13 @@ const columnSummary = (col) => {
|
|
|
811
875
|
if (!col) return "Unknown";
|
|
812
876
|
switch (col.type) {
|
|
813
877
|
case "Field":
|
|
814
|
-
return `Field ${col.field_name} ${col.fieldview}`;
|
|
878
|
+
return `Field ${col.field_name} ${col.fieldview || ""}`;
|
|
815
879
|
case "Link":
|
|
816
880
|
return `Link ${col.link_text}`;
|
|
817
881
|
case "JoinField":
|
|
818
882
|
return `Join ${col.join_field}`;
|
|
819
883
|
case "ViewLink":
|
|
820
|
-
return `View ${col.view_label || col.view.split(":")[1] || ""}`;
|
|
884
|
+
return `View link ${col.view_label || col.view.split(":")[1] || ""}`;
|
|
821
885
|
case "Action":
|
|
822
886
|
return `Action ${col.action_label || col.action_name}`;
|
|
823
887
|
case "Aggregation":
|
package/routes/fields.js
CHANGED
|
@@ -629,10 +629,14 @@ router.post(
|
|
|
629
629
|
*/
|
|
630
630
|
router.post(
|
|
631
631
|
"/show-calculated/:tableName/:fieldName/:fieldview",
|
|
632
|
-
isAdmin,
|
|
633
632
|
error_catcher(async (req, res) => {
|
|
634
633
|
const { tableName, fieldName, fieldview } = req.params;
|
|
635
634
|
const table = await Table.findOne({ name: tableName });
|
|
635
|
+
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
636
|
+
if (role > table.min_role_read) {
|
|
637
|
+
res.status(401).send("");
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
636
640
|
const fields = await table.getFields();
|
|
637
641
|
const row = { ...req.body };
|
|
638
642
|
readState(row, fields);
|
|
@@ -643,6 +647,10 @@ router.post(
|
|
|
643
647
|
if (kpath.length === 2 && row[kpath[0]]) {
|
|
644
648
|
const field = fields.find((f) => f.name === kpath[0]);
|
|
645
649
|
const reftable = await Table.findOne({ name: field.reftable_name });
|
|
650
|
+
if (role > reftable.min_role_read) {
|
|
651
|
+
res.status(401).send("");
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
646
654
|
const targetField = (await reftable.getFields()).find(
|
|
647
655
|
(f) => f.name === kpath[1]
|
|
648
656
|
);
|
|
@@ -671,6 +679,10 @@ router.post(
|
|
|
671
679
|
if (field.is_fkey) {
|
|
672
680
|
const reftable = await Table.findOne({ name: field.reftable_name });
|
|
673
681
|
if (!oldRow[ref]) break;
|
|
682
|
+
if (role > reftable.min_role_read) {
|
|
683
|
+
res.status(401).send("");
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
674
686
|
const q = { [reftable.pk_name]: oldRow[ref] };
|
|
675
687
|
oldRow = await reftable.getRow(q);
|
|
676
688
|
oldTable = reftable;
|
package/routes/files.js
CHANGED
|
@@ -9,6 +9,7 @@ const File = require("@saltcorn/data/models/file");
|
|
|
9
9
|
const User = require("@saltcorn/data/models/user");
|
|
10
10
|
const { getState } = require("@saltcorn/data/db/state");
|
|
11
11
|
const s3storage = require("../s3storage");
|
|
12
|
+
const sharp = require("sharp");
|
|
12
13
|
|
|
13
14
|
const {
|
|
14
15
|
mkTable,
|
|
@@ -43,6 +44,8 @@ const {
|
|
|
43
44
|
config_fields_form,
|
|
44
45
|
save_config_from_form,
|
|
45
46
|
} = require("../markup/admin");
|
|
47
|
+
const fsp = require("fs").promises;
|
|
48
|
+
const fs = require("fs");
|
|
46
49
|
|
|
47
50
|
/**
|
|
48
51
|
* @type {object}
|
|
@@ -187,6 +190,54 @@ router.get(
|
|
|
187
190
|
})
|
|
188
191
|
);
|
|
189
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @name get/resize/:id
|
|
195
|
+
* @function
|
|
196
|
+
* @memberof module:routes/files~filesRouter
|
|
197
|
+
* @function
|
|
198
|
+
*/
|
|
199
|
+
router.get(
|
|
200
|
+
"/resize/:id/:width_str",
|
|
201
|
+
error_catcher(async (req, res) => {
|
|
202
|
+
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
203
|
+
const user_id = req.user && req.user.id;
|
|
204
|
+
const { id, width_str } = req.params;
|
|
205
|
+
let file;
|
|
206
|
+
if (typeof strictParseInt(id) !== "undefined")
|
|
207
|
+
file = await File.findOne({ id });
|
|
208
|
+
else file = await File.findOne({ filename: id });
|
|
209
|
+
|
|
210
|
+
if (!file) {
|
|
211
|
+
res
|
|
212
|
+
.status(404)
|
|
213
|
+
.sendWrap(req.__("Not found"), h1(req.__("File not found")));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (role <= file.min_role_read || (user_id && user_id === file.user_id)) {
|
|
217
|
+
res.type(file.mimetype);
|
|
218
|
+
const cacheability = file.min_role_read === 10 ? "public" : "private";
|
|
219
|
+
res.set("Cache-Control", `${cacheability}, max-age=86400`);
|
|
220
|
+
//TODO s3
|
|
221
|
+
if (file.s3_store) s3storage.serveObject(file, res, false);
|
|
222
|
+
else {
|
|
223
|
+
const width = strictParseInt(width_str);
|
|
224
|
+
if (!width) {
|
|
225
|
+
res.sendFile(file.location);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const fnm = `${file.location}_w${width}`;
|
|
229
|
+
if (!fs.existsSync(fnm)) {
|
|
230
|
+
await sharp(file.location).resize({ width }).toFile(fnm);
|
|
231
|
+
}
|
|
232
|
+
res.sendFile(fnm);
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
req.flash("warning", req.__("Not authorized"));
|
|
236
|
+
res.redirect("/");
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
|
|
190
241
|
/**
|
|
191
242
|
* @name post/setrole/:id
|
|
192
243
|
* @function
|
package/routes/plugins.js
CHANGED
|
@@ -149,6 +149,7 @@ const local_has_theme = (name) => {
|
|
|
149
149
|
*/
|
|
150
150
|
const get_store_items = async () => {
|
|
151
151
|
const installed_plugins = await Plugin.find({});
|
|
152
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
152
153
|
|
|
153
154
|
const instore = await Plugin.store_plugins_available();
|
|
154
155
|
const packs_available = await fetch_available_packs();
|
|
@@ -156,15 +157,18 @@ const get_store_items = async () => {
|
|
|
156
157
|
const schema = db.getTenantSchema();
|
|
157
158
|
const installed_plugin_names = installed_plugins.map((p) => p.name);
|
|
158
159
|
const store_plugin_names = instore.map((p) => p.name);
|
|
159
|
-
const plugins_item = instore
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
const plugins_item = instore
|
|
161
|
+
.map((plugin) => ({
|
|
162
|
+
name: plugin.name,
|
|
163
|
+
installed: installed_plugin_names.includes(plugin.name),
|
|
164
|
+
plugin: true,
|
|
165
|
+
description: plugin.description,
|
|
166
|
+
documentation_link: plugin.documentation_link,
|
|
167
|
+
has_theme: plugin.has_theme,
|
|
168
|
+
has_auth: plugin.has_auth,
|
|
169
|
+
unsafe: plugin.unsafe,
|
|
170
|
+
}))
|
|
171
|
+
.filter((p) => !p.unsafe || isRoot);
|
|
168
172
|
const local_logins = installed_plugins
|
|
169
173
|
.filter((p) => !store_plugin_names.includes(p.name) && p.name !== "base")
|
|
170
174
|
.map((plugin) => ({
|
|
@@ -648,16 +652,16 @@ router.get(
|
|
|
648
652
|
error_catcher(async (req, res) => {
|
|
649
653
|
const { plugin } = req.params;
|
|
650
654
|
const filepath = req.params[0];
|
|
655
|
+
const hasVersion = plugin.includes("@");
|
|
651
656
|
const location =
|
|
652
|
-
getState().plugin_locations[
|
|
653
|
-
plugin.includes("@") ? plugin.split("@")[0] : plugin
|
|
654
|
-
];
|
|
657
|
+
getState().plugin_locations[hasVersion ? plugin.split("@")[0] : plugin];
|
|
655
658
|
if (location) {
|
|
656
659
|
const safeFile = path
|
|
657
660
|
.normalize(filepath)
|
|
658
661
|
.replace(/^(\.\.(\/|\\|$))+/, "");
|
|
659
662
|
const fullpath = path.join(location, "public", safeFile);
|
|
660
|
-
if (fs.existsSync(fullpath))
|
|
663
|
+
if (fs.existsSync(fullpath))
|
|
664
|
+
res.sendFile(fullpath, { maxAge: hasVersion ? "100d" : "1d" });
|
|
661
665
|
else res.status(404).send(req.__("Not found"));
|
|
662
666
|
} else {
|
|
663
667
|
res.status(404).send(req.__("Not found"));
|
|
@@ -961,6 +965,15 @@ router.post(
|
|
|
961
965
|
res.redirect(`/plugins`);
|
|
962
966
|
return;
|
|
963
967
|
}
|
|
968
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
969
|
+
if (!isRoot && plugin.unsafe) {
|
|
970
|
+
req.flash(
|
|
971
|
+
"error",
|
|
972
|
+
req.__("Cannot install unsafe plugins on subdomain tenants")
|
|
973
|
+
);
|
|
974
|
+
res.redirect(`/plugins`);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
964
977
|
delete plugin.id;
|
|
965
978
|
await load_plugins.loadAndSaveNewPlugin(plugin);
|
|
966
979
|
const plugin_module = getState().plugins[name];
|
package/routes/viewedit.js
CHANGED
|
@@ -582,8 +582,11 @@ router.get(
|
|
|
582
582
|
return;
|
|
583
583
|
}
|
|
584
584
|
const configFlow = await view.get_config_flow(req);
|
|
585
|
+
const hasConfig =
|
|
586
|
+
view.configuration && Object.keys(view.configuration).length > 0;
|
|
585
587
|
const wfres = await configFlow.run(
|
|
586
588
|
{
|
|
589
|
+
id: hasConfig ? view.id : undefined,
|
|
587
590
|
table_id: view.table_id,
|
|
588
591
|
exttable_name: view.exttable_name,
|
|
589
592
|
viewname: name,
|
|
@@ -704,6 +707,48 @@ router.post(
|
|
|
704
707
|
})
|
|
705
708
|
);
|
|
706
709
|
|
|
710
|
+
/**
|
|
711
|
+
* @name post/saveconfig/:id
|
|
712
|
+
* @function
|
|
713
|
+
* @memberof module:routes/viewedit~vieweditRouter
|
|
714
|
+
* @function
|
|
715
|
+
*/
|
|
716
|
+
router.post(
|
|
717
|
+
"/saveconfig/:viewname",
|
|
718
|
+
isAdmin,
|
|
719
|
+
error_catcher(async (req, res) => {
|
|
720
|
+
const { viewname } = req.params;
|
|
721
|
+
|
|
722
|
+
if (viewname && req.body) {
|
|
723
|
+
const view = await View.findOne({ name: viewname });
|
|
724
|
+
const configFlow = await view.get_config_flow(req);
|
|
725
|
+
const step = await configFlow.singleStepForm(req.body, req);
|
|
726
|
+
if (step?.renderForm) {
|
|
727
|
+
if (!step.renderForm.hasErrors) {
|
|
728
|
+
let newcfg;
|
|
729
|
+
if (step.contextField)
|
|
730
|
+
newcfg = {
|
|
731
|
+
...view.configuration,
|
|
732
|
+
[step.contextField]: {
|
|
733
|
+
...view.configuration?.[step.contextField],
|
|
734
|
+
...step.renderForm.values,
|
|
735
|
+
},
|
|
736
|
+
};
|
|
737
|
+
else newcfg = { ...view.configuration, ...step.renderForm.values };
|
|
738
|
+
await View.update({ configuration: newcfg }, view.id);
|
|
739
|
+
res.json({ success: "ok" });
|
|
740
|
+
} else {
|
|
741
|
+
res.json({ error: step.renderForm.errorSummary });
|
|
742
|
+
}
|
|
743
|
+
} else {
|
|
744
|
+
res.json({ error: "no form" });
|
|
745
|
+
}
|
|
746
|
+
} else {
|
|
747
|
+
res.json({ error: "no view" });
|
|
748
|
+
}
|
|
749
|
+
})
|
|
750
|
+
);
|
|
751
|
+
|
|
707
752
|
/**
|
|
708
753
|
* @name post/setrole/:id
|
|
709
754
|
* @function
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const load_script = (fnm) => {
|
|
8
|
+
const srcFile = fs.readFileSync(path.join(__dirname, "..", "public", fnm), {
|
|
9
|
+
encoding: "utf-8",
|
|
10
|
+
});
|
|
11
|
+
const scriptEl = document.createElement("script");
|
|
12
|
+
scriptEl.textContent = srcFile;
|
|
13
|
+
document.body.appendChild(scriptEl);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
load_script("jquery-3.6.0.min.js");
|
|
17
|
+
load_script("saltcorn.js");
|
|
18
|
+
|
|
19
|
+
test("updateQueryStringParameter", () => {
|
|
20
|
+
const element = document.createElement("div");
|
|
21
|
+
expect(element).not.toBeNull();
|
|
22
|
+
expect(updateQueryStringParameter("/foo", "age", 43)).toBe("/foo?age=43");
|
|
23
|
+
expect(updateQueryStringParameter("/foo?age=44", "age", 43)).toBe(
|
|
24
|
+
"/foo?age=43"
|
|
25
|
+
);
|
|
26
|
+
expect(updateQueryStringParameter("/foo?name=Bar", "age", 43)).toBe(
|
|
27
|
+
"/foo?name=Bar&age=43"
|
|
28
|
+
);
|
|
29
|
+
expect(removeQueryStringParameter("/foo?age=44", "age")).toBe("/foo");
|
|
30
|
+
expect(removeQueryStringParameter("/foo?name=Bar", "age")).toBe(
|
|
31
|
+
"/foo?name=Bar"
|
|
32
|
+
);
|
|
33
|
+
expect(removeQueryStringParameter("/foo?name=Bar&age=45", "age")).toBe(
|
|
34
|
+
"/foo?name=Bar"
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("updateQueryStringParameter hash", () => {
|
|
39
|
+
expect(updateQueryStringParameter("/foo#baz", "age", 43)).toBe(
|
|
40
|
+
"/foo?age=43#baz"
|
|
41
|
+
);
|
|
42
|
+
expect(updateQueryStringParameter("/foo?age=44#Baz", "age", 43)).toBe(
|
|
43
|
+
"/foo?age=43#Baz"
|
|
44
|
+
);
|
|
45
|
+
expect(updateQueryStringParameter("/foo?name=Bar#Zap", "age", 43)).toBe(
|
|
46
|
+
"/foo?name=Bar&age=43#Zap"
|
|
47
|
+
);
|
|
48
|
+
expect(removeQueryStringParameter("/foo?age=44#Baz", "age")).toBe("/foo#Baz");
|
|
49
|
+
expect(removeQueryStringParameter("/foo?name=Bar#Baz", "age")).toBe(
|
|
50
|
+
"/foo?name=Bar#Baz"
|
|
51
|
+
);
|
|
52
|
+
expect(removeQueryStringParameter("/foo?name=Bar&age=45#Baz", "age")).toBe(
|
|
53
|
+
"/foo?name=Bar#Baz"
|
|
54
|
+
);
|
|
55
|
+
});
|
package/tests/plugins.test.js
CHANGED
|
@@ -90,7 +90,7 @@ describe("Plugin Endpoints", () => {
|
|
|
90
90
|
.expect(toInclude("testfilecontents"));
|
|
91
91
|
await request(app)
|
|
92
92
|
.get(
|
|
93
|
-
"/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.
|
|
93
|
+
"/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.4/css/sb-admin-2.min.css"
|
|
94
94
|
)
|
|
95
95
|
.expect(toInclude("Start Bootstrap"));
|
|
96
96
|
|
|
@@ -100,7 +100,7 @@ describe("Plugin Endpoints", () => {
|
|
|
100
100
|
.expect(toRedirect("/plugins"));
|
|
101
101
|
await request(app)
|
|
102
102
|
.get(
|
|
103
|
-
"/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.
|
|
103
|
+
"/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.4/css/sb-admin-2.min.css"
|
|
104
104
|
)
|
|
105
105
|
.expect(toInclude("Start Bootstrap"));
|
|
106
106
|
});
|