@saltcorn/server 0.9.4-beta.10 → 0.9.4-beta.12
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 +8 -2
- package/markup/admin.js +22 -18
- package/package.json +9 -8
- package/public/saltcorn-builder.css +18 -1
- package/public/saltcorn.js +9 -8
- package/routes/actions.js +7 -1
- package/routes/admin.js +1 -0
- package/routes/common_lists.js +1 -1
- package/routes/homepage.js +2 -1
- package/routes/page.js +30 -13
- package/routes/page_groupedit.js +104 -83
- package/routes/pageedit.js +1 -1
- package/routes/tag_entries.js +6 -1
- package/routes/utils.js +19 -0
- package/routes/view.js +5 -1
- package/routes/viewedit.js +9 -0
- package/tests/page_group.test.js +1 -0
package/locales/en.json
CHANGED
|
@@ -1360,5 +1360,11 @@
|
|
|
1360
1360
|
"Add entries to tag %s": "Add entries to tag %s",
|
|
1361
1361
|
"Tag not found": "Tag not found",
|
|
1362
1362
|
"Unable to save: No page or no layout": "Unable to save: No page or no layout",
|
|
1363
|
-
"Unable to save: No view": "Unable to save: No view"
|
|
1364
|
-
|
|
1363
|
+
"Unable to save: No view": "Unable to save: No view",
|
|
1364
|
+
"%s has no eligible page": "%s has no eligible page",
|
|
1365
|
+
"Random allocation": "Random allocation",
|
|
1366
|
+
"Serve a random page, ignoring the eligible formula. Within a session, reloads will always deliver the same page. This is a basic requirement for A/B testing.": "Serve a random page, ignoring the eligible formula. Within a session, reloads will always deliver the same page. This is a basic requirement for A/B testing.",
|
|
1367
|
+
"Pagegroup %s not found": "Pagegroup %s not found",
|
|
1368
|
+
"Create trigger": "Create trigger",
|
|
1369
|
+
"Omit the menu from this view": "Omit the menu from this view"
|
|
1370
|
+
}
|
package/markup/admin.js
CHANGED
|
@@ -95,23 +95,27 @@ const add_edit_bar = ({
|
|
|
95
95
|
const singleton = view?.viewtemplateObj?.singleton;
|
|
96
96
|
|
|
97
97
|
const bar = div(
|
|
98
|
-
{ class: "
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
{ class: "card p-1 mt-1 mb-3 d-print-none admin-edit-bar" },
|
|
99
|
+
div(
|
|
100
|
+
{ class: "card-body p-1" },
|
|
101
|
+
i({ class: "fas fa-user-cog me-1" }),
|
|
102
|
+
what && span({ class: "ms-1 me-2 badge bg-secondary" }, what),
|
|
103
|
+
title,
|
|
104
|
+
!singleton &&
|
|
105
|
+
a(
|
|
106
|
+
{ class: "ms-2", href: url },
|
|
107
|
+
"Edit ",
|
|
108
|
+
i({ class: "fas fa-edit" })
|
|
109
|
+
),
|
|
110
|
+
cfgUrl && !singleton
|
|
111
|
+
? a(
|
|
112
|
+
{ class: "ms-1 me-3", href: cfgUrl },
|
|
113
|
+
"Configure ",
|
|
114
|
+
i({ class: "fas fa-cog" })
|
|
115
|
+
)
|
|
116
|
+
: "",
|
|
117
|
+
!singleton && viewSpec
|
|
118
|
+
)
|
|
115
119
|
);
|
|
116
120
|
|
|
117
121
|
if (contents.above) {
|
|
@@ -232,7 +236,7 @@ const send_infoarch_page = (args) => {
|
|
|
232
236
|
{ text: "Multitenancy", href: "/tenant/settings" },
|
|
233
237
|
]
|
|
234
238
|
: []),
|
|
235
|
-
{ text: "Pagegroups", href: "/page_group/settings"},
|
|
239
|
+
{ text: "Pagegroups", href: "/page_group/settings" },
|
|
236
240
|
{ text: "Tags", href: "/tag" },
|
|
237
241
|
{ text: "Diagram", href: "/diagram" },
|
|
238
242
|
],
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.4-beta.
|
|
3
|
+
"version": "0.9.4-beta.12",
|
|
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.451.0",
|
|
10
|
-
"@saltcorn/base-plugin": "0.9.4-beta.
|
|
11
|
-
"@saltcorn/builder": "0.9.4-beta.
|
|
12
|
-
"@saltcorn/data": "0.9.4-beta.
|
|
13
|
-
"@saltcorn/admin-models": "0.9.4-beta.
|
|
14
|
-
"@saltcorn/filemanager": "0.9.4-beta.
|
|
15
|
-
"@saltcorn/markup": "0.9.4-beta.
|
|
16
|
-
"@saltcorn/sbadmin2": "0.9.4-beta.
|
|
10
|
+
"@saltcorn/base-plugin": "0.9.4-beta.12",
|
|
11
|
+
"@saltcorn/builder": "0.9.4-beta.12",
|
|
12
|
+
"@saltcorn/data": "0.9.4-beta.12",
|
|
13
|
+
"@saltcorn/admin-models": "0.9.4-beta.12",
|
|
14
|
+
"@saltcorn/filemanager": "0.9.4-beta.12",
|
|
15
|
+
"@saltcorn/markup": "0.9.4-beta.12",
|
|
16
|
+
"@saltcorn/sbadmin2": "0.9.4-beta.12",
|
|
17
17
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
18
18
|
"@socket.io/sticky": "^1.0.1",
|
|
19
19
|
"adm-zip": "0.5.10",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"pluralize": "^8.0.0",
|
|
56
56
|
"qrcode": "1.5.1",
|
|
57
57
|
"resize-with-sharp-or-jimp": "0.1.7",
|
|
58
|
+
"semver": "^7.6.0",
|
|
58
59
|
"socket.io": "4.6.0",
|
|
59
60
|
"systeminformation": "^5.21.7",
|
|
60
61
|
"thirty-two": "1.0.2",
|
|
@@ -13,6 +13,10 @@ div.settings-panel {
|
|
|
13
13
|
min-height: 150px;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
div.settings-panel td {
|
|
17
|
+
vertical-align: top;
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
span.is-builder-link {
|
|
17
21
|
color: blue;
|
|
18
22
|
text-decoration: underline;
|
|
@@ -190,10 +194,16 @@ div.settings-panel div.rfipbtn {
|
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
div.componets-and-library-accordion {
|
|
193
|
-
min-height: 45vh;
|
|
194
197
|
margin-top: -0.25rem;
|
|
195
198
|
}
|
|
196
199
|
|
|
200
|
+
.builder-left-enlarged .componets-and-library-accordion {
|
|
201
|
+
min-height: 20vh;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.builder-left-shrunk .componets-and-library-accordion {
|
|
205
|
+
min-height: 35vh;
|
|
206
|
+
}
|
|
197
207
|
.builder-layers {
|
|
198
208
|
max-height: calc(45vh - 50px);
|
|
199
209
|
overflow-y: scroll;
|
|
@@ -299,6 +309,13 @@ Copyright (c) 2017 Taha Paksu
|
|
|
299
309
|
margin: 0px;
|
|
300
310
|
padding: 1px;
|
|
301
311
|
text-align: center;
|
|
312
|
+
cursor: pointer;
|
|
313
|
+
}
|
|
314
|
+
.boxmodel-container .boxmodel-input-container {
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
}
|
|
317
|
+
.boxmodel-container .boxmodel-header {
|
|
318
|
+
cursor: pointer;
|
|
302
319
|
}
|
|
303
320
|
.boxmodel-container .dim-display {
|
|
304
321
|
background: transparent;
|
package/public/saltcorn.js
CHANGED
|
@@ -154,7 +154,7 @@ $(function () {
|
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
function reload_embedded_view(viewname) {
|
|
157
|
+
function reload_embedded_view(viewname, new_query_string) {
|
|
158
158
|
if (window._sc_loglevel > 4)
|
|
159
159
|
console.log(
|
|
160
160
|
"reload_embedded_view",
|
|
@@ -164,15 +164,16 @@ function reload_embedded_view(viewname) {
|
|
|
164
164
|
);
|
|
165
165
|
$(`[data-sc-embed-viewname="${viewname}"]`).each(function () {
|
|
166
166
|
const $e = $(this);
|
|
167
|
-
|
|
168
|
-
$e.attr("data-sc-local-state") || $e.attr("data-sc-view-source");
|
|
167
|
+
let url = $e.attr("data-sc-local-state") || $e.attr("data-sc-view-source");
|
|
169
168
|
if (!url) return;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
};
|
|
169
|
+
if (new_query_string) {
|
|
170
|
+
url = url.split("?")[0] + "?" + new_query_string;
|
|
171
|
+
}
|
|
174
172
|
$.ajax(url, {
|
|
175
|
-
headers
|
|
173
|
+
headers: {
|
|
174
|
+
pjaxpageload: "true",
|
|
175
|
+
localizedstate: "true", //no admin bar
|
|
176
|
+
},
|
|
176
177
|
success: function (res, textStatus, request) {
|
|
177
178
|
$e.html(res);
|
|
178
179
|
initialize_page();
|
package/routes/actions.js
CHANGED
|
@@ -105,7 +105,13 @@ router.get(
|
|
|
105
105
|
title: req.__("Triggers"),
|
|
106
106
|
contents: div(
|
|
107
107
|
await getTriggerList(triggers, req, { filterOnTag }),
|
|
108
|
-
|
|
108
|
+
a(
|
|
109
|
+
{
|
|
110
|
+
href: "/actions/new",
|
|
111
|
+
class: "btn btn-primary",
|
|
112
|
+
},
|
|
113
|
+
req.__("Create trigger")
|
|
114
|
+
)
|
|
109
115
|
),
|
|
110
116
|
},
|
|
111
117
|
{
|
package/routes/admin.js
CHANGED
package/routes/common_lists.js
CHANGED
package/routes/homepage.js
CHANGED
|
@@ -24,6 +24,7 @@ const packagejson = require("../package.json");
|
|
|
24
24
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
25
25
|
const { fileUploadForm } = require("../markup/forms");
|
|
26
26
|
const { get_base_url, sendHtmlFile, getEligiblePage } = require("./utils.js");
|
|
27
|
+
const semver = require("semver");
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Tables List
|
|
@@ -519,8 +520,8 @@ const no_views_logged_in = async (req, res) => {
|
|
|
519
520
|
const latest =
|
|
520
521
|
isRoot && (await get_latest_npm_version("@saltcorn/cli", 500));
|
|
521
522
|
const can_update =
|
|
522
|
-
packagejson.version !== latest &&
|
|
523
523
|
latest &&
|
|
524
|
+
semver.gt(latest, packagejson.version) &&
|
|
524
525
|
!process.env.SALTCORN_DISABLE_UPGRADE;
|
|
525
526
|
if (latest && can_update && isRoot)
|
|
526
527
|
req.flash(
|
package/routes/page.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
isAdmin,
|
|
17
17
|
sendHtmlFile,
|
|
18
18
|
getEligiblePage,
|
|
19
|
+
getRandomPage,
|
|
19
20
|
} = require("../routes/utils.js");
|
|
20
21
|
const { isTest } = require("@saltcorn/data/utils");
|
|
21
22
|
const { add_edit_bar } = require("../markup/admin.js");
|
|
@@ -88,20 +89,36 @@ const runPage = async (page, req, res, tic) => {
|
|
|
88
89
|
const runPageGroup = async (pageGroup, req, res, tic) => {
|
|
89
90
|
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
90
91
|
if (role <= pageGroup.min_role) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!
|
|
92
|
+
if (pageGroup.random_allocation) {
|
|
93
|
+
const page = getRandomPage(pageGroup, req);
|
|
94
|
+
if (typeof page === "string") {
|
|
95
|
+
getState().log(2, page);
|
|
96
|
+
res.status(400).sendWrap(req.__("Internal Error"), page);
|
|
97
|
+
} else if (!page) {
|
|
98
|
+
getState().log(2, `Unable to find a random page in ${pageGroup.name}`);
|
|
99
|
+
res
|
|
100
|
+
.status(404)
|
|
101
|
+
.sendWrap(
|
|
102
|
+
req.__("Internal Error"),
|
|
103
|
+
req.__("Unable to find a random page in %s", pageGroup.name)
|
|
104
|
+
);
|
|
105
|
+
} else await runPage(page, req, res, tic);
|
|
97
106
|
} else {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
.sendWrap(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
const eligible = await getEligiblePage(pageGroup, req, res);
|
|
108
|
+
if (typeof eligible === "string") {
|
|
109
|
+
getState().log(2, eligible);
|
|
110
|
+
res.status(400).sendWrap(req.__("Internal Error"), eligible);
|
|
111
|
+
} else if (eligible) {
|
|
112
|
+
if (!eligible.isReload) await runPage(eligible, req, res, tic);
|
|
113
|
+
} else {
|
|
114
|
+
getState().log(2, `Pagegroup ${pageGroup.name} has no eligible page`);
|
|
115
|
+
res
|
|
116
|
+
.status(404)
|
|
117
|
+
.sendWrap(
|
|
118
|
+
req.__("Internal Error"),
|
|
119
|
+
req.__("%s has no eligible page", pageGroup.name)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
105
122
|
}
|
|
106
123
|
} else {
|
|
107
124
|
getState().log(2, `Pagegroup ${pageGroup.name} not authorized`);
|
package/routes/page_groupedit.js
CHANGED
|
@@ -29,9 +29,17 @@ const groupPropsForm = async (req, isNew) => {
|
|
|
29
29
|
...(isNew
|
|
30
30
|
? {}
|
|
31
31
|
: {
|
|
32
|
-
onChange: `
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
onChange: `
|
|
33
|
+
saveAndContinue(this, (res) => {
|
|
34
|
+
history.replaceState(null, '', res.responseJSON.row.name);
|
|
35
|
+
const arrowsVisible = $('#upDownArrowsId').length > 0;
|
|
36
|
+
if (
|
|
37
|
+
arrowsVisible && res.responseJSON.row.random_allocation ||
|
|
38
|
+
!arrowsVisible && !res.responseJSON.row.random_allocation
|
|
39
|
+
) {
|
|
40
|
+
window.location.reload();
|
|
41
|
+
}
|
|
42
|
+
});`,
|
|
35
43
|
}),
|
|
36
44
|
noSubmitButton: !isNew,
|
|
37
45
|
fields: [
|
|
@@ -68,65 +76,78 @@ const groupPropsForm = async (req, isNew) => {
|
|
|
68
76
|
input_type: "select",
|
|
69
77
|
options: roles.map((r) => ({ value: r.id, label: r.role })),
|
|
70
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: "random_allocation",
|
|
81
|
+
label: req.__("Random allocation"),
|
|
82
|
+
type: "Bool",
|
|
83
|
+
sublabel: req.__(
|
|
84
|
+
"Serve a random page, ignoring the eligible formula. " +
|
|
85
|
+
"Within a session, reloads will always deliver the same page. " +
|
|
86
|
+
"This is a basic requirement for A/B testing."
|
|
87
|
+
),
|
|
88
|
+
},
|
|
71
89
|
],
|
|
72
90
|
});
|
|
73
91
|
};
|
|
74
92
|
|
|
75
|
-
const memberForm = async (action, req,
|
|
93
|
+
const memberForm = async (action, req, group, pageValidator) => {
|
|
76
94
|
const pageOptions = (await Page.find()).map((p) => p.name);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
attributes: {
|
|
88
|
-
options: pageOptions,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "description",
|
|
93
|
-
label: req.__("Description"),
|
|
94
|
-
type: "String",
|
|
95
|
-
sublabel: req.__("A description of the group member"),
|
|
95
|
+
const fields = [
|
|
96
|
+
{
|
|
97
|
+
name: "page_name",
|
|
98
|
+
label: req.__("Page"),
|
|
99
|
+
sublabel: req.__("Page to be served"),
|
|
100
|
+
type: "String",
|
|
101
|
+
required: true,
|
|
102
|
+
validator: pageValidator,
|
|
103
|
+
attributes: {
|
|
104
|
+
options: pageOptions,
|
|
96
105
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "description",
|
|
109
|
+
label: req.__("Description"),
|
|
110
|
+
type: "String",
|
|
111
|
+
sublabel: req.__("A description of the group member"),
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
if (!group.random_allocation) {
|
|
115
|
+
fields.push({
|
|
116
|
+
name: "eligible_formula",
|
|
117
|
+
label: req.__("Eligible Formula"),
|
|
118
|
+
sublabel:
|
|
119
|
+
req.__("Formula to determine if this page should be served.") +
|
|
120
|
+
br() +
|
|
121
|
+
span(
|
|
122
|
+
"Variables in scope: ",
|
|
123
|
+
[
|
|
124
|
+
"width",
|
|
125
|
+
"height",
|
|
126
|
+
"innerWidth",
|
|
127
|
+
"innerHeight",
|
|
128
|
+
"user",
|
|
129
|
+
"locale",
|
|
130
|
+
"device",
|
|
131
|
+
]
|
|
132
|
+
.map((f) => code(f))
|
|
133
|
+
.join(", ")
|
|
134
|
+
),
|
|
135
|
+
help: {
|
|
136
|
+
topic: "Eligible Formula",
|
|
123
137
|
},
|
|
124
|
-
|
|
138
|
+
type: "String",
|
|
139
|
+
required: true,
|
|
140
|
+
class: "validate-expression",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return new Form({
|
|
144
|
+
action,
|
|
145
|
+
fields,
|
|
125
146
|
additionalButtons: [
|
|
126
147
|
{
|
|
127
148
|
label: req.__("Cancel"),
|
|
128
149
|
class: "btn btn-primary",
|
|
129
|
-
onclick: `cancelMemberEdit('${
|
|
150
|
+
onclick: `cancelMemberEdit('${group.name}');`,
|
|
130
151
|
},
|
|
131
152
|
],
|
|
132
153
|
});
|
|
@@ -142,7 +163,7 @@ const editMemberForm = async (member, req) => {
|
|
|
142
163
|
return await memberForm(
|
|
143
164
|
`/page_groupedit/edit-member/${member.id}`,
|
|
144
165
|
req,
|
|
145
|
-
group
|
|
166
|
+
group,
|
|
146
167
|
validator
|
|
147
168
|
);
|
|
148
169
|
};
|
|
@@ -156,7 +177,7 @@ const addMemberForm = async (group, req) => {
|
|
|
156
177
|
return await memberForm(
|
|
157
178
|
`/page_groupedit/add-member/${group.name}`,
|
|
158
179
|
req,
|
|
159
|
-
group
|
|
180
|
+
group,
|
|
160
181
|
validator
|
|
161
182
|
);
|
|
162
183
|
};
|
|
@@ -224,7 +245,7 @@ const pageGroupMembers = async (pageGroup, req) => {
|
|
|
224
245
|
if (members.length <= 1) return "";
|
|
225
246
|
else
|
|
226
247
|
return div(
|
|
227
|
-
{ class: "container" },
|
|
248
|
+
{ class: "container", id: "upDownArrowsId" },
|
|
228
249
|
div(
|
|
229
250
|
{ class: "row" },
|
|
230
251
|
div(
|
|
@@ -266,38 +287,38 @@ const pageGroupMembers = async (pageGroup, req) => {
|
|
|
266
287
|
)
|
|
267
288
|
);
|
|
268
289
|
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
key: (member) =>
|
|
284
|
-
link(`/page_groupedit/edit-member/${member.id}`, req.__("Edit")),
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
label: req.__("Delete"),
|
|
288
|
-
key: (member) =>
|
|
289
|
-
post_delete_btn(
|
|
290
|
-
`/page_groupedit/remove-member/${member.id}`,
|
|
291
|
-
req,
|
|
292
|
-
req.__("Member %s", member.sequence)
|
|
293
|
-
),
|
|
294
|
-
},
|
|
295
|
-
],
|
|
296
|
-
members,
|
|
290
|
+
const tblArr = [
|
|
291
|
+
{
|
|
292
|
+
label: req.__("Page"),
|
|
293
|
+
key: (r) =>
|
|
294
|
+
link(`/page/${pageIdToName[r.page_id]}`, pageIdToName[r.page_id]),
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
if (!pageGroup.random_allocation) {
|
|
298
|
+
tblArr.push({
|
|
299
|
+
label: "",
|
|
300
|
+
key: (r) => upDownBtns(r, req),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
tblArr.push(
|
|
297
304
|
{
|
|
298
|
-
|
|
305
|
+
label: req.__("Edit"),
|
|
306
|
+
key: (member) =>
|
|
307
|
+
link(`/page_groupedit/edit-member/${member.id}`, req.__("Edit")),
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
label: req.__("Delete"),
|
|
311
|
+
key: (member) =>
|
|
312
|
+
post_delete_btn(
|
|
313
|
+
`/page_groupedit/remove-member/${member.id}`,
|
|
314
|
+
req,
|
|
315
|
+
req.__("Member %s", member.sequence)
|
|
316
|
+
),
|
|
299
317
|
}
|
|
300
318
|
);
|
|
319
|
+
return mkTable(tblArr, members, {
|
|
320
|
+
hover: true,
|
|
321
|
+
});
|
|
301
322
|
};
|
|
302
323
|
|
|
303
324
|
/**
|
package/routes/pageedit.js
CHANGED
|
@@ -383,7 +383,7 @@ const wrap = (contents, noCard, req, page) => ({
|
|
|
383
383
|
crumbs: [
|
|
384
384
|
{ text: req.__("Pages"), href: "/pageedit" },
|
|
385
385
|
page
|
|
386
|
-
? { href: `/page/${page.name}`, text: page.name }
|
|
386
|
+
? { href: `/page/${encodeURIComponent(page.name)}`, text: page.name }
|
|
387
387
|
: { text: req.__("New") },
|
|
388
388
|
],
|
|
389
389
|
},
|
package/routes/tag_entries.js
CHANGED
|
@@ -30,7 +30,12 @@ const buildFields = (entryType, formOptions, req) => {
|
|
|
30
30
|
div(
|
|
31
31
|
{ class: "col-sm-10" },
|
|
32
32
|
select(
|
|
33
|
-
{
|
|
33
|
+
{
|
|
34
|
+
name: "ids",
|
|
35
|
+
class: "form-control form-select",
|
|
36
|
+
multiple: true,
|
|
37
|
+
size: 20,
|
|
38
|
+
},
|
|
34
39
|
list.map((entry) => {
|
|
35
40
|
return option({ value: entry.id, label: entry.name });
|
|
36
41
|
})
|
package/routes/utils.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
features,
|
|
14
14
|
} = require("@saltcorn/data/db/state");
|
|
15
15
|
const { get_base_url } = require("@saltcorn/data/models/config");
|
|
16
|
+
const { hash } = require("@saltcorn/data/utils");
|
|
16
17
|
const { input, script, domReady } = require("@saltcorn/markup/tags");
|
|
17
18
|
const session = require("express-session");
|
|
18
19
|
const cookieSession = require("cookie-session");
|
|
@@ -21,6 +22,7 @@ const { validateHeaderName, validateHeaderValue } = require("http");
|
|
|
21
22
|
const Crash = require("@saltcorn/data/models/crash");
|
|
22
23
|
const File = require("@saltcorn/data/models/file");
|
|
23
24
|
const User = require("@saltcorn/data/models/user");
|
|
25
|
+
const Page = require("@saltcorn/data/models/page");
|
|
24
26
|
const si = require("systeminformation");
|
|
25
27
|
const {
|
|
26
28
|
config_fields_form,
|
|
@@ -30,6 +32,7 @@ const {
|
|
|
30
32
|
} = require("../markup/admin.js");
|
|
31
33
|
const path = require("path");
|
|
32
34
|
const { UAParser } = require("ua-parser-js");
|
|
35
|
+
const crypto = require("crypto");
|
|
33
36
|
|
|
34
37
|
const get_sys_info = async () => {
|
|
35
38
|
const disks = await si.fsSize();
|
|
@@ -505,6 +508,21 @@ const getEligiblePage = async (pageGroup, req, res) => {
|
|
|
505
508
|
}
|
|
506
509
|
};
|
|
507
510
|
|
|
511
|
+
/**
|
|
512
|
+
* @param {PageGroup} pageGroup
|
|
513
|
+
* @param {any} req
|
|
514
|
+
* @returns the page, null or an error msg
|
|
515
|
+
*/
|
|
516
|
+
const getRandomPage = (pageGroup, req) => {
|
|
517
|
+
if (pageGroup.members.length === 0)
|
|
518
|
+
return req.__("Pagegroup %s has no members", pageGroup.name);
|
|
519
|
+
const hash = crypto.createHash("sha1").update(req.sessionID).digest("hex");
|
|
520
|
+
const idx =
|
|
521
|
+
parseInt(hash.substring(hash.length - 4), 16) % pageGroup.members.length;
|
|
522
|
+
const sessionMember = pageGroup.members[idx];
|
|
523
|
+
return Page.findOne({ id: sessionMember.page_id });
|
|
524
|
+
};
|
|
525
|
+
|
|
508
526
|
module.exports = {
|
|
509
527
|
sqlsanitize,
|
|
510
528
|
csrfField,
|
|
@@ -524,4 +542,5 @@ module.exports = {
|
|
|
524
542
|
sendHtmlFile,
|
|
525
543
|
setRole,
|
|
526
544
|
getEligiblePage,
|
|
545
|
+
getRandomPage,
|
|
527
546
|
};
|
package/routes/view.js
CHANGED
|
@@ -79,6 +79,7 @@ router.get(
|
|
|
79
79
|
if ((title || "").includes("{{")) {
|
|
80
80
|
title = await view.interpolate_title_string(title, query);
|
|
81
81
|
}
|
|
82
|
+
title = { title };
|
|
82
83
|
if (isModal && view.attributes?.popup_width)
|
|
83
84
|
res.set(
|
|
84
85
|
"SaltcornModalWidth",
|
|
@@ -102,7 +103,10 @@ router.get(
|
|
|
102
103
|
if ((description || "").includes("{{")) {
|
|
103
104
|
description = await view.interpolate_title_string(description, query);
|
|
104
105
|
}
|
|
105
|
-
title =
|
|
106
|
+
title.description = description;
|
|
107
|
+
}
|
|
108
|
+
if (view.attributes?.no_menu) {
|
|
109
|
+
title.no_menu = true;
|
|
106
110
|
}
|
|
107
111
|
const tock = new Date();
|
|
108
112
|
const ms = tock.getTime() - tic.getTime();
|
package/routes/viewedit.js
CHANGED
|
@@ -217,6 +217,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
217
217
|
),
|
|
218
218
|
}),
|
|
219
219
|
new Field({
|
|
220
|
+
// legacy
|
|
220
221
|
name: "default_render_page",
|
|
221
222
|
label: req.__("Show on page"),
|
|
222
223
|
sublabel: req.__(
|
|
@@ -243,6 +244,14 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
243
244
|
},
|
|
244
245
|
showIf: { viewtemplate: hasTable },
|
|
245
246
|
}),
|
|
247
|
+
new Field({
|
|
248
|
+
name: "no_menu",
|
|
249
|
+
label: req.__("No menu"),
|
|
250
|
+
sublabel: req.__("Omit the menu from this view"),
|
|
251
|
+
tab: "View settings",
|
|
252
|
+
parent_field: "attributes",
|
|
253
|
+
type: "Bool",
|
|
254
|
+
}),
|
|
246
255
|
new Field({
|
|
247
256
|
name: "popup_title",
|
|
248
257
|
label: req.__("Title"),
|