@saltcorn/server 0.9.4-beta.8 → 0.9.4
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 +16 -4
- package/auth/testhelp.js +17 -1
- package/load_plugins.js +8 -2
- package/locales/en.json +29 -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 +43 -2
- package/public/saltcorn-common.js +39 -29
- package/public/saltcorn.js +29 -8
- package/public/tabulator_bootstrap5.min.css +1 -0
- package/restart_watcher.js +1 -0
- package/routes/actions.js +175 -18
- package/routes/admin.js +83 -9
- package/routes/common_lists.js +344 -152
- package/routes/fields.js +18 -3
- package/routes/homepage.js +2 -1
- package/routes/page.js +30 -13
- package/routes/page_groupedit.js +104 -83
- package/routes/pageedit.js +23 -7
- package/routes/tables.js +51 -5
- package/routes/tag_entries.js +18 -5
- package/routes/tags.js +65 -12
- package/routes/utils.js +23 -2
- package/routes/view.js +12 -1
- package/routes/viewedit.js +46 -3
- package/serve.js +177 -10
- package/tests/admin.test.js +17 -11
- package/tests/api.test.js +27 -0
- package/tests/fields.test.js +132 -5
- package/tests/help.test.js +37 -0
- package/tests/page_group.test.js +1 -0
- package/tests/plugins.test.js +0 -12
- package/tests/table.test.js +1 -5
- package/tests/view.test.js +127 -15
- package/tests/viewedit.test.js +52 -8
- package/wrapper.js +9 -2
- package/public/relation_helpers.js +0 -351
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,
|
|
@@ -64,6 +65,7 @@ const {
|
|
|
64
65
|
const { EOL } = require("os");
|
|
65
66
|
|
|
66
67
|
const path = require("path");
|
|
68
|
+
const Tag = require("@saltcorn/data/models/tag");
|
|
67
69
|
/**
|
|
68
70
|
* @type {object}
|
|
69
71
|
* @const
|
|
@@ -830,6 +832,7 @@ router.get(
|
|
|
830
832
|
}
|
|
831
833
|
viewCard = {
|
|
832
834
|
type: "card",
|
|
835
|
+
id: "table-views",
|
|
833
836
|
title: req.__("Views of this table"),
|
|
834
837
|
contents:
|
|
835
838
|
viewCardContents +
|
|
@@ -992,6 +995,13 @@ router.get(
|
|
|
992
995
|
req,
|
|
993
996
|
true
|
|
994
997
|
),
|
|
998
|
+
table.name !== "users" &&
|
|
999
|
+
post_dropdown_item(
|
|
1000
|
+
`/table/delete/${table.id}`,
|
|
1001
|
+
'<i class="fas fa-trash"></i> ' + req.__("Delete table"),
|
|
1002
|
+
req,
|
|
1003
|
+
true
|
|
1004
|
+
),
|
|
995
1005
|
])
|
|
996
1006
|
)
|
|
997
1007
|
);
|
|
@@ -1171,6 +1181,32 @@ router.post(
|
|
|
1171
1181
|
res.redirect(`/table`);
|
|
1172
1182
|
return;
|
|
1173
1183
|
}
|
|
1184
|
+
const views = await View.find(
|
|
1185
|
+
t.id ? { table_id: t.id } : { exttable_name: t.name }
|
|
1186
|
+
);
|
|
1187
|
+
if (views.length) {
|
|
1188
|
+
req.flash(
|
|
1189
|
+
"error",
|
|
1190
|
+
`${text(t.name)} has views. Delete these first: <a href="/table/${
|
|
1191
|
+
t.name
|
|
1192
|
+
}#table-views">Views for ${text(t.name)}</a>`
|
|
1193
|
+
);
|
|
1194
|
+
res.redirect(`/table`);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (t.id) {
|
|
1198
|
+
const triggers = await Trigger.find({ table_id: t.id });
|
|
1199
|
+
if (triggers.length) {
|
|
1200
|
+
req.flash(
|
|
1201
|
+
"error",
|
|
1202
|
+
`${text(
|
|
1203
|
+
t.name
|
|
1204
|
+
)} has triggers. Delete these first: <a href="/actions">Trigger list</a>`
|
|
1205
|
+
);
|
|
1206
|
+
res.redirect(`/table`);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1174
1210
|
try {
|
|
1175
1211
|
await t.delete();
|
|
1176
1212
|
req.flash("success", req.__(`Table %s deleted`, t.name));
|
|
@@ -1222,13 +1258,23 @@ router.get(
|
|
|
1222
1258
|
"/",
|
|
1223
1259
|
isAdmin,
|
|
1224
1260
|
error_catcher(async (req, res) => {
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1261
|
+
const tblq = {};
|
|
1262
|
+
let filterOnTag;
|
|
1263
|
+
if (req.query._tag) {
|
|
1264
|
+
const tagEntries = await TagEntry.find({
|
|
1265
|
+
tag_id: +req.query._tag,
|
|
1266
|
+
not: { table_id: null },
|
|
1267
|
+
});
|
|
1268
|
+
tblq.id = { in: tagEntries.map((te) => te.table_id).filter(Boolean) };
|
|
1269
|
+
filterOnTag = await Tag.findOne({ id: +req.query._tag });
|
|
1270
|
+
}
|
|
1271
|
+
const rows = await Table.find_with_external(tblq, {
|
|
1272
|
+
orderBy: "name",
|
|
1273
|
+
nocase: true,
|
|
1274
|
+
});
|
|
1229
1275
|
const roles = await User.get_roles();
|
|
1230
1276
|
const getRole = (rid) => roles.find((r) => r.id === rid).role;
|
|
1231
|
-
const mainCard = await tablesList(rows, req);
|
|
1277
|
+
const mainCard = await tablesList(rows, req, { filterOnTag });
|
|
1232
1278
|
const createCard = div(
|
|
1233
1279
|
a(
|
|
1234
1280
|
{ 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}`);
|