@saltcorn/server 0.8.0 → 0.8.1-beta.1
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/auth/admin.js +34 -0
- package/locales/en.json +17 -1
- package/locales/fr.json +14 -2
- package/locales/it.json +2 -1
- package/package.json +8 -8
- package/public/saltcorn-common.js +138 -69
- package/public/saltcorn.js +71 -22
- package/routes/actions.js +8 -2
- package/routes/admin.js +15 -1
- package/routes/api.js +2 -9
- package/routes/fields.js +44 -0
- package/routes/tenant.js +2 -1
- package/routes/view.js +14 -2
- package/routes/viewedit.js +35 -0
- package/s3storage.js +7 -4
- package/tests/fields.test.js +23 -0
- package/wrapper.js +2 -0
package/auth/admin.js
CHANGED
|
@@ -162,6 +162,11 @@ const user_dropdown = (user, req, can_reset) =>
|
|
|
162
162
|
},
|
|
163
163
|
'<i class="fas fa-edit"></i> ' + req.__("Edit")
|
|
164
164
|
),
|
|
165
|
+
post_dropdown_item(
|
|
166
|
+
`/useradmin/become-user/${user.id}`,
|
|
167
|
+
'<i class="fas fa-ghost"></i> ' + req.__("Become user"),
|
|
168
|
+
req
|
|
169
|
+
),
|
|
165
170
|
post_dropdown_item(
|
|
166
171
|
`/useradmin/set-random-password/${user.id}`,
|
|
167
172
|
'<i class="fas fa-random"></i> ' + req.__("Set random password"),
|
|
@@ -1074,6 +1079,35 @@ router.post(
|
|
|
1074
1079
|
})
|
|
1075
1080
|
);
|
|
1076
1081
|
|
|
1082
|
+
/**
|
|
1083
|
+
* Become user
|
|
1084
|
+
* @name post/become-user/:id
|
|
1085
|
+
* @function
|
|
1086
|
+
* @memberof module:auth/admin~auth/adminRouter
|
|
1087
|
+
*/
|
|
1088
|
+
router.post(
|
|
1089
|
+
"/become-user/:id",
|
|
1090
|
+
isAdmin,
|
|
1091
|
+
error_catcher(async (req, res) => {
|
|
1092
|
+
const { id } = req.params;
|
|
1093
|
+
const u = await User.findOne({ id });
|
|
1094
|
+
if (u) {
|
|
1095
|
+
u.relogin(req);
|
|
1096
|
+
req.flash(
|
|
1097
|
+
"success",
|
|
1098
|
+
req.__(
|
|
1099
|
+
`Your are now logged in as %s. Logout and login again to assume your usual identity`,
|
|
1100
|
+
u.email
|
|
1101
|
+
)
|
|
1102
|
+
);
|
|
1103
|
+
res.redirect(`/`);
|
|
1104
|
+
} else {
|
|
1105
|
+
req.flash("error", req.__(`User not found`));
|
|
1106
|
+
res.redirect(`/useradmin`);
|
|
1107
|
+
}
|
|
1108
|
+
})
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1077
1111
|
/**
|
|
1078
1112
|
* @name post/disable/:id
|
|
1079
1113
|
* @function
|
package/locales/en.json
CHANGED
|
@@ -1057,5 +1057,21 @@
|
|
|
1057
1057
|
"Specifies a default filter for what file types the user can pick from the file input dialog box. Example is `.doc, text/csv,audio/*,video/*,image/*`": "Specifies a default filter for what file types the user can pick from the file input dialog box. Example is `.doc, text/csv,audio/*,video/*,image/*`",
|
|
1058
1058
|
"Destination page": "Destination page",
|
|
1059
1059
|
"Module Store endpoint": "Module Store endpoint",
|
|
1060
|
-
"Authentication settings updated": "Authentication settings updated"
|
|
1060
|
+
"Authentication settings updated": "Authentication settings updated",
|
|
1061
|
+
"Log client errors": "Log client errors",
|
|
1062
|
+
"Record all client errors in the crash log": "Record all client errors in the crash log",
|
|
1063
|
+
"Default File accept filter": "Default File accept filter",
|
|
1064
|
+
"File upload debug": "File upload debug",
|
|
1065
|
+
"Turn on to debug file upload in express-fileupload.": "Turn on to debug file upload in express-fileupload.",
|
|
1066
|
+
"Upload size limit (Kb)": "Upload size limit (Kb)",
|
|
1067
|
+
"Maximum upload file size in kilobytes": "Maximum upload file size in kilobytes",
|
|
1068
|
+
"File upload timeout": "File upload timeout",
|
|
1069
|
+
"Defines how long to wait for data before aborting file upload. Set to 0 if you want to turn off timeout checks. ": "Defines how long to wait for data before aborting file upload. Set to 0 if you want to turn off timeout checks. ",
|
|
1070
|
+
"Files settings": "Files settings",
|
|
1071
|
+
"NPM packages in code": "NPM packages in code",
|
|
1072
|
+
"Comma-separated list of packages which will be available in JavaScript actions": "Comma-separated list of packages which will be available in JavaScript actions",
|
|
1073
|
+
"Become user": "Become user",
|
|
1074
|
+
"Your are now logged in as %s. Logout and login again to assume your usual identity": "Your are now logged in as %s. Logout and login again to assume your usual identity",
|
|
1075
|
+
"Done": "Done",
|
|
1076
|
+
"Configure trigger %s": "Configure trigger %s"
|
|
1061
1077
|
}
|
package/locales/fr.json
CHANGED
|
@@ -274,5 +274,17 @@
|
|
|
274
274
|
"Field %s deleted": "Champ %s supprimé",
|
|
275
275
|
"Language: ": "Langage: ",
|
|
276
276
|
"Local": "Local",
|
|
277
|
-
"Language changed to %s": "Langage changé vers %s"
|
|
278
|
-
|
|
277
|
+
"Language changed to %s": "Langage changé vers %s",
|
|
278
|
+
"CSV upload": "CSV upload",
|
|
279
|
+
"Create page": "Create page",
|
|
280
|
+
"Action": "Action",
|
|
281
|
+
"Table or Channel": "Table or Channel",
|
|
282
|
+
"Add trigger": "Add trigger",
|
|
283
|
+
"Upload file(s)": "Upload file(s)",
|
|
284
|
+
"About application": "About application",
|
|
285
|
+
"Modules": "Modules",
|
|
286
|
+
"Users and security": "Users and security",
|
|
287
|
+
"Site structure": "Site structure",
|
|
288
|
+
"Events": "Events",
|
|
289
|
+
"Are you sure?": "Are you sure?"
|
|
290
|
+
}
|
package/locales/it.json
CHANGED
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1-beta.1",
|
|
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.
|
|
10
|
-
"@saltcorn/builder": "0.8.
|
|
11
|
-
"@saltcorn/data": "0.8.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.
|
|
14
|
-
"@saltcorn/markup": "0.8.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.1-beta.1",
|
|
10
|
+
"@saltcorn/builder": "0.8.1-beta.1",
|
|
11
|
+
"@saltcorn/data": "0.8.1-beta.1",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.1-beta.1",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.1-beta.1",
|
|
14
|
+
"@saltcorn/markup": "0.8.1-beta.1",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.1-beta.1",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"aws-sdk": "^2.1037.0",
|
|
@@ -36,11 +36,11 @@ function add_repeater(nm) {
|
|
|
36
36
|
newe.appendTo($("div.repeats-" + nm));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const _apply_showif_plugins = []
|
|
39
|
+
const _apply_showif_plugins = [];
|
|
40
40
|
|
|
41
|
-
const add_apply_showif_plugin = p => {
|
|
42
|
-
_apply_showif_plugins.push(p)
|
|
43
|
-
}
|
|
41
|
+
const add_apply_showif_plugin = (p) => {
|
|
42
|
+
_apply_showif_plugins.push(p);
|
|
43
|
+
};
|
|
44
44
|
function apply_showif() {
|
|
45
45
|
$("[data-show-if]").each(function (ix, element) {
|
|
46
46
|
var e = $(element);
|
|
@@ -92,7 +92,8 @@ function apply_showif() {
|
|
|
92
92
|
} else {
|
|
93
93
|
e.append(
|
|
94
94
|
$(
|
|
95
|
-
`<option ${
|
|
95
|
+
`<option ${
|
|
96
|
+
`${current}` === `${o.value}` ? "selected" : ""
|
|
96
97
|
} value="${o.value}">${o.label}</option>`
|
|
97
98
|
)
|
|
98
99
|
);
|
|
@@ -117,76 +118,127 @@ function apply_showif() {
|
|
|
117
118
|
e.attr("data-selected", ec.target.value);
|
|
118
119
|
});
|
|
119
120
|
|
|
120
|
-
const currentOptionsSet = e.prop(
|
|
121
|
+
const currentOptionsSet = e.prop("data-fetch-options-current-set");
|
|
121
122
|
if (currentOptionsSet === qs) return;
|
|
122
123
|
|
|
123
124
|
const activate = (success, qs) => {
|
|
124
125
|
e.empty();
|
|
125
|
-
e.prop(
|
|
126
|
+
e.prop("data-fetch-options-current-set", qs);
|
|
126
127
|
if (!dynwhere.required) e.append($(`<option></option>`));
|
|
127
128
|
let currentDataOption = undefined;
|
|
128
|
-
const dataOptions = []
|
|
129
|
+
const dataOptions = [];
|
|
129
130
|
success.forEach((r) => {
|
|
130
131
|
const label = dynwhere.label_formula
|
|
131
132
|
? new Function(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
: r[dynwhere.summary_field]
|
|
136
|
-
const value = r[dynwhere.refname]
|
|
137
|
-
const selected = `${current}` === `${r[dynwhere.refname]}
|
|
133
|
+
`{${Object.keys(r).join(",")}}`,
|
|
134
|
+
"return " + dynwhere.label_formula
|
|
135
|
+
)(r)
|
|
136
|
+
: r[dynwhere.summary_field];
|
|
137
|
+
const value = r[dynwhere.refname];
|
|
138
|
+
const selected = `${current}` === `${r[dynwhere.refname]}`;
|
|
138
139
|
dataOptions.push({ text: label, value });
|
|
139
140
|
if (selected) currentDataOption = value;
|
|
140
|
-
const html = `<option ${
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
);
|
|
141
|
+
const html = `<option ${
|
|
142
|
+
selected ? "selected" : ""
|
|
143
|
+
} value="${value}">${label}</option>`;
|
|
144
|
+
e.append($(html));
|
|
145
145
|
});
|
|
146
|
-
element.dispatchEvent(new Event(
|
|
146
|
+
element.dispatchEvent(new Event("RefreshSelectOptions"));
|
|
147
147
|
if (e.hasClass("selectized") && $().selectize) {
|
|
148
148
|
e.selectize()[0].selectize.clearOptions();
|
|
149
149
|
e.selectize()[0].selectize.addOption(dataOptions);
|
|
150
150
|
if (typeof currentDataOption !== "undefined")
|
|
151
151
|
e.selectize()[0].selectize.setValue(currentDataOption);
|
|
152
|
-
|
|
153
152
|
}
|
|
154
|
-
}
|
|
153
|
+
};
|
|
155
154
|
|
|
156
|
-
const cache = e.prop(
|
|
155
|
+
const cache = e.prop("data-fetch-options-cache") || {};
|
|
157
156
|
if (cache[qs]) {
|
|
158
|
-
activate(cache[qs], qs)
|
|
157
|
+
activate(cache[qs], qs);
|
|
159
158
|
} else
|
|
160
159
|
$.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
|
|
161
160
|
if (resp.success) {
|
|
162
|
-
activate(resp.success, qs)
|
|
163
|
-
const cacheNow = e.prop(
|
|
164
|
-
e.prop(
|
|
161
|
+
activate(resp.success, qs);
|
|
162
|
+
const cacheNow = e.prop("data-fetch-options-cache") || {};
|
|
163
|
+
e.prop("data-fetch-options-cache", {
|
|
164
|
+
...cacheNow,
|
|
165
|
+
[qs]: resp.success,
|
|
166
|
+
});
|
|
165
167
|
}
|
|
166
168
|
});
|
|
167
169
|
});
|
|
168
170
|
|
|
169
171
|
$("[data-source-url]").each(function (ix, element) {
|
|
170
172
|
const e = $(element);
|
|
171
|
-
const
|
|
173
|
+
const rec0 = get_form_record(e);
|
|
174
|
+
|
|
175
|
+
const relevantFieldsStr = e.attr("data-relevant-fields");
|
|
176
|
+
let rec;
|
|
177
|
+
if (relevantFieldsStr) {
|
|
178
|
+
rec = {};
|
|
179
|
+
relevantFieldsStr.split(",").forEach((k) => {
|
|
180
|
+
rec[k] = rec0[k];
|
|
181
|
+
});
|
|
182
|
+
} else rec = rec0;
|
|
183
|
+
const recS = JSON.stringify(rec);
|
|
184
|
+
|
|
185
|
+
const shown = e.prop("data-source-url-current");
|
|
186
|
+
if (shown === recS) return;
|
|
187
|
+
|
|
188
|
+
const cache = e.prop("data-source-url-cache") || {};
|
|
189
|
+
|
|
190
|
+
const activate_onchange_coldef = () => {
|
|
191
|
+
e.closest(".form-namespace")
|
|
192
|
+
.find("input,select, textarea")
|
|
193
|
+
.on("change", (ec) => {
|
|
194
|
+
const $ec = $(ec.target);
|
|
195
|
+
const k = $ec.attr("name");
|
|
196
|
+
if (!k || k === "_columndef") return;
|
|
197
|
+
const v = ec.target.value;
|
|
198
|
+
const $def = e
|
|
199
|
+
.closest(".form-namespace")
|
|
200
|
+
.find("input[name=_columndef]");
|
|
201
|
+
const def = JSON.parse($def.val());
|
|
202
|
+
def[k] = v;
|
|
203
|
+
$def.val(JSON.stringify(def));
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (typeof cache[recS] !== "undefined") {
|
|
208
|
+
e.html(cache[recS]);
|
|
209
|
+
activate_onchange_coldef();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
172
212
|
ajax_post_json(e.attr("data-source-url"), rec, {
|
|
173
213
|
success: (data) => {
|
|
174
214
|
e.html(data);
|
|
215
|
+
const cacheNow = e.prop("data-source-url-cache") || {};
|
|
216
|
+
e.prop("data-source-url-cache", {
|
|
217
|
+
...cacheNow,
|
|
218
|
+
[recS]: data,
|
|
219
|
+
});
|
|
220
|
+
e.prop("data-source-url-current", recS);
|
|
221
|
+
activate_onchange_coldef();
|
|
175
222
|
},
|
|
176
223
|
error: (err) => {
|
|
177
224
|
console.error(err);
|
|
225
|
+
const cacheNow = e.prop("data-source-url-cache") || {};
|
|
226
|
+
e.prop("data-source-url-cache", {
|
|
227
|
+
...cacheNow,
|
|
228
|
+
[recS]: "",
|
|
229
|
+
});
|
|
178
230
|
e.html("");
|
|
179
231
|
},
|
|
180
232
|
});
|
|
181
233
|
});
|
|
182
|
-
_apply_showif_plugins.forEach(p => p())
|
|
234
|
+
_apply_showif_plugins.forEach((p) => p());
|
|
183
235
|
}
|
|
184
236
|
|
|
185
237
|
function splitTargetMatch(elemValue, target, keySpec) {
|
|
186
238
|
if (!elemValue) return false;
|
|
187
|
-
const [fld, keySpec1] = keySpec.split("|_")
|
|
188
|
-
const [sep, pos] = keySpec1.split("_")
|
|
189
|
-
const elemValueShort = elemValue.split(sep)[pos]
|
|
239
|
+
const [fld, keySpec1] = keySpec.split("|_");
|
|
240
|
+
const [sep, pos] = keySpec1.split("_");
|
|
241
|
+
const elemValueShort = elemValue.split(sep)[pos];
|
|
190
242
|
return elemValueShort === target;
|
|
191
243
|
}
|
|
192
244
|
|
|
@@ -195,7 +247,7 @@ function get_form_record(e, select_labels) {
|
|
|
195
247
|
e.closest(".form-namespace")
|
|
196
248
|
.find("input[name],select[name]")
|
|
197
249
|
.each(function () {
|
|
198
|
-
const name = $(this).attr("data-fieldname") || $(this).attr("name")
|
|
250
|
+
const name = $(this).attr("data-fieldname") || $(this).attr("name");
|
|
199
251
|
if (select_labels && $(this).prop("tagName").toLowerCase() === "select")
|
|
200
252
|
rec[name] = $(this).find("option:selected").text();
|
|
201
253
|
else if ($(this).prop("type") === "checkbox")
|
|
@@ -392,10 +444,19 @@ function initialize_page() {
|
|
|
392
444
|
setTimeout(() => {
|
|
393
445
|
codes.forEach((el) => {
|
|
394
446
|
//console.log($(el).attr("mode"), el);
|
|
395
|
-
CodeMirror.fromTextArea(el, {
|
|
447
|
+
const cm = CodeMirror.fromTextArea(el, {
|
|
396
448
|
lineNumbers: true,
|
|
397
449
|
mode: $(el).attr("mode"),
|
|
398
450
|
});
|
|
451
|
+
cm.on(
|
|
452
|
+
"change",
|
|
453
|
+
$.debounce(() => {
|
|
454
|
+
$(el).closest("form").trigger("change");
|
|
455
|
+
}),
|
|
456
|
+
500,
|
|
457
|
+
null,
|
|
458
|
+
true
|
|
459
|
+
);
|
|
399
460
|
});
|
|
400
461
|
}, 100);
|
|
401
462
|
});
|
|
@@ -508,14 +569,16 @@ function notifyAlert(note, spin) {
|
|
|
508
569
|
}
|
|
509
570
|
|
|
510
571
|
$("#alerts-area")
|
|
511
|
-
.append(`<div class="alert alert-${type} alert-dismissible fade show ${
|
|
512
|
-
|
|
572
|
+
.append(`<div class="alert alert-${type} alert-dismissible fade show ${
|
|
573
|
+
spin ? "d-flex align-items-center" : ""
|
|
574
|
+
}" role="alert">
|
|
513
575
|
${txt}
|
|
514
|
-
${
|
|
515
|
-
|
|
516
|
-
|
|
576
|
+
${
|
|
577
|
+
spin
|
|
578
|
+
? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
|
|
579
|
+
: `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
517
580
|
</button>`
|
|
518
|
-
|
|
581
|
+
}
|
|
519
582
|
</div>`);
|
|
520
583
|
}
|
|
521
584
|
|
|
@@ -532,8 +595,9 @@ function common_done(res, isWeb = true) {
|
|
|
532
595
|
(isWeb ? location : parent.location).reload(); //TODO notify to cookie if reload or goto
|
|
533
596
|
}
|
|
534
597
|
if (res.download) {
|
|
535
|
-
const dataurl = `data:${
|
|
536
|
-
|
|
598
|
+
const dataurl = `data:${
|
|
599
|
+
res.download.mimetype || "application/octet-stream"
|
|
600
|
+
};base64,${res.download.blob}`;
|
|
537
601
|
fetch(dataurl)
|
|
538
602
|
.then((res) => res.blob())
|
|
539
603
|
.then((blob) => {
|
|
@@ -553,15 +617,19 @@ function common_done(res, isWeb = true) {
|
|
|
553
617
|
else if (res.goto) {
|
|
554
618
|
if (res.target === "_blank") window.open(res.goto, "_blank").focus();
|
|
555
619
|
else {
|
|
556
|
-
const prev = new URL(window.location.href)
|
|
557
|
-
const next = new URL(res.goto, prev.origin)
|
|
620
|
+
const prev = new URL(window.location.href);
|
|
621
|
+
const next = new URL(res.goto, prev.origin);
|
|
558
622
|
window.location.href = res.goto;
|
|
559
|
-
if (
|
|
560
|
-
|
|
623
|
+
if (
|
|
624
|
+
prev.origin === next.origin &&
|
|
625
|
+
prev.pathname === next.pathname &&
|
|
626
|
+
next.hash !== prev.hash
|
|
627
|
+
)
|
|
628
|
+
location.reload();
|
|
561
629
|
}
|
|
562
630
|
}
|
|
563
631
|
if (res.popup) {
|
|
564
|
-
ajax_modal(res.popup)
|
|
632
|
+
ajax_modal(res.popup);
|
|
565
633
|
}
|
|
566
634
|
}
|
|
567
635
|
|
|
@@ -572,7 +640,9 @@ const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
|
|
|
572
640
|
const $e = form.find(`input[name="${k}_${ix}"]`);
|
|
573
641
|
if ($e.length) $e.val(v);
|
|
574
642
|
else {
|
|
575
|
-
const $ne = $(
|
|
643
|
+
const $ne = $(
|
|
644
|
+
`<input type="hidden" data-repeater-ix="${ix}" name="${k}_${ix}"></input>`
|
|
645
|
+
);
|
|
576
646
|
$ne.val(v);
|
|
577
647
|
form.append($ne);
|
|
578
648
|
}
|
|
@@ -700,9 +770,9 @@ function room_older(viewname, room_id, btn) {
|
|
|
700
770
|
function init_room(viewname, room_id) {
|
|
701
771
|
const socket = parent?.config?.server_path
|
|
702
772
|
? io(parent.config.server_path, {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
773
|
+
query: `jwt=${localStorage.getItem("auth_jwt")}`,
|
|
774
|
+
transports: ["websocket"],
|
|
775
|
+
})
|
|
706
776
|
: io({ transports: ["websocket"] });
|
|
707
777
|
|
|
708
778
|
socket.emit("join_room", [viewname, room_id]);
|
|
@@ -735,32 +805,31 @@ function cancel_form(form) {
|
|
|
735
805
|
}
|
|
736
806
|
|
|
737
807
|
function split_paste_handler(e) {
|
|
738
|
-
let clipboardData =
|
|
808
|
+
let clipboardData =
|
|
809
|
+
e.clipboardData || window.clipboardData || e.originalEvent.clipboardData;
|
|
739
810
|
|
|
740
|
-
const lines = clipboardData.getData(
|
|
811
|
+
const lines = clipboardData.getData("text").split(/\r\n/g);
|
|
741
812
|
|
|
742
813
|
// do normal thing if not multiline - do not interfere with ordinary copy paste
|
|
743
814
|
if (lines.length < 2) return;
|
|
744
815
|
e.preventDefault();
|
|
745
|
-
const form = $(e.target).closest(
|
|
816
|
+
const form = $(e.target).closest("form");
|
|
746
817
|
|
|
747
818
|
let matched = false;
|
|
748
819
|
|
|
749
|
-
form
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
$elem.val(lines.shift())
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
820
|
+
form
|
|
821
|
+
.find("input:not(:disabled):not([readonly]):not(:hidden)")
|
|
822
|
+
.each(function (ix, element) {
|
|
823
|
+
if (!matched && element === e.target) matched = true;
|
|
824
|
+
if (matched && lines.length > 0) {
|
|
825
|
+
const $elem = $(element);
|
|
826
|
+
if (ix === 0 && $elem.attr("type") !== "number") {
|
|
827
|
+
//const existing = $elem.val()
|
|
828
|
+
//const pasted =
|
|
829
|
+
$elem.val(lines.shift());
|
|
830
|
+
} else $elem.val(lines.shift());
|
|
831
|
+
}
|
|
832
|
+
});
|
|
764
833
|
}
|
|
765
834
|
|
|
766
835
|
function is_paging_param(key) {
|
package/public/saltcorn.js
CHANGED
|
@@ -97,7 +97,7 @@ function invalidate_pagings(href) {
|
|
|
97
97
|
return newhref;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function set_state_fields(kvs) {
|
|
100
|
+
function set_state_fields(kvs, disable_pjax) {
|
|
101
101
|
let newhref = get_current_state_url();
|
|
102
102
|
if (Object.keys(kvs).some((k) => !is_paging_param(k))) {
|
|
103
103
|
newhref = invalidate_pagings(newhref);
|
|
@@ -107,7 +107,8 @@ function set_state_fields(kvs) {
|
|
|
107
107
|
newhref = removeQueryStringParameter(newhref, kv[0]);
|
|
108
108
|
else newhref = updateQueryStringParameter(newhref, kv[0], kv[1]);
|
|
109
109
|
});
|
|
110
|
-
|
|
110
|
+
if (disable_pjax) href_to(newhref.replace("&&", "&").replace("?&", "?"));
|
|
111
|
+
else pjax_to(newhref.replace("&&", "&").replace("?&", "?"));
|
|
111
112
|
}
|
|
112
113
|
function unset_state_field(key) {
|
|
113
114
|
pjax_to(removeQueryStringParameter(get_current_state_url(), key));
|
|
@@ -202,6 +203,19 @@ function view_post(viewname, route, data, onDone) {
|
|
|
202
203
|
});
|
|
203
204
|
}
|
|
204
205
|
let logged_errors = [];
|
|
206
|
+
let error_catcher_enabled = false;
|
|
207
|
+
function enable_error_catcher() {
|
|
208
|
+
if (error_catcher_enabled) return;
|
|
209
|
+
document.addEventListener(
|
|
210
|
+
"DOMContentLoaded",
|
|
211
|
+
function () {
|
|
212
|
+
window.onerror = globalErrorCatcher;
|
|
213
|
+
},
|
|
214
|
+
false
|
|
215
|
+
);
|
|
216
|
+
error_catcher_enabled = true;
|
|
217
|
+
}
|
|
218
|
+
|
|
205
219
|
function globalErrorCatcher(message, source, lineno, colno, error) {
|
|
206
220
|
if (error && error.preventDefault) error.preventDefault();
|
|
207
221
|
if (logged_errors.includes(message)) return;
|
|
@@ -260,16 +274,22 @@ function ajax_modal(url, opts = {}) {
|
|
|
260
274
|
if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
|
|
261
275
|
else $("#scmodal").removeClass("no-submit-reload");
|
|
262
276
|
$.ajax(url, {
|
|
277
|
+
headers: {
|
|
278
|
+
SaltcornModalRequest: "true",
|
|
279
|
+
},
|
|
263
280
|
success: function (res, textStatus, request) {
|
|
264
281
|
var title = request.getResponseHeader("Page-Title");
|
|
282
|
+
var width = request.getResponseHeader("SaltcornModalWidth");
|
|
283
|
+
if (width) $(".modal-dialog").css("max-width", width);
|
|
284
|
+
else $(".modal-dialog").css("max-width", "");
|
|
265
285
|
if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
|
|
266
286
|
$("#scmodal .modal-body").html(res);
|
|
267
287
|
$("#scmodal").prop("data-modal-state", url);
|
|
268
288
|
new bootstrap.Modal($("#scmodal")).show();
|
|
269
289
|
initialize_page();
|
|
270
|
-
(opts.onOpen || function () {
|
|
290
|
+
(opts.onOpen || function () {})(res);
|
|
271
291
|
$("#scmodal").on("hidden.bs.modal", function (e) {
|
|
272
|
-
(opts.onClose || function () {
|
|
292
|
+
(opts.onClose || function () {})(res);
|
|
273
293
|
$("body").css("overflow", "");
|
|
274
294
|
});
|
|
275
295
|
},
|
|
@@ -278,7 +298,7 @@ function ajax_modal(url, opts = {}) {
|
|
|
278
298
|
|
|
279
299
|
function saveAndContinue(e, k) {
|
|
280
300
|
var form = $(e).closest("form");
|
|
281
|
-
const valres = form[0].reportValidity()
|
|
301
|
+
const valres = form[0].reportValidity();
|
|
282
302
|
if (!valres) return;
|
|
283
303
|
submitWithEmptyAction(form[0]);
|
|
284
304
|
var url = form.attr("action");
|
|
@@ -323,7 +343,7 @@ function applyViewConfig(e, url, k) {
|
|
|
323
343
|
"CSRF-Token": _sc_globalCsrf,
|
|
324
344
|
},
|
|
325
345
|
data: JSON.stringify(cfg),
|
|
326
|
-
error: function (request) {
|
|
346
|
+
error: function (request) {},
|
|
327
347
|
success: function (res) {
|
|
328
348
|
k && k(res);
|
|
329
349
|
!k && updateViewPreview();
|
|
@@ -344,10 +364,14 @@ function updateViewPreview() {
|
|
|
344
364
|
"CSRF-Token": _sc_globalCsrf,
|
|
345
365
|
},
|
|
346
366
|
|
|
347
|
-
error: function (request) {
|
|
367
|
+
error: function (request) {},
|
|
348
368
|
success: function (res) {
|
|
349
369
|
$preview.css({ opacity: 1.0 });
|
|
350
370
|
|
|
371
|
+
//disable functions preview migght try to call
|
|
372
|
+
set_state_field = () => {};
|
|
373
|
+
set_state_fields = () => {};
|
|
374
|
+
|
|
351
375
|
//disable elements in preview
|
|
352
376
|
$preview.html(res);
|
|
353
377
|
$preview.find("a").attr("href", "#");
|
|
@@ -357,11 +381,6 @@ function updateViewPreview() {
|
|
|
357
381
|
|
|
358
382
|
$preview.find("textarea").attr("disabled", true);
|
|
359
383
|
$preview.find("input").attr("readonly", true);
|
|
360
|
-
|
|
361
|
-
//disable functions preview migght try to call
|
|
362
|
-
set_state_field = () => { }
|
|
363
|
-
set_state_fields = () => { }
|
|
364
|
-
|
|
365
384
|
},
|
|
366
385
|
});
|
|
367
386
|
}
|
|
@@ -596,15 +615,13 @@ function build_mobile_app(button) {
|
|
|
596
615
|
localStorage.setItem("sidebarClosed", `${closed}`);
|
|
597
616
|
});
|
|
598
617
|
}
|
|
599
|
-
})()
|
|
600
|
-
|
|
601
|
-
|
|
618
|
+
})() +
|
|
602
619
|
/*
|
|
603
620
|
https://github.com/jeffdavidgreen/bootstrap-html5-history-tabs/blob/master/bootstrap-history-tabs.js
|
|
604
621
|
Copyright (c) 2015 Jeff Green
|
|
605
622
|
*/
|
|
606
623
|
|
|
607
|
-
|
|
624
|
+
(function ($) {
|
|
608
625
|
"use strict";
|
|
609
626
|
$.fn.historyTabs = function () {
|
|
610
627
|
var that = this;
|
|
@@ -619,21 +636,24 @@ function build_mobile_app(button) {
|
|
|
619
636
|
$(element).on("show.bs.tab", function () {
|
|
620
637
|
var stateObject = { url: $(this).attr("href") };
|
|
621
638
|
|
|
622
|
-
if (
|
|
639
|
+
if (
|
|
640
|
+
window.location.hash &&
|
|
641
|
+
stateObject.url !== window.location.hash
|
|
642
|
+
) {
|
|
623
643
|
window.history.pushState(
|
|
624
644
|
stateObject,
|
|
625
645
|
document.title,
|
|
626
646
|
window.location.pathname +
|
|
627
|
-
|
|
628
|
-
|
|
647
|
+
window.location.search +
|
|
648
|
+
$(this).attr("href")
|
|
629
649
|
);
|
|
630
650
|
} else {
|
|
631
651
|
window.history.replaceState(
|
|
632
652
|
stateObject,
|
|
633
653
|
document.title,
|
|
634
654
|
window.location.pathname +
|
|
635
|
-
|
|
636
|
-
|
|
655
|
+
window.location.search +
|
|
656
|
+
$(this).attr("href")
|
|
637
657
|
);
|
|
638
658
|
}
|
|
639
659
|
});
|
|
@@ -649,4 +669,33 @@ function build_mobile_app(button) {
|
|
|
649
669
|
|
|
650
670
|
// Copyright (c) 2011 Marcus Ekwall, http://writeless.se/
|
|
651
671
|
// https://github.com/mekwall/jquery-throttle
|
|
652
|
-
(function (a) {
|
|
672
|
+
(function (a) {
|
|
673
|
+
var b = a.jQuery || a.me || (a.me = {}),
|
|
674
|
+
i = function (e, f, g, h, c, a) {
|
|
675
|
+
f || (f = 100);
|
|
676
|
+
var d = !1,
|
|
677
|
+
j = !1,
|
|
678
|
+
i = typeof g === "function",
|
|
679
|
+
l = function (a, b) {
|
|
680
|
+
d = setTimeout(function () {
|
|
681
|
+
d = !1;
|
|
682
|
+
if (h || c) e.apply(a, b), c && (j = +new Date());
|
|
683
|
+
i && g.apply(a, b);
|
|
684
|
+
}, f);
|
|
685
|
+
},
|
|
686
|
+
k = function () {
|
|
687
|
+
if (!d || a) {
|
|
688
|
+
if (!d && !h && (!c || +new Date() - j > f))
|
|
689
|
+
e.apply(this, arguments), c && (j = +new Date());
|
|
690
|
+
(a || !c) && clearTimeout(d);
|
|
691
|
+
l(this, arguments);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
if (b.guid) k.guid = e.guid = e.guid || b.guid++;
|
|
695
|
+
return k;
|
|
696
|
+
};
|
|
697
|
+
b.throttle = i;
|
|
698
|
+
b.debounce = function (a, b, g, h, c) {
|
|
699
|
+
return i(a, b, g, h, c, !0);
|
|
700
|
+
};
|
|
701
|
+
})(this);
|
package/routes/actions.js
CHANGED
|
@@ -401,7 +401,7 @@ router.get(
|
|
|
401
401
|
sub2_page: "Configure",
|
|
402
402
|
contents: {
|
|
403
403
|
type: "card",
|
|
404
|
-
title: req.__("Configure trigger"),
|
|
404
|
+
title: req.__("Configure trigger %s", trigger.name),
|
|
405
405
|
contents: {
|
|
406
406
|
widths: [8, 4],
|
|
407
407
|
besides: [
|
|
@@ -456,6 +456,8 @@ router.get(
|
|
|
456
456
|
// create form
|
|
457
457
|
const form = new Form({
|
|
458
458
|
action: addOnDoneRedirect(`/actions/configure/${id}`, req),
|
|
459
|
+
onChange: "saveAndContinue(this)",
|
|
460
|
+
submitLabel: req.__("Done"),
|
|
459
461
|
fields: cfgFields,
|
|
460
462
|
});
|
|
461
463
|
// populate form values
|
|
@@ -468,7 +470,7 @@ router.get(
|
|
|
468
470
|
sub2_page: "Configure",
|
|
469
471
|
contents: {
|
|
470
472
|
type: "card",
|
|
471
|
-
title: req.__("Configure trigger"),
|
|
473
|
+
title: req.__("Configure trigger %s", trigger.name),
|
|
472
474
|
contents: renderForm(form, req.csrfToken()),
|
|
473
475
|
},
|
|
474
476
|
});
|
|
@@ -512,6 +514,10 @@ router.post(
|
|
|
512
514
|
});
|
|
513
515
|
} else {
|
|
514
516
|
await Trigger.update(trigger.id, { configuration: form.values });
|
|
517
|
+
if (req.xhr) {
|
|
518
|
+
res.json({ success: "ok" });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
515
521
|
req.flash("success", "Action configuration saved");
|
|
516
522
|
res.redirect(
|
|
517
523
|
req.query.on_done_redirect
|
package/routes/admin.js
CHANGED
|
@@ -59,6 +59,7 @@ const {
|
|
|
59
59
|
getState,
|
|
60
60
|
restart_tenant,
|
|
61
61
|
getTenant,
|
|
62
|
+
getRootState,
|
|
62
63
|
//get_other_domain_tenant,
|
|
63
64
|
get_process_init_time,
|
|
64
65
|
} = require("@saltcorn/data/db/state");
|
|
@@ -1820,9 +1821,22 @@ router.post(
|
|
|
1820
1821
|
* @returns {Promise<Form>} form
|
|
1821
1822
|
*/
|
|
1822
1823
|
const dev_form = async (req) => {
|
|
1824
|
+
const role_to_create_tenant = +getRootState().getConfig(
|
|
1825
|
+
"role_to_create_tenant"
|
|
1826
|
+
);
|
|
1827
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
1828
|
+
|
|
1823
1829
|
return await config_fields_form({
|
|
1824
1830
|
req,
|
|
1825
|
-
field_names: [
|
|
1831
|
+
field_names: [
|
|
1832
|
+
"development_mode",
|
|
1833
|
+
"log_sql",
|
|
1834
|
+
"log_client_errors",
|
|
1835
|
+
"log_level",
|
|
1836
|
+
...(isRoot || role_to_create_tenant < 10
|
|
1837
|
+
? ["npm_available_js_code"]
|
|
1838
|
+
: []),
|
|
1839
|
+
],
|
|
1826
1840
|
action: "/admin/dev",
|
|
1827
1841
|
});
|
|
1828
1842
|
};
|
package/routes/api.js
CHANGED
|
@@ -381,10 +381,7 @@ router.post(
|
|
|
381
381
|
res.status(400).json({ error: errors.join(", ") });
|
|
382
382
|
return;
|
|
383
383
|
}
|
|
384
|
-
const ins_res = await table.tryInsertRow(
|
|
385
|
-
row,
|
|
386
|
-
req.user ? +req.user.id : undefined
|
|
387
|
-
);
|
|
384
|
+
const ins_res = await table.tryInsertRow(row, req.user);
|
|
388
385
|
if (ins_res.error) res.status(400).json(ins_res);
|
|
389
386
|
else res.json(ins_res);
|
|
390
387
|
} else {
|
|
@@ -439,11 +436,7 @@ router.post(
|
|
|
439
436
|
res.status(400).json({ error: errors.join(", ") });
|
|
440
437
|
return;
|
|
441
438
|
}
|
|
442
|
-
const ins_res = await table.tryUpdateRow(
|
|
443
|
-
row,
|
|
444
|
-
id,
|
|
445
|
-
req.user ? +req.user.id : undefined
|
|
446
|
-
);
|
|
439
|
+
const ins_res = await table.tryUpdateRow(row, id, req.user);
|
|
447
440
|
|
|
448
441
|
if (ins_res.error) res.status(400).json(ins_res);
|
|
449
442
|
else res.json(ins_res);
|
package/routes/fields.js
CHANGED
|
@@ -28,11 +28,13 @@ const expressionBlurb = require("../markup/expression_blurb");
|
|
|
28
28
|
const {
|
|
29
29
|
readState,
|
|
30
30
|
add_free_variables_to_joinfields,
|
|
31
|
+
calcfldViewConfig,
|
|
31
32
|
} = require("@saltcorn/data/plugin-helper");
|
|
32
33
|
const { wizardCardTitle } = require("../markup/forms.js");
|
|
33
34
|
const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
|
|
34
35
|
const { applyAsync } = require("@saltcorn/data/utils");
|
|
35
36
|
const { text } = require("@saltcorn/markup/tags");
|
|
37
|
+
const { mkFormContentNoLayout } = require("@saltcorn/markup/form");
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* @type {object}
|
|
@@ -903,3 +905,45 @@ router.post(
|
|
|
903
905
|
res.send("");
|
|
904
906
|
})
|
|
905
907
|
);
|
|
908
|
+
|
|
909
|
+
router.post(
|
|
910
|
+
"/fieldviewcfgform/:tableName",
|
|
911
|
+
isAdmin,
|
|
912
|
+
error_catcher(async (req, res) => {
|
|
913
|
+
const { tableName } = req.params;
|
|
914
|
+
const {
|
|
915
|
+
field_name,
|
|
916
|
+
fieldview,
|
|
917
|
+
type,
|
|
918
|
+
join_field,
|
|
919
|
+
join_fieldview,
|
|
920
|
+
_columndef,
|
|
921
|
+
} = req.body;
|
|
922
|
+
const table = await Table.findOne({ name: tableName });
|
|
923
|
+
const fieldName = type == "Field" ? field_name : join_field;
|
|
924
|
+
const fv_name = type == "Field" ? fieldview : join_fieldview;
|
|
925
|
+
if (!fieldName) {
|
|
926
|
+
res.send("");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const field = await table.getField(fieldName);
|
|
931
|
+
|
|
932
|
+
const fieldViewConfigForms = await calcfldViewConfig([field], false, 0);
|
|
933
|
+
const formFields = fieldViewConfigForms[field.name][fv_name];
|
|
934
|
+
if (!formFields) {
|
|
935
|
+
res.send("");
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
formFields.forEach((ff) => {
|
|
939
|
+
ff.class = ff.class ? `${ff.class} item-menu` : "item-menu";
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
const form = new Form({
|
|
943
|
+
formStyle: "vert",
|
|
944
|
+
fields: formFields,
|
|
945
|
+
});
|
|
946
|
+
if (_columndef) form.values = JSON.parse(_columndef);
|
|
947
|
+
res.send(mkFormContentNoLayout(form));
|
|
948
|
+
})
|
|
949
|
+
);
|
package/routes/tenant.js
CHANGED
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
renderForm,
|
|
22
22
|
link,
|
|
23
23
|
post_delete_btn,
|
|
24
|
+
localeDateTime,
|
|
24
25
|
mkTable,
|
|
25
26
|
} = require("@saltcorn/markup");
|
|
26
27
|
const {
|
|
@@ -384,7 +385,7 @@ router.get(
|
|
|
384
385
|
},
|
|
385
386
|
{
|
|
386
387
|
label: req.__("Created"),
|
|
387
|
-
key: (r) =>
|
|
388
|
+
key: (r) => (r.created ? localeDateTime(r.created) : ""),
|
|
388
389
|
},
|
|
389
390
|
{
|
|
390
391
|
label: req.__("Information"),
|
package/routes/view.js
CHANGED
|
@@ -9,7 +9,7 @@ const Router = require("express-promise-router");
|
|
|
9
9
|
const View = require("@saltcorn/data/models/view");
|
|
10
10
|
const Table = require("@saltcorn/data/models/table");
|
|
11
11
|
|
|
12
|
-
const { text } = require("@saltcorn/markup/tags");
|
|
12
|
+
const { text, style } = require("@saltcorn/markup/tags");
|
|
13
13
|
const {
|
|
14
14
|
isAdmin,
|
|
15
15
|
error_catcher,
|
|
@@ -62,8 +62,20 @@ router.get(
|
|
|
62
62
|
res.redirect("/");
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
const isModal = req.headers?.saltcornmodalrequest;
|
|
66
|
+
|
|
65
67
|
const contents = await view.run_possibly_on_page(query, req, res);
|
|
66
|
-
const title =
|
|
68
|
+
const title =
|
|
69
|
+
isModal && view.attributes?.popup_title
|
|
70
|
+
? view.attributes?.popup_title
|
|
71
|
+
: scan_for_page_title(contents, view.name);
|
|
72
|
+
if (isModal && view.attributes?.popup_width)
|
|
73
|
+
res.set(
|
|
74
|
+
"SaltcornModalWidth",
|
|
75
|
+
`${view.attributes?.popup_width}${
|
|
76
|
+
view.attributes?.popup_width_units || "px"
|
|
77
|
+
}`
|
|
78
|
+
);
|
|
67
79
|
res.sendWrap(
|
|
68
80
|
title,
|
|
69
81
|
add_edit_bar({
|
package/routes/viewedit.js
CHANGED
|
@@ -134,6 +134,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
134
134
|
action: addOnDoneRedirect("/viewedit/save", req),
|
|
135
135
|
submitLabel: req.__("Configure") + " »",
|
|
136
136
|
blurb: req.__("First, please give some basic information about the view."),
|
|
137
|
+
tabs: { tabsStyle: "Accordion" },
|
|
137
138
|
fields: [
|
|
138
139
|
new Field({
|
|
139
140
|
label: req.__("View name"),
|
|
@@ -191,6 +192,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
191
192
|
"Requests to render this view directly will instead show the chosen page, if any. The chosen page should embed this view. Use this to decorate the view with additional elements."
|
|
192
193
|
),
|
|
193
194
|
input_type: "select",
|
|
195
|
+
tab: "View settings",
|
|
194
196
|
options: [
|
|
195
197
|
{ value: "", label: "" },
|
|
196
198
|
...pages.map((p) => ({ value: p.name, label: p.name })),
|
|
@@ -201,6 +203,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
201
203
|
label: req.__("Slug"),
|
|
202
204
|
sublabel: req.__("Field that can be used for a prettier URL structure"),
|
|
203
205
|
type: "String",
|
|
206
|
+
tab: "View settings",
|
|
204
207
|
attributes: {
|
|
205
208
|
calcOptions: [
|
|
206
209
|
"table_name",
|
|
@@ -209,6 +212,33 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
209
212
|
},
|
|
210
213
|
showIf: { viewtemplate: hasTable },
|
|
211
214
|
}),
|
|
215
|
+
new Field({
|
|
216
|
+
name: "popup_title",
|
|
217
|
+
label: req.__("Title"),
|
|
218
|
+
type: "String",
|
|
219
|
+
parent_field: "attributes",
|
|
220
|
+
tab: "Popup settings",
|
|
221
|
+
}),
|
|
222
|
+
{
|
|
223
|
+
name: "popup_width",
|
|
224
|
+
label: req.__("Column width"),
|
|
225
|
+
type: "Integer",
|
|
226
|
+
tab: "Popup settings",
|
|
227
|
+
parent_field: "attributes",
|
|
228
|
+
attributes: { asideNext: true },
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "popup_width_units",
|
|
232
|
+
label: req.__("Units"),
|
|
233
|
+
type: "String",
|
|
234
|
+
tab: "Popup settings",
|
|
235
|
+
fieldview: "radio_group",
|
|
236
|
+
parent_field: "attributes",
|
|
237
|
+
attributes: {
|
|
238
|
+
inline: true,
|
|
239
|
+
options: ["px", "%", "vw", "em", "rem"],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
212
242
|
...(isEdit
|
|
213
243
|
? [
|
|
214
244
|
new Field({
|
|
@@ -396,6 +426,7 @@ router.post(
|
|
|
396
426
|
const vt = getState().viewtemplates[v.viewtemplate];
|
|
397
427
|
if (vt.initial_config) v.configuration = await vt.initial_config(v);
|
|
398
428
|
else v.configuration = {};
|
|
429
|
+
//console.log(v);
|
|
399
430
|
await View.create(v);
|
|
400
431
|
}
|
|
401
432
|
res.redirect(
|
|
@@ -503,6 +534,9 @@ router.get(
|
|
|
503
534
|
res.redirect("/viewedit");
|
|
504
535
|
return;
|
|
505
536
|
}
|
|
537
|
+
(view.configuration?.columns || []).forEach((c) => {
|
|
538
|
+
c._columndef = JSON.stringify(c);
|
|
539
|
+
});
|
|
506
540
|
const configFlow = await view.get_config_flow(req);
|
|
507
541
|
const hasConfig =
|
|
508
542
|
view.configuration && Object.keys(view.configuration).length > 0;
|
|
@@ -644,6 +678,7 @@ router.post(
|
|
|
644
678
|
|
|
645
679
|
if (viewname && req.body) {
|
|
646
680
|
const view = await View.findOne({ name: viewname });
|
|
681
|
+
req.staticFieldViewConfig = true;
|
|
647
682
|
const configFlow = await view.get_config_flow(req);
|
|
648
683
|
const step = await configFlow.singleStepForm(req.body, req);
|
|
649
684
|
if (step?.renderForm) {
|
package/s3storage.js
CHANGED
|
@@ -46,7 +46,8 @@ module.exports = {
|
|
|
46
46
|
s3upload(req, res, next);
|
|
47
47
|
} else {
|
|
48
48
|
// Use regular file upload https://www.npmjs.com/package/express-fileupload
|
|
49
|
-
const fileSizeLimit =
|
|
49
|
+
const fileSizeLimit =
|
|
50
|
+
1024 * +getState().getConfig("file_upload_limit", 0);
|
|
50
51
|
fileUpload({
|
|
51
52
|
useTempFiles: true,
|
|
52
53
|
createParentPath: true,
|
|
@@ -58,9 +59,11 @@ module.exports = {
|
|
|
58
59
|
defCharset: "utf8",
|
|
59
60
|
defParamCharset: "utf8",
|
|
60
61
|
// 0 - means no upload limit check
|
|
61
|
-
limits:
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
limits: fileSizeLimit
|
|
63
|
+
? {
|
|
64
|
+
fileSize: fileSizeLimit,
|
|
65
|
+
}
|
|
66
|
+
: {},
|
|
64
67
|
abortOnLimit: fileSizeLimit !== 0,
|
|
65
68
|
// 0 - means no upload limit check
|
|
66
69
|
uploadTimeout: getState().getConfig("file_upload_timeout", 0),
|
package/tests/fields.test.js
CHANGED
|
@@ -367,3 +367,26 @@ describe("Field Endpoints", () => {
|
|
|
367
367
|
.expect((r) => +r.body > 1);
|
|
368
368
|
});
|
|
369
369
|
});
|
|
370
|
+
|
|
371
|
+
describe("Fieldview config", () => {
|
|
372
|
+
//itShouldRedirectUnauthToLogin("/field/2");
|
|
373
|
+
it("should return fieldview options", async () => {
|
|
374
|
+
const loginCookie = await getAdminLoginCookie();
|
|
375
|
+
|
|
376
|
+
const app = await getApp({ disableCsrf: true });
|
|
377
|
+
|
|
378
|
+
await request(app)
|
|
379
|
+
.post("/field/fieldviewcfgform/books")
|
|
380
|
+
.set("Cookie", loginCookie)
|
|
381
|
+
.send({
|
|
382
|
+
type: "Field",
|
|
383
|
+
field_name: "pages",
|
|
384
|
+
fieldview: "progress_bar",
|
|
385
|
+
})
|
|
386
|
+
.expect(
|
|
387
|
+
toInclude(
|
|
388
|
+
`<div class="form-group"><div><label for="inputmax">max</label></div><div><input type="number" class="form-control item-menu" data-fieldname="max" name="max" id="inputmax" step="1" required></div></div><div class="form-group"><div><label for="inputbar_color">Bar color</label></div><div><input type="color" class="form-control item-menu" data-fieldname="bar_color" name="bar_color" id="inputbar_color"></div></div><div class="form-group"><div><label for="inputbg_color">Background color</label></div><div><input type="color" class="form-control item-menu" data-fieldname="bg_color" name="bg_color" id="inputbg_color"></div></div><div class="form-group"><div><label for="inputpx_height">Height in px</label></div><div><input type="number" class="form-control item-menu" data-fieldname="px_height" name="px_height" id="inputpx_height" step="1"></div></div>`
|
|
389
|
+
)
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
});
|
package/wrapper.js
CHANGED
|
@@ -203,6 +203,8 @@ const get_headers = (req, version_tag, description, extras = []) => {
|
|
|
203
203
|
from_cfg.push({ style: state.getConfig("page_custom_css", "") });
|
|
204
204
|
if (state.getConfig("page_custom_html", ""))
|
|
205
205
|
from_cfg.push({ headerTag: state.getConfig("page_custom_html", "") });
|
|
206
|
+
if (state.getConfig("log_client_errors", false))
|
|
207
|
+
from_cfg.push({ scriptBody: `enable_error_catcher()` });
|
|
206
208
|
const state_headers = [];
|
|
207
209
|
for (const hs of Object.values(state.headers)) {
|
|
208
210
|
state_headers.push(...hs);
|