@saltcorn/server 1.1.2-beta.0 → 1.1.2-beta.10
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/CHANGELOG.md +12 -1
- package/app.js +3 -2
- package/locales/en.json +2 -1
- package/package.json +12 -11
- package/public/saltcorn-common.js +49 -6
- package/public/saltcorn.css +8 -0
- package/public/saltcorn.js +32 -4
- package/routes/actions.js +5 -2
- package/routes/admin.js +7 -5
- package/routes/common_lists.js +4 -4
- package/routes/fields.js +10 -2
- package/routes/homepage.js +4 -1
- package/routes/plugins.js +1 -0
- package/routes/tables.js +3 -2
- package/routes/tag_entries.js +10 -4
- package/tests/view.test.js +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## 1.1.2 - In beta
|
|
4
4
|
|
|
5
|
-
*
|
|
5
|
+
* Builder:
|
|
6
|
+
- Container background image by file field in Show views
|
|
7
|
+
- Container opacity setting
|
|
8
|
+
- Set custom class on links and actions
|
|
9
|
+
- Fix error toast when saving on Firefox
|
|
10
|
+
- Set action to be submit action - action run on enter keypress.
|
|
11
|
+
|
|
12
|
+
* Restore large backups: stream JSON files to database, use system unzip
|
|
13
|
+
|
|
14
|
+
* Handle multiple fields with same name in Edit.
|
|
15
|
+
|
|
16
|
+
* Upgrade a large number of dependencies (express, typescript, oclif, pg, webpack, typescript, axios, mjml, svelte). Node.js 18+ is require for this release.
|
|
6
17
|
|
|
7
18
|
## 1.1.1 - Released 2 February 2025
|
|
8
19
|
|
package/app.js
CHANGED
|
@@ -32,7 +32,7 @@ const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
|
|
|
32
32
|
const path = require("path");
|
|
33
33
|
const helmet = require("helmet");
|
|
34
34
|
const wrapper = require("./wrapper");
|
|
35
|
-
const csrf = require("csurf");
|
|
35
|
+
const csrf = require("@dr.pogodin/csurf");
|
|
36
36
|
const { I18n } = require("i18n");
|
|
37
37
|
const { h1 } = require("@saltcorn/markup/tags");
|
|
38
38
|
const is = require("contractis/is");
|
|
@@ -131,6 +131,7 @@ const getApp = async (opts = {}) => {
|
|
|
131
131
|
"cross_domain_iframe",
|
|
132
132
|
false
|
|
133
133
|
);
|
|
134
|
+
app.set("query parser", "extended");
|
|
134
135
|
|
|
135
136
|
const helmetOptions = {
|
|
136
137
|
contentSecurityPolicy: {
|
|
@@ -160,7 +161,7 @@ const getApp = async (opts = {}) => {
|
|
|
160
161
|
helmetOptions.contentSecurityPolicy = false;
|
|
161
162
|
|
|
162
163
|
if (cross_domain_iframe) helmetOptions.xFrameOptions = false;
|
|
163
|
-
|
|
164
|
+
app.use(helmet(helmetOptions));
|
|
164
165
|
|
|
165
166
|
// TODO ch find a better solution
|
|
166
167
|
if (getState().getConfig("cors_enabled", true)) app.use(cors());
|
package/locales/en.json
CHANGED
|
@@ -1552,5 +1552,6 @@
|
|
|
1552
1552
|
"Show results from each table in this type of element": "Show results from each table in this type of element",
|
|
1553
1553
|
"Search syntax help": "Search syntax help",
|
|
1554
1554
|
"Search syntax": "Search syntax",
|
|
1555
|
-
"Maximum role": "Maximum role"
|
|
1555
|
+
"Maximum role": "Maximum role",
|
|
1556
|
+
"Module dependencies": "Module dependencies"
|
|
1556
1557
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.1.2-beta.
|
|
3
|
+
"version": "1.1.2-beta.10",
|
|
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
9
|
"@aws-sdk/client-s3": "^3.735.0",
|
|
10
|
-
"@
|
|
11
|
-
"@saltcorn/
|
|
12
|
-
"@saltcorn/
|
|
13
|
-
"@saltcorn/
|
|
14
|
-
"@saltcorn/
|
|
15
|
-
"@saltcorn/
|
|
16
|
-
"@saltcorn/
|
|
17
|
-
"@saltcorn/
|
|
10
|
+
"@dr.pogodin/csurf": "^1.14.1",
|
|
11
|
+
"@saltcorn/base-plugin": "1.1.2-beta.10",
|
|
12
|
+
"@saltcorn/builder": "1.1.2-beta.10",
|
|
13
|
+
"@saltcorn/data": "1.1.2-beta.10",
|
|
14
|
+
"@saltcorn/admin-models": "1.1.2-beta.10",
|
|
15
|
+
"@saltcorn/filemanager": "1.1.2-beta.10",
|
|
16
|
+
"@saltcorn/markup": "1.1.2-beta.10",
|
|
17
|
+
"@saltcorn/plugins-loader": "1.1.2-beta.10",
|
|
18
|
+
"@saltcorn/sbadmin2": "1.1.2-beta.10",
|
|
18
19
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
20
|
"@socket.io/sticky": "^1.0.1",
|
|
20
21
|
"adm-zip": "0.5.16",
|
|
@@ -25,7 +26,6 @@
|
|
|
25
26
|
"cookie-parser": "^1.4.7",
|
|
26
27
|
"cookie-session": "^2.1.0",
|
|
27
28
|
"cors": "2.8.5",
|
|
28
|
-
"csurf": "^1.11.0",
|
|
29
29
|
"csv-stringify": "^6.5.2",
|
|
30
30
|
"dockerode": "~4.0.4",
|
|
31
31
|
"express": "^5.0.1",
|
|
@@ -77,11 +77,12 @@
|
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"dev": "nodemon index.js",
|
|
80
|
-
"test": "jest --runInBand",
|
|
80
|
+
"test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js ./tests --runInBand",
|
|
81
81
|
"tsc": "echo \"Error: no TypeScript support yet\"",
|
|
82
82
|
"clean": "echo \"Error: no TypeScript support yet\""
|
|
83
83
|
},
|
|
84
84
|
"jest": {
|
|
85
|
+
"transform": {},
|
|
85
86
|
"testEnvironment": "node",
|
|
86
87
|
"testPathIgnorePatterns": [
|
|
87
88
|
"/node_modules/",
|
|
@@ -103,15 +103,26 @@ function rep_del(e) {
|
|
|
103
103
|
var myrep = $(e).closest(".form-repeat");
|
|
104
104
|
var ix = myrep.index();
|
|
105
105
|
var parent = myrep.parent();
|
|
106
|
-
myrep.remove();
|
|
107
106
|
parent.children().each(function (childix, element) {
|
|
108
107
|
if (childix > ix) {
|
|
109
108
|
reindex(element, childix, childix - 1);
|
|
110
109
|
}
|
|
111
110
|
});
|
|
111
|
+
myrep.remove();
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function reindex(element, oldix, newix) {
|
|
115
|
+
$(element)
|
|
116
|
+
.find("input,textarea")
|
|
117
|
+
.each(function () {
|
|
118
|
+
$(this).attr("value", $(this).val());
|
|
119
|
+
});
|
|
120
|
+
$(element)
|
|
121
|
+
.find("select")
|
|
122
|
+
.each(function () {
|
|
123
|
+
$(this).find(":selected").attr("selected", "selected");
|
|
124
|
+
});
|
|
125
|
+
|
|
115
126
|
$(element).html(
|
|
116
127
|
$(element)
|
|
117
128
|
.html()
|
|
@@ -909,6 +920,27 @@ function initialize_page() {
|
|
|
909
920
|
if (e.keyCode === 13) e.target.blur();
|
|
910
921
|
});
|
|
911
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
|
+
|
|
912
944
|
const validate_expression_elem = (target) => {
|
|
913
945
|
const next = target.next();
|
|
914
946
|
if (next.hasClass("expr-error")) next.remove();
|
|
@@ -921,7 +953,10 @@ function initialize_page() {
|
|
|
921
953
|
}
|
|
922
954
|
if (!val) return;
|
|
923
955
|
try {
|
|
924
|
-
|
|
956
|
+
const AsyncFunction = Object.getPrototypeOf(
|
|
957
|
+
async function () {}
|
|
958
|
+
).constructor;
|
|
959
|
+
AsyncFunction("return " + val);
|
|
925
960
|
} catch (error) {
|
|
926
961
|
target.after(`<small class="text-danger font-monospace d-block expr-error">
|
|
927
962
|
${error.message}
|
|
@@ -932,6 +967,7 @@ function initialize_page() {
|
|
|
932
967
|
const target = $(e.target);
|
|
933
968
|
validate_expression_elem(target);
|
|
934
969
|
});
|
|
970
|
+
|
|
935
971
|
$(".validate-expression-conditional").each(function () {
|
|
936
972
|
const theInput = $(this);
|
|
937
973
|
theInput
|
|
@@ -1543,7 +1579,7 @@ function emptyAlerts() {
|
|
|
1543
1579
|
$("#toasts-area").html("");
|
|
1544
1580
|
}
|
|
1545
1581
|
|
|
1546
|
-
function press_store_button(clicked, keepOld) {
|
|
1582
|
+
function press_store_button(clicked, keepOld, disable) {
|
|
1547
1583
|
let btn = clicked;
|
|
1548
1584
|
if ($(clicked).is("form")) btn = $(clicked).find("button[type=submit]");
|
|
1549
1585
|
if (keepOld) {
|
|
@@ -1552,17 +1588,24 @@ function press_store_button(clicked, keepOld) {
|
|
|
1552
1588
|
}
|
|
1553
1589
|
const width = $(btn).width();
|
|
1554
1590
|
$(btn).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
|
1591
|
+
setTimeout(() => {
|
|
1592
|
+
$(btn).prop("disabled", true);
|
|
1593
|
+
}, 50);
|
|
1555
1594
|
}
|
|
1556
1595
|
|
|
1557
1596
|
function restore_old_button(btnId) {
|
|
1558
|
-
const btn = $(`#${btnId}`);
|
|
1597
|
+
const btn = btnId instanceof jQuery ? btnId : $(`#${btnId}`);
|
|
1559
1598
|
const oldText = $(btn).data("old-text");
|
|
1560
1599
|
btn.html(oldText);
|
|
1561
|
-
btn.css({ width: "" });
|
|
1600
|
+
btn.css({ width: "" }).prop("disabled", false);
|
|
1562
1601
|
btn.removeData("old-text");
|
|
1563
1602
|
}
|
|
1564
1603
|
|
|
1565
|
-
async function common_done(res,
|
|
1604
|
+
async function common_done(res, viewnameOrElem0, isWeb = true) {
|
|
1605
|
+
const viewnameOrElem =
|
|
1606
|
+
viewnameOrElem0 === "undefined"
|
|
1607
|
+
? last_route_viewname
|
|
1608
|
+
: viewnameOrElem0 || last_route_viewname;
|
|
1566
1609
|
const viewname =
|
|
1567
1610
|
typeof viewnameOrElem === "string"
|
|
1568
1611
|
? viewnameOrElem
|
package/public/saltcorn.css
CHANGED
|
@@ -812,3 +812,11 @@ tr span.add-tag {
|
|
|
812
812
|
tr:hover span.add-tag {
|
|
813
813
|
opacity: 1;
|
|
814
814
|
}
|
|
815
|
+
|
|
816
|
+
#saltcorn-file-manager .filelist tr {
|
|
817
|
+
cursor: pointer;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
#saltcorn-file-manager .filelist tr.selected td {
|
|
821
|
+
background-color: var(--bs-secondary-bg-subtle, var(--tblr-secondary-bg-subtle, gray));;
|
|
822
|
+
}
|
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/admin.js
CHANGED
|
@@ -657,9 +657,11 @@ router.get(
|
|
|
657
657
|
{
|
|
658
658
|
label: req.__("When"),
|
|
659
659
|
key: (r) =>
|
|
660
|
-
`${moment(
|
|
661
|
-
r.created
|
|
662
|
-
|
|
660
|
+
`${moment(r.created).fromNow()}<br><small>${localeDateTime(
|
|
661
|
+
r.created,
|
|
662
|
+
{},
|
|
663
|
+
locale
|
|
664
|
+
)}</small>`,
|
|
663
665
|
},
|
|
664
666
|
{
|
|
665
667
|
label: req.__("Name"),
|
|
@@ -1193,10 +1195,10 @@ router.get(
|
|
|
1193
1195
|
th({ valign: "top" }, req.__("Saltcorn version")),
|
|
1194
1196
|
td(
|
|
1195
1197
|
packagejson.version,
|
|
1196
|
-
isRoot
|
|
1198
|
+
isRoot
|
|
1197
1199
|
? post_btn(
|
|
1198
1200
|
"/admin/upgrade",
|
|
1199
|
-
req.__("Upgrade"),
|
|
1201
|
+
req.__("Upgrade") + " (latest)",
|
|
1200
1202
|
req.csrfToken(),
|
|
1201
1203
|
{
|
|
1202
1204
|
btnClass: "btn-primary btn-sm",
|
package/routes/common_lists.js
CHANGED
|
@@ -290,7 +290,7 @@ const tagsDropdown = (tags, altHeader) =>
|
|
|
290
290
|
)
|
|
291
291
|
);
|
|
292
292
|
|
|
293
|
-
const mkAddBtn = (tags, entityType, id, req, myTagIds) =>
|
|
293
|
+
const mkAddBtn = (tags, entityType, id, req, myTagIds, on_done_redirect_str) =>
|
|
294
294
|
div(
|
|
295
295
|
{ class: "dropdown d-inline ms-1" },
|
|
296
296
|
span(
|
|
@@ -314,7 +314,7 @@ const mkAddBtn = (tags, entityType, id, req, myTagIds) =>
|
|
|
314
314
|
post_dropdown_item(
|
|
315
315
|
`/tag-entries/add-tag-entity/${encodeURIComponent(
|
|
316
316
|
t.name
|
|
317
|
-
)}/${entityType}/${id}`,
|
|
317
|
+
)}/${entityType}/${id}${on_done_redirect_str||""}`,
|
|
318
318
|
t.name,
|
|
319
319
|
req
|
|
320
320
|
)
|
|
@@ -345,7 +345,7 @@ const viewsList = async (
|
|
|
345
345
|
const tagBadges = (view) => {
|
|
346
346
|
const myTags = tag_entries.filter((te) => te.view_id === view.id);
|
|
347
347
|
const myTagIds = new Set(myTags.map((t) => t.tag_id));
|
|
348
|
-
const addBtn = mkAddBtn(tags, "views", view.id, req, myTagIds);
|
|
348
|
+
const addBtn = mkAddBtn(tags, "views", view.id, req, myTagIds, on_done_redirect_str);
|
|
349
349
|
return (
|
|
350
350
|
myTags.map((te) => tagBadge(tagsById[te.tag_id], "views")).join(nbsp) +
|
|
351
351
|
addBtn
|
|
@@ -709,7 +709,7 @@ const getTriggerList = async (
|
|
|
709
709
|
const myTagIds = new Set(myTags.map((t) => t.tag_id));
|
|
710
710
|
return (
|
|
711
711
|
myTags.map((te) => tagBadge(tagsById[te.tag_id], "triggers")).join(nbsp) +
|
|
712
|
-
mkAddBtn(tags, "triggers", trigger.id, req, myTagIds)
|
|
712
|
+
mkAddBtn(tags, "triggers", trigger.id, req, myTagIds, on_done_redirect_str)
|
|
713
713
|
);
|
|
714
714
|
};
|
|
715
715
|
return mkTable(
|
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/routes/homepage.js
CHANGED
|
@@ -262,7 +262,10 @@ const actionsTab = async (req, triggers) => {
|
|
|
262
262
|
? p(req.__("No triggers"))
|
|
263
263
|
: mkTable(
|
|
264
264
|
[
|
|
265
|
-
{
|
|
265
|
+
{
|
|
266
|
+
label: req.__("Name"),
|
|
267
|
+
key: (tr) => a({ href: `actions/configure/${tr.id}` }, tr.name),
|
|
268
|
+
},
|
|
266
269
|
{ label: req.__("Action"), key: "action" },
|
|
267
270
|
{
|
|
268
271
|
label: req.__("Table or Channel"),
|
package/routes/plugins.js
CHANGED
package/routes/tables.js
CHANGED
|
@@ -1582,7 +1582,7 @@ const constraintForm = (req, table, fields, type) => {
|
|
|
1582
1582
|
case "Formula":
|
|
1583
1583
|
return new Form({
|
|
1584
1584
|
action: `/table/add-constraint/${table.id}/${type}`,
|
|
1585
|
-
|
|
1585
|
+
onSubmit: "press_store_button(this)",
|
|
1586
1586
|
fields: [
|
|
1587
1587
|
{
|
|
1588
1588
|
name: "formula",
|
|
@@ -1617,6 +1617,7 @@ const constraintForm = (req, table, fields, type) => {
|
|
|
1617
1617
|
blurb: req.__(
|
|
1618
1618
|
"Tick the boxes for the fields that should be jointly unique"
|
|
1619
1619
|
),
|
|
1620
|
+
onSubmit: "press_store_button(this)",
|
|
1620
1621
|
fields: [
|
|
1621
1622
|
...fields.map((f) => ({
|
|
1622
1623
|
name: f.name,
|
|
@@ -1641,7 +1642,7 @@ const constraintForm = (req, table, fields, type) => {
|
|
|
1641
1642
|
blurb: req.__(
|
|
1642
1643
|
"Choose the field to be indexed. This make searching the table faster."
|
|
1643
1644
|
),
|
|
1644
|
-
|
|
1645
|
+
onSubmit: "press_store_button(this)",
|
|
1645
1646
|
fields: [
|
|
1646
1647
|
{
|
|
1647
1648
|
type: "String",
|
package/routes/tag_entries.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
csrfField,
|
|
19
19
|
isAdminOrHasConfigMinRole,
|
|
20
20
|
checkEditPermission,
|
|
21
|
+
is_relative_url,
|
|
21
22
|
} = require("./utils");
|
|
22
23
|
|
|
23
24
|
const Table = require("@saltcorn/data/models/table");
|
|
@@ -192,18 +193,23 @@ router.post(
|
|
|
192
193
|
const auth = checkEditPermission(entitytype, req.user);
|
|
193
194
|
if (!auth) req.flash("error", "Not authorized");
|
|
194
195
|
else await tag.addEntry({ [fieldName]: +entityid });
|
|
196
|
+
let redirectTarget =
|
|
197
|
+
req.query.on_done_redirect &&
|
|
198
|
+
is_relative_url("/" + req.query.on_done_redirect)
|
|
199
|
+
? `/${req.query.on_done_redirect}`
|
|
200
|
+
: null;
|
|
195
201
|
switch (entitytype) {
|
|
196
202
|
case "views":
|
|
197
|
-
res.redirect(`/viewedit`);
|
|
203
|
+
res.redirect(redirectTarget || `/viewedit`);
|
|
198
204
|
break;
|
|
199
205
|
case "pages":
|
|
200
|
-
res.redirect(`/pageedit`);
|
|
206
|
+
res.redirect(redirectTarget || `/pageedit`);
|
|
201
207
|
break;
|
|
202
208
|
case "tables":
|
|
203
|
-
res.redirect(`/table`);
|
|
209
|
+
res.redirect(redirectTarget || `/table`);
|
|
204
210
|
break;
|
|
205
211
|
case "triggers":
|
|
206
|
-
res.redirect(`/actions`);
|
|
212
|
+
res.redirect(redirectTarget || `/actions`);
|
|
207
213
|
break;
|
|
208
214
|
|
|
209
215
|
default:
|
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
|
+
});
|