@saltcorn/server 0.9.4-beta.2 → 0.9.4-beta.20
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/app.js +16 -1
- package/auth/admin.js +19 -3
- package/auth/routes.js +8 -2
- package/help/JavaScript action code.tmd +1 -0
- package/load_plugins.js +8 -2
- package/locales/en.json +34 -1
- package/markup/admin.js +22 -18
- package/package.json +10 -9
- package/public/dayjslocales/af.js +1 -0
- package/public/dayjslocales/am.js +1 -0
- package/public/dayjslocales/ar-dz.js +1 -0
- package/public/dayjslocales/ar-iq.js +1 -0
- package/public/dayjslocales/ar-kw.js +1 -0
- package/public/dayjslocales/ar-ly.js +1 -0
- package/public/dayjslocales/ar-ma.js +1 -0
- package/public/dayjslocales/ar-sa.js +1 -0
- package/public/dayjslocales/ar-tn.js +1 -0
- package/public/dayjslocales/ar.js +1 -0
- package/public/dayjslocales/az.js +1 -0
- package/public/dayjslocales/be.js +1 -0
- package/public/dayjslocales/bg.js +1 -0
- package/public/dayjslocales/bi.js +1 -0
- package/public/dayjslocales/bm.js +1 -0
- package/public/dayjslocales/bn-bd.js +1 -0
- package/public/dayjslocales/bn.js +1 -0
- package/public/dayjslocales/bo.js +1 -0
- package/public/dayjslocales/br.js +1 -0
- package/public/dayjslocales/bs.js +1 -0
- package/public/dayjslocales/ca.js +1 -0
- package/public/dayjslocales/cs.js +1 -0
- package/public/dayjslocales/cv.js +1 -0
- package/public/dayjslocales/cy.js +1 -0
- package/public/dayjslocales/da.js +1 -0
- package/public/dayjslocales/de-at.js +1 -0
- package/public/dayjslocales/de-ch.js +1 -0
- package/public/dayjslocales/de.js +1 -0
- package/public/dayjslocales/dv.js +1 -0
- package/public/dayjslocales/el.js +1 -0
- package/public/dayjslocales/en-au.js +1 -0
- package/public/dayjslocales/en-ca.js +1 -0
- package/public/dayjslocales/en-gb.js +1 -0
- package/public/dayjslocales/en-ie.js +1 -0
- package/public/dayjslocales/en-il.js +1 -0
- package/public/dayjslocales/en-in.js +1 -0
- package/public/dayjslocales/en-nz.js +1 -0
- package/public/dayjslocales/en-sg.js +1 -0
- package/public/dayjslocales/en-tt.js +1 -0
- package/public/dayjslocales/en.js +1 -0
- package/public/dayjslocales/eo.js +1 -0
- package/public/dayjslocales/es-do.js +1 -0
- package/public/dayjslocales/es-mx.js +1 -0
- package/public/dayjslocales/es-pr.js +1 -0
- package/public/dayjslocales/es-us.js +1 -0
- package/public/dayjslocales/es.js +1 -0
- package/public/dayjslocales/et.js +1 -0
- package/public/dayjslocales/eu.js +1 -0
- package/public/dayjslocales/fa.js +1 -0
- package/public/dayjslocales/fi.js +1 -0
- package/public/dayjslocales/fo.js +1 -0
- package/public/dayjslocales/fr-ca.js +1 -0
- package/public/dayjslocales/fr-ch.js +1 -0
- package/public/dayjslocales/fr.js +1 -0
- package/public/dayjslocales/fy.js +1 -0
- package/public/dayjslocales/ga.js +1 -0
- package/public/dayjslocales/gd.js +1 -0
- package/public/dayjslocales/gl.js +1 -0
- package/public/dayjslocales/gom-latn.js +1 -0
- package/public/dayjslocales/gu.js +1 -0
- package/public/dayjslocales/he.js +1 -0
- package/public/dayjslocales/hi.js +1 -0
- package/public/dayjslocales/hr.js +1 -0
- package/public/dayjslocales/ht.js +1 -0
- package/public/dayjslocales/hu.js +1 -0
- package/public/dayjslocales/hy-am.js +1 -0
- package/public/dayjslocales/id.js +1 -0
- package/public/dayjslocales/is.js +1 -0
- package/public/dayjslocales/it-ch.js +1 -0
- package/public/dayjslocales/it.js +1 -0
- package/public/dayjslocales/ja.js +1 -0
- package/public/dayjslocales/jv.js +1 -0
- package/public/dayjslocales/ka.js +1 -0
- package/public/dayjslocales/kk.js +1 -0
- package/public/dayjslocales/km.js +1 -0
- package/public/dayjslocales/kn.js +1 -0
- package/public/dayjslocales/ko.js +1 -0
- package/public/dayjslocales/ku.js +1 -0
- package/public/dayjslocales/ky.js +1 -0
- package/public/dayjslocales/lb.js +1 -0
- package/public/dayjslocales/lo.js +1 -0
- package/public/dayjslocales/lt.js +1 -0
- package/public/dayjslocales/lv.js +1 -0
- package/public/dayjslocales/me.js +1 -0
- package/public/dayjslocales/mi.js +1 -0
- package/public/dayjslocales/mk.js +1 -0
- package/public/dayjslocales/ml.js +1 -0
- package/public/dayjslocales/mn.js +1 -0
- package/public/dayjslocales/mr.js +1 -0
- package/public/dayjslocales/ms-my.js +1 -0
- package/public/dayjslocales/ms.js +1 -0
- package/public/dayjslocales/mt.js +1 -0
- package/public/dayjslocales/my.js +1 -0
- package/public/dayjslocales/nb.js +1 -0
- package/public/dayjslocales/ne.js +1 -0
- package/public/dayjslocales/nl-be.js +1 -0
- package/public/dayjslocales/nl.js +1 -0
- package/public/dayjslocales/nn.js +1 -0
- package/public/dayjslocales/oc-lnc.js +1 -0
- package/public/dayjslocales/pa-in.js +1 -0
- package/public/dayjslocales/pl.js +1 -0
- package/public/dayjslocales/pt-br.js +1 -0
- package/public/dayjslocales/pt.js +1 -0
- package/public/dayjslocales/rn.js +1 -0
- package/public/dayjslocales/ro.js +1 -0
- package/public/dayjslocales/ru.js +1 -0
- package/public/dayjslocales/rw.js +1 -0
- package/public/dayjslocales/sd.js +1 -0
- package/public/dayjslocales/se.js +1 -0
- package/public/dayjslocales/si.js +1 -0
- package/public/dayjslocales/sk.js +1 -0
- package/public/dayjslocales/sl.js +1 -0
- package/public/dayjslocales/sq.js +1 -0
- package/public/dayjslocales/sr-cyrl.js +1 -0
- package/public/dayjslocales/sr.js +1 -0
- package/public/dayjslocales/ss.js +1 -0
- package/public/dayjslocales/sv-fi.js +1 -0
- package/public/dayjslocales/sv.js +1 -0
- package/public/dayjslocales/sw.js +1 -0
- package/public/dayjslocales/ta.js +1 -0
- package/public/dayjslocales/te.js +1 -0
- package/public/dayjslocales/tet.js +1 -0
- package/public/dayjslocales/tg.js +1 -0
- package/public/dayjslocales/th.js +1 -0
- package/public/dayjslocales/tk.js +1 -0
- package/public/dayjslocales/tl-ph.js +1 -0
- package/public/dayjslocales/tlh.js +1 -0
- package/public/dayjslocales/tr.js +1 -0
- package/public/dayjslocales/tzl.js +1 -0
- package/public/dayjslocales/tzm-latn.js +1 -0
- package/public/dayjslocales/tzm.js +1 -0
- package/public/dayjslocales/ug-cn.js +1 -0
- package/public/dayjslocales/uk.js +1 -0
- package/public/dayjslocales/ur.js +1 -0
- package/public/dayjslocales/uz-latn.js +1 -0
- package/public/dayjslocales/uz.js +1 -0
- package/public/dayjslocales/vi.js +1 -0
- package/public/dayjslocales/x-pseudo.js +1 -0
- package/public/dayjslocales/yo.js +1 -0
- package/public/dayjslocales/zh-cn.js +1 -0
- package/public/dayjslocales/zh-hk.js +1 -0
- package/public/dayjslocales/zh-tw.js +1 -0
- package/public/dayjslocales/zh.js +1 -0
- package/public/gridedit.js +2 -2
- package/public/log_viewer_utils.js +156 -0
- package/public/saltcorn-builder.css +62 -3
- package/public/saltcorn-common.js +8 -0
- package/public/saltcorn.js +30 -9
- package/public/tabulator_bootstrap5.min.css +1 -0
- package/restart_watcher.js +1 -0
- package/routes/actions.js +175 -18
- package/routes/admin.js +77 -5
- package/routes/common_lists.js +344 -152
- package/routes/fields.js +29 -5
- package/routes/files.js +3 -1
- package/routes/homepage.js +2 -1
- package/routes/list.js +5 -0
- package/routes/page.js +30 -13
- package/routes/page_groupedit.js +104 -83
- package/routes/pageedit.js +23 -7
- package/routes/tables.js +56 -6
- package/routes/tag_entries.js +18 -5
- package/routes/tags.js +65 -12
- package/routes/utils.js +23 -2
- package/routes/view.js +21 -2
- package/routes/viewedit.js +70 -4
- package/serve.js +177 -10
- package/tests/admin.test.js +17 -11
- package/tests/page_group.test.js +1 -0
- package/tests/table.test.js +1 -5
- package/tests/view.test.js +115 -15
- package/tests/viewedit.test.js +52 -29
- package/wrapper.js +11 -3
- package/public/relation_helpers.js +0 -351
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/list.js
CHANGED
|
@@ -161,6 +161,11 @@ const typeToGridType = (t, field) => {
|
|
|
161
161
|
jsgField.formatterParams = {
|
|
162
162
|
inputFormat: "iso",
|
|
163
163
|
};
|
|
164
|
+
|
|
165
|
+
if (field.attributes?.day_only) {
|
|
166
|
+
jsgField.editorParams = { dayOnly: true };
|
|
167
|
+
jsgField.formatter = "__isoDateFormatter";
|
|
168
|
+
}
|
|
164
169
|
} else if (t.name === "Color") {
|
|
165
170
|
jsgField.editor = "__colorEditor";
|
|
166
171
|
jsgField.formatter = "__colorFormatter";
|
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
|
@@ -21,6 +21,8 @@ const { getViews, traverseSync } = require("@saltcorn/data/models/layout");
|
|
|
21
21
|
const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
|
|
22
22
|
const db = require("@saltcorn/data/db");
|
|
23
23
|
const { getPageList, getPageGroupList } = require("./common_lists");
|
|
24
|
+
const TagEntry = require("@saltcorn/data/models/tag_entry");
|
|
25
|
+
const Tag = require("@saltcorn/data/models/tag");
|
|
24
26
|
|
|
25
27
|
const {
|
|
26
28
|
isAdmin,
|
|
@@ -184,7 +186,7 @@ const pageBuilderData = async (req, context) => {
|
|
|
184
186
|
for (const view of views) {
|
|
185
187
|
fixed_state_fields[view.name] = [];
|
|
186
188
|
const table = Table.findOne(view.table_id || view.exttable_name);
|
|
187
|
-
|
|
189
|
+
if (table) view.table_name = table.name;
|
|
188
190
|
const fs = await view.get_state_fields();
|
|
189
191
|
for (const frec of fs) {
|
|
190
192
|
const f = new Field(frec);
|
|
@@ -217,9 +219,10 @@ const pageBuilderData = async (req, context) => {
|
|
|
217
219
|
}
|
|
218
220
|
}
|
|
219
221
|
}
|
|
222
|
+
|
|
220
223
|
//console.log(fixed_state_fields.ListTasks);
|
|
221
224
|
return {
|
|
222
|
-
views,
|
|
225
|
+
views: views.map((v) => v.select_option),
|
|
223
226
|
images,
|
|
224
227
|
pages,
|
|
225
228
|
page_groups,
|
|
@@ -293,7 +296,18 @@ router.get(
|
|
|
293
296
|
"/",
|
|
294
297
|
isAdmin,
|
|
295
298
|
error_catcher(async (req, res) => {
|
|
296
|
-
const
|
|
299
|
+
const pageq = {};
|
|
300
|
+
let filterOnTag;
|
|
301
|
+
|
|
302
|
+
if (req.query._tag) {
|
|
303
|
+
const tagEntries = await TagEntry.find({
|
|
304
|
+
tag_id: +req.query._tag,
|
|
305
|
+
not: { page_id: null },
|
|
306
|
+
});
|
|
307
|
+
pageq.id = { in: tagEntries.map((te) => te.page_id).filter(Boolean) };
|
|
308
|
+
filterOnTag = await Tag.findOne({ id: +req.query._tag });
|
|
309
|
+
}
|
|
310
|
+
const pages = await Page.find(pageq, { orderBy: "name", nocase: true });
|
|
297
311
|
const pageGroups = await PageGroup.find(
|
|
298
312
|
{},
|
|
299
313
|
{ orderBy: "name", nocase: true }
|
|
@@ -311,7 +325,7 @@ router.get(
|
|
|
311
325
|
title: req.__("Your pages"),
|
|
312
326
|
class: "mt-0",
|
|
313
327
|
contents: div(
|
|
314
|
-
getPageList(pages, roles, req),
|
|
328
|
+
await getPageList(pages, roles, req, { filterOnTag }),
|
|
315
329
|
a(
|
|
316
330
|
{
|
|
317
331
|
href: `/pageedit/new`,
|
|
@@ -370,7 +384,7 @@ const wrap = (contents, noCard, req, page) => ({
|
|
|
370
384
|
crumbs: [
|
|
371
385
|
{ text: req.__("Pages"), href: "/pageedit" },
|
|
372
386
|
page
|
|
373
|
-
? { href: `/page/${page.name}`, text: page.name }
|
|
387
|
+
? { href: `/page/${encodeURIComponent(page.name)}`, text: page.name }
|
|
374
388
|
: { text: req.__("New") },
|
|
375
389
|
],
|
|
376
390
|
},
|
|
@@ -677,9 +691,11 @@ router.post(
|
|
|
677
691
|
|
|
678
692
|
if (id && req.body.layout) {
|
|
679
693
|
await Page.update(+id, { layout: req.body.layout });
|
|
680
|
-
res.json({
|
|
694
|
+
res.json({
|
|
695
|
+
success: "ok",
|
|
696
|
+
});
|
|
681
697
|
} else {
|
|
682
|
-
res.json({ error: "
|
|
698
|
+
res.json({ error: req.__("Unable to save: No page or no layout") });
|
|
683
699
|
}
|
|
684
700
|
})
|
|
685
701
|
);
|
package/routes/tables.js
CHANGED
|
@@ -13,6 +13,7 @@ const View = require("@saltcorn/data/models/view");
|
|
|
13
13
|
const User = require("@saltcorn/data/models/user");
|
|
14
14
|
const Model = require("@saltcorn/data/models/model");
|
|
15
15
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
16
|
+
const TagEntry = require("@saltcorn/data/models/tag_entry");
|
|
16
17
|
const {
|
|
17
18
|
mkTable,
|
|
18
19
|
renderForm,
|
|
@@ -59,10 +60,12 @@ const { tablesList, viewsList } = require("./common_lists");
|
|
|
59
60
|
const {
|
|
60
61
|
InvalidConfiguration,
|
|
61
62
|
removeAllWhiteSpace,
|
|
63
|
+
comparingCaseInsensitive,
|
|
62
64
|
} = require("@saltcorn/data/utils");
|
|
63
65
|
const { EOL } = require("os");
|
|
64
66
|
|
|
65
67
|
const path = require("path");
|
|
68
|
+
const Tag = require("@saltcorn/data/models/tag");
|
|
66
69
|
/**
|
|
67
70
|
* @type {object}
|
|
68
71
|
* @const
|
|
@@ -714,6 +717,7 @@ router.get(
|
|
|
714
717
|
...new Set(child_relations.map(({ table }) => table.name)),
|
|
715
718
|
];
|
|
716
719
|
const triggers = table.id ? Trigger.find({ table_id: table.id }) : [];
|
|
720
|
+
triggers.sort(comparingCaseInsensitive("name"));
|
|
717
721
|
let fieldCard;
|
|
718
722
|
if (fields.length === 0) {
|
|
719
723
|
fieldCard = [
|
|
@@ -785,7 +789,16 @@ router.get(
|
|
|
785
789
|
triggers.length
|
|
786
790
|
? req.__("Table triggers: ") +
|
|
787
791
|
triggers
|
|
788
|
-
.map((t) =>
|
|
792
|
+
.map((t) =>
|
|
793
|
+
link(
|
|
794
|
+
`/actions/configure/${
|
|
795
|
+
t.id
|
|
796
|
+
}?on_done_redirect=${encodeURIComponent(
|
|
797
|
+
`table/${table.name}`
|
|
798
|
+
)}`,
|
|
799
|
+
t.name
|
|
800
|
+
)
|
|
801
|
+
)
|
|
789
802
|
.join(", ") +
|
|
790
803
|
"<br>"
|
|
791
804
|
: "",
|
|
@@ -819,6 +832,7 @@ router.get(
|
|
|
819
832
|
}
|
|
820
833
|
viewCard = {
|
|
821
834
|
type: "card",
|
|
835
|
+
id: "table-views",
|
|
822
836
|
title: req.__("Views of this table"),
|
|
823
837
|
contents:
|
|
824
838
|
viewCardContents +
|
|
@@ -1160,6 +1174,32 @@ router.post(
|
|
|
1160
1174
|
res.redirect(`/table`);
|
|
1161
1175
|
return;
|
|
1162
1176
|
}
|
|
1177
|
+
const views = await View.find(
|
|
1178
|
+
t.id ? { table_id: t.id } : { exttable_name: t.name }
|
|
1179
|
+
);
|
|
1180
|
+
if (views.length) {
|
|
1181
|
+
req.flash(
|
|
1182
|
+
"error",
|
|
1183
|
+
`${text(t.name)} has views. Delete these first: <a href="/table/${
|
|
1184
|
+
t.name
|
|
1185
|
+
}#table-views">Views for ${text(t.name)}</a>`
|
|
1186
|
+
);
|
|
1187
|
+
res.redirect(`/table`);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (t.id) {
|
|
1191
|
+
const triggers = await Trigger.find({ table_id: t.id });
|
|
1192
|
+
if (triggers.length) {
|
|
1193
|
+
req.flash(
|
|
1194
|
+
"error",
|
|
1195
|
+
`${text(
|
|
1196
|
+
t.name
|
|
1197
|
+
)} has triggers. Delete these first: <a href="/actions">Trigger list</a>`
|
|
1198
|
+
);
|
|
1199
|
+
res.redirect(`/table`);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1163
1203
|
try {
|
|
1164
1204
|
await t.delete();
|
|
1165
1205
|
req.flash("success", req.__(`Table %s deleted`, t.name));
|
|
@@ -1211,13 +1251,23 @@ router.get(
|
|
|
1211
1251
|
"/",
|
|
1212
1252
|
isAdmin,
|
|
1213
1253
|
error_catcher(async (req, res) => {
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1254
|
+
const tblq = {};
|
|
1255
|
+
let filterOnTag;
|
|
1256
|
+
if (req.query._tag) {
|
|
1257
|
+
const tagEntries = await TagEntry.find({
|
|
1258
|
+
tag_id: +req.query._tag,
|
|
1259
|
+
not: { table_id: null },
|
|
1260
|
+
});
|
|
1261
|
+
tblq.id = { in: tagEntries.map((te) => te.table_id).filter(Boolean) };
|
|
1262
|
+
filterOnTag = await Tag.findOne({ id: +req.query._tag });
|
|
1263
|
+
}
|
|
1264
|
+
const rows = await Table.find_with_external(tblq, {
|
|
1265
|
+
orderBy: "name",
|
|
1266
|
+
nocase: true,
|
|
1267
|
+
});
|
|
1218
1268
|
const roles = await User.get_roles();
|
|
1219
1269
|
const getRole = (rid) => roles.find((r) => r.id === rid).role;
|
|
1220
|
-
const mainCard = await tablesList(rows, req);
|
|
1270
|
+
const mainCard = await tablesList(rows, req, { filterOnTag });
|
|
1221
1271
|
const createCard = div(
|
|
1222
1272
|
a(
|
|
1223
1273
|
{ href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
|
package/routes/tag_entries.js
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
select,
|
|
6
6
|
option,
|
|
7
7
|
label,
|
|
8
|
+
text,
|
|
8
9
|
} = require("@saltcorn/markup/tags");
|
|
9
10
|
|
|
10
11
|
const Tag = require("@saltcorn/data/models/tag");
|
|
@@ -29,7 +30,12 @@ const buildFields = (entryType, formOptions, req) => {
|
|
|
29
30
|
div(
|
|
30
31
|
{ class: "col-sm-10" },
|
|
31
32
|
select(
|
|
32
|
-
{
|
|
33
|
+
{
|
|
34
|
+
name: "ids",
|
|
35
|
+
class: "form-control form-select",
|
|
36
|
+
multiple: true,
|
|
37
|
+
size: 20,
|
|
38
|
+
},
|
|
33
39
|
list.map((entry) => {
|
|
34
40
|
return option({ value: entry.id, label: entry.name });
|
|
35
41
|
})
|
|
@@ -94,15 +100,21 @@ router.get(
|
|
|
94
100
|
isAdmin,
|
|
95
101
|
error_catcher(async (req, res) => {
|
|
96
102
|
const { entry_type, tag_id } = req.params;
|
|
97
|
-
|
|
103
|
+
const tag = await Tag.findOne({ id: tag_id });
|
|
104
|
+
|
|
105
|
+
res.sendWrap(req.__("Add %s to tag %s", entry_type, tag.name), {
|
|
98
106
|
above: [
|
|
99
107
|
{
|
|
100
108
|
type: "breadcrumbs",
|
|
101
|
-
crumbs: [
|
|
109
|
+
crumbs: [
|
|
110
|
+
{ text: req.__(`Tags`), href: "/tag" },
|
|
111
|
+
{ text: tag.name, href: `/tag/${tag.id}` },
|
|
112
|
+
{ text: req.__(`Add %s`, text(entry_type)) },
|
|
113
|
+
],
|
|
102
114
|
},
|
|
103
115
|
{
|
|
104
116
|
type: "card",
|
|
105
|
-
title: req.__(`Add entries to tag
|
|
117
|
+
title: req.__(`Add entries to tag %s`, tag.name),
|
|
106
118
|
contents: buildForm(
|
|
107
119
|
entry_type,
|
|
108
120
|
tag_id,
|
|
@@ -148,9 +160,10 @@ router.post(
|
|
|
148
160
|
req.flash("error", req.__("Please select at least one item"));
|
|
149
161
|
return res.redirect(`/tag-entries/add/${entry_type}/${tag_id}`);
|
|
150
162
|
}
|
|
163
|
+
const ids_array = Array.isArray(ids) ? ids : [ids];
|
|
151
164
|
const fieldName = idField(entry_type);
|
|
152
165
|
const tag = await Tag.findOne({ id: tag_id });
|
|
153
|
-
for (const id of
|
|
166
|
+
for (const id of ids_array) {
|
|
154
167
|
await tag.addEntry({ [fieldName]: id });
|
|
155
168
|
}
|
|
156
169
|
res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
|