@saltcorn/server 0.9.4-beta.8 → 0.9.4-beta.9
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/package.json +8 -8
- package/public/saltcorn.js +3 -0
- package/routes/actions.js +17 -2
- package/routes/common_lists.js +304 -135
- package/routes/pageedit.js +19 -4
- package/routes/tables.js +17 -5
- package/routes/tag_entries.js +12 -4
- package/routes/tags.js +61 -12
- package/routes/view.js +7 -0
- package/routes/viewedit.js +37 -3
- package/tests/view.test.js +115 -15
- package/wrapper.js +0 -1
- package/public/relation_helpers.js +0 -351
package/locales/en.json
CHANGED
|
@@ -1354,5 +1354,11 @@
|
|
|
1354
1354
|
"Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>": "Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>",
|
|
1355
1355
|
"For search engines. Some view patterns accept interpolations.": "For search engines. Some view patterns accept interpolations.",
|
|
1356
1356
|
"Files cache TTL (minutes)": "Files cache TTL (minutes)",
|
|
1357
|
-
"Cache-control max-age for files.": "Cache-control max-age for files."
|
|
1358
|
-
|
|
1357
|
+
"Cache-control max-age for files.": "Cache-control max-age for files.",
|
|
1358
|
+
"Popup min width": "Popup min width",
|
|
1359
|
+
"Add %s to tag %s": "Add %s to tag %s",
|
|
1360
|
+
"Add entries to tag %s": "Add entries to tag %s",
|
|
1361
|
+
"Tag not found": "Tag not found",
|
|
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
|
+
}
|
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.9",
|
|
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.9",
|
|
11
|
+
"@saltcorn/builder": "0.9.4-beta.9",
|
|
12
|
+
"@saltcorn/data": "0.9.4-beta.9",
|
|
13
|
+
"@saltcorn/admin-models": "0.9.4-beta.9",
|
|
14
|
+
"@saltcorn/filemanager": "0.9.4-beta.9",
|
|
15
|
+
"@saltcorn/markup": "0.9.4-beta.9",
|
|
16
|
+
"@saltcorn/sbadmin2": "0.9.4-beta.9",
|
|
17
17
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
18
18
|
"@socket.io/sticky": "^1.0.1",
|
|
19
19
|
"adm-zip": "0.5.10",
|
package/public/saltcorn.js
CHANGED
|
@@ -376,6 +376,7 @@ function ajax_modal(url, opts = {}) {
|
|
|
376
376
|
success: function (res, textStatus, request) {
|
|
377
377
|
var title = request.getResponseHeader("Page-Title");
|
|
378
378
|
var width = request.getResponseHeader("SaltcornModalWidth");
|
|
379
|
+
var minwidth = request.getResponseHeader("SaltcornModalMinWidth");
|
|
379
380
|
var saveIndicate = !!request.getResponseHeader(
|
|
380
381
|
"SaltcornModalSaveIndicator"
|
|
381
382
|
);
|
|
@@ -386,6 +387,8 @@ function ajax_modal(url, opts = {}) {
|
|
|
386
387
|
else $(".sc-modal-linkout").hide();
|
|
387
388
|
if (width) $(".modal-dialog").css("max-width", width);
|
|
388
389
|
else $(".modal-dialog").css("max-width", "");
|
|
390
|
+
if (minwidth) $(".modal-dialog").css("min-width", minwidth);
|
|
391
|
+
else $(".modal-dialog").css("min-width", "");
|
|
389
392
|
if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
|
|
390
393
|
$("#scmodal .modal-body").html(res);
|
|
391
394
|
$("#scmodal").prop("data-modal-state", url);
|
package/routes/actions.js
CHANGED
|
@@ -14,6 +14,8 @@ const {
|
|
|
14
14
|
const { getState } = require("@saltcorn/data/db/state");
|
|
15
15
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
16
16
|
const { getTriggerList } = require("./common_lists");
|
|
17
|
+
const TagEntry = require("@saltcorn/data/models/tag_entry");
|
|
18
|
+
const Tag = require("@saltcorn/data/models/tag");
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* @type {object}
|
|
@@ -77,7 +79,20 @@ router.get(
|
|
|
77
79
|
"/",
|
|
78
80
|
isAdmin,
|
|
79
81
|
error_catcher(async (req, res) => {
|
|
80
|
-
|
|
82
|
+
let triggers = await Trigger.findAllWithTableName();
|
|
83
|
+
let filterOnTag;
|
|
84
|
+
|
|
85
|
+
if (req.query._tag) {
|
|
86
|
+
const tagEntries = await TagEntry.find({
|
|
87
|
+
tag_id: +req.query._tag,
|
|
88
|
+
not: { trigger_id: null },
|
|
89
|
+
});
|
|
90
|
+
const tagged_trigger_ids = new Set(
|
|
91
|
+
tagEntries.map((te) => te.trigger_id).filter(Boolean)
|
|
92
|
+
);
|
|
93
|
+
triggers = triggers.filter((t) => tagged_trigger_ids.has(t.id));
|
|
94
|
+
filterOnTag = await Tag.findOne({ id: +req.query._tag });
|
|
95
|
+
}
|
|
81
96
|
const actions = await getActions();
|
|
82
97
|
send_events_page({
|
|
83
98
|
res,
|
|
@@ -89,7 +104,7 @@ router.get(
|
|
|
89
104
|
type: "card",
|
|
90
105
|
title: req.__("Triggers"),
|
|
91
106
|
contents: div(
|
|
92
|
-
getTriggerList(triggers, req),
|
|
107
|
+
await getTriggerList(triggers, req, { filterOnTag }),
|
|
93
108
|
link("/actions/new", req.__("Add trigger"))
|
|
94
109
|
),
|
|
95
110
|
},
|
package/routes/common_lists.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const User = require("@saltcorn/data/models/user");
|
|
2
2
|
const Table = require("@saltcorn/data/models/table");
|
|
3
|
+
const Tag = require("@saltcorn/data/models/tag");
|
|
4
|
+
const TagEntry = require("@saltcorn/data/models/tag_entry");
|
|
3
5
|
const { editRoleForm } = require("../markup/forms.js");
|
|
4
6
|
const {
|
|
5
7
|
mkTable,
|
|
@@ -7,16 +9,16 @@ const {
|
|
|
7
9
|
post_delete_btn,
|
|
8
10
|
settingsDropdown,
|
|
9
11
|
post_dropdown_item,
|
|
12
|
+
badge,
|
|
10
13
|
} = require("@saltcorn/markup");
|
|
11
14
|
const { get_base_url } = require("./utils.js");
|
|
12
|
-
const { h4, p, div, a, i, text } = require("@saltcorn/markup/tags");
|
|
15
|
+
const { h4, p, div, a, i, text, span, nbsp } = require("@saltcorn/markup/tags");
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* @param {string} col
|
|
16
19
|
* @param {string} lbl
|
|
17
20
|
* @returns {string}
|
|
18
21
|
*/
|
|
19
|
-
const badge = (col, lbl) => `<span class="badge bg-${col}">${lbl}</span> `;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Table badges to show in System Table list views
|
|
@@ -42,57 +44,90 @@ const valIfSet = (check, value) => (check ? value : "");
|
|
|
42
44
|
const listClass = (tagId, showList) =>
|
|
43
45
|
valIfSet(tagId, `collapse ${valIfSet(showList, "show")}`);
|
|
44
46
|
|
|
45
|
-
const tablesList = async (
|
|
47
|
+
const tablesList = async (
|
|
48
|
+
tables,
|
|
49
|
+
req,
|
|
50
|
+
{ tagId, domId, showList, filterOnTag } = {}
|
|
51
|
+
) => {
|
|
46
52
|
const roles = await User.get_roles();
|
|
47
53
|
const getRole = (rid) => roles.find((r) => r.id === rid)?.role || "?";
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`/tag-entries/remove/tables/${r.id}/${tagId}`,
|
|
79
|
-
req,
|
|
80
|
-
`${r.name} from this tag`
|
|
81
|
-
),
|
|
54
|
+
const tags = await Tag.find();
|
|
55
|
+
const tag_entries = await TagEntry.find({
|
|
56
|
+
not: { table_id: null },
|
|
57
|
+
});
|
|
58
|
+
const tagsById = {};
|
|
59
|
+
tags.forEach((t) => (tagsById[t.id] = t));
|
|
60
|
+
|
|
61
|
+
const tagBadges = (table) => {
|
|
62
|
+
const myTags = tag_entries.filter((te) => te.table_id === table.id);
|
|
63
|
+
return myTags
|
|
64
|
+
.map((te) => tagBadge(tagsById[te.tag_id], "tables"))
|
|
65
|
+
.join(nbsp);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
mkTable(
|
|
70
|
+
[
|
|
71
|
+
{
|
|
72
|
+
label: req.__("Name"),
|
|
73
|
+
key: (r) => link(`/table/${r.id || r.name}`, text(r.name)),
|
|
74
|
+
},
|
|
75
|
+
...(tagId
|
|
76
|
+
? []
|
|
77
|
+
: [
|
|
78
|
+
{
|
|
79
|
+
label: tagsDropdown(
|
|
80
|
+
tags,
|
|
81
|
+
filterOnTag ? `Tag:${filterOnTag.name}` : undefined
|
|
82
|
+
),
|
|
83
|
+
key: (r) => tagBadges(r),
|
|
82
84
|
},
|
|
83
|
-
|
|
84
|
-
tables,
|
|
85
|
+
]),
|
|
85
86
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
label: "",
|
|
88
|
+
key: (r) => tableBadges(r, req),
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
label: req.__("Access Read/Write"),
|
|
93
|
+
key: (t) =>
|
|
94
|
+
t.external
|
|
95
|
+
? `${getRole(t.min_role_read)} (read only)`
|
|
96
|
+
: `${getRole(t.min_role_read)}/${getRole(t.min_role_write)}`,
|
|
97
|
+
},
|
|
98
|
+
!tagId
|
|
99
|
+
? {
|
|
100
|
+
label: req.__("Delete"),
|
|
101
|
+
key: (r) =>
|
|
102
|
+
r.name === "users" || r.external
|
|
103
|
+
? ""
|
|
104
|
+
: post_delete_btn(`/table/delete/${r.id}`, req, r.name),
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
label: req.__("Remove From Tag"),
|
|
108
|
+
key: (r) =>
|
|
109
|
+
post_delete_btn(
|
|
110
|
+
`/tag-entries/remove/tables/${r.id}/${tagId}`,
|
|
111
|
+
req,
|
|
112
|
+
`${r.name} from this tag`
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
tables,
|
|
117
|
+
{
|
|
118
|
+
hover: true,
|
|
119
|
+
tableClass: listClass(tagId, showList),
|
|
120
|
+
tableId: domId,
|
|
121
|
+
}
|
|
122
|
+
) +
|
|
123
|
+
(tables.length == 0 && !filterOnTag
|
|
124
|
+
? div(
|
|
125
|
+
{ class: listClass(tagId, showList), id: domId },
|
|
126
|
+
h4(req.__("No tables defined")),
|
|
127
|
+
p(req.__("Tables hold collections of similar data"))
|
|
128
|
+
)
|
|
129
|
+
: "")
|
|
130
|
+
);
|
|
96
131
|
};
|
|
97
132
|
|
|
98
133
|
/**
|
|
@@ -177,100 +212,176 @@ const setTableRefs = async (views) => {
|
|
|
177
212
|
return views;
|
|
178
213
|
};
|
|
179
214
|
|
|
215
|
+
const tagBadge = (tag, type) =>
|
|
216
|
+
a(
|
|
217
|
+
{
|
|
218
|
+
href: `/tag/${tag.id}?show_list=${type}`,
|
|
219
|
+
class: "badge bg-secondary",
|
|
220
|
+
},
|
|
221
|
+
tag.name
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const tagsDropdown = (tags, altHeader) =>
|
|
225
|
+
div(
|
|
226
|
+
{ class: "dropdown" },
|
|
227
|
+
div(
|
|
228
|
+
{
|
|
229
|
+
class: "link-style",
|
|
230
|
+
"data-boundary": "viewport",
|
|
231
|
+
type: "button",
|
|
232
|
+
id: "tagsselector",
|
|
233
|
+
"data-bs-toggle": "dropdown",
|
|
234
|
+
"aria-haspopup": "true",
|
|
235
|
+
"aria-expanded": "false",
|
|
236
|
+
},
|
|
237
|
+
altHeader || "Tags",
|
|
238
|
+
i({ class: "ms-1 fas fa-caret-down" })
|
|
239
|
+
),
|
|
240
|
+
div(
|
|
241
|
+
{
|
|
242
|
+
class: "dropdown-menu",
|
|
243
|
+
"aria-labelledby": "tagsselector",
|
|
244
|
+
},
|
|
245
|
+
a(
|
|
246
|
+
{
|
|
247
|
+
class: "dropdown-item",
|
|
248
|
+
// TODO check url why view for page, what do we need for page group
|
|
249
|
+
href: `javascript:unset_state_field('_tag', this)`,
|
|
250
|
+
},
|
|
251
|
+
"All tags"
|
|
252
|
+
),
|
|
253
|
+
tags.map((tag) =>
|
|
254
|
+
a(
|
|
255
|
+
{
|
|
256
|
+
class: "dropdown-item",
|
|
257
|
+
// TODO check url why view for page, what do we need for page group
|
|
258
|
+
href: `javascript:set_state_field('_tag', ${tag.id}, this)`,
|
|
259
|
+
},
|
|
260
|
+
tag.name
|
|
261
|
+
)
|
|
262
|
+
),
|
|
263
|
+
a(
|
|
264
|
+
{
|
|
265
|
+
class: "dropdown-item",
|
|
266
|
+
// TODO check url why view for page, what do we need for page group
|
|
267
|
+
href: `tag`,
|
|
268
|
+
},
|
|
269
|
+
"Manage tags..."
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
);
|
|
273
|
+
|
|
180
274
|
const viewsList = async (
|
|
181
275
|
views,
|
|
182
276
|
req,
|
|
183
|
-
{ tagId, domId, showList, on_done_redirect, notable } = {}
|
|
277
|
+
{ tagId, domId, showList, on_done_redirect, notable, filterOnTag } = {}
|
|
184
278
|
) => {
|
|
185
279
|
const roles = await User.get_roles();
|
|
186
280
|
const on_done_redirect_str = on_done_redirect
|
|
187
281
|
? `?on_done_redirect=${on_done_redirect}`
|
|
188
282
|
: "";
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
key: (r) => link(`/table/${r.table}`, r.table),
|
|
223
|
-
sortlink: !tagId
|
|
224
|
-
? `set_state_field('_sortby', 'table', this)`
|
|
225
|
-
: undefined,
|
|
226
|
-
},
|
|
227
|
-
]),
|
|
228
|
-
{
|
|
229
|
-
label: req.__("Role to access"),
|
|
230
|
-
key: (row) =>
|
|
231
|
-
row.id
|
|
232
|
-
? editViewRoleForm(row, roles, req, on_done_redirect_str)
|
|
233
|
-
: "admin",
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
label: "",
|
|
237
|
-
key: (r) =>
|
|
238
|
-
r.id && r.viewtemplateObj?.configuration_workflow
|
|
239
|
-
? link(
|
|
240
|
-
`/viewedit/config/${encodeURIComponent(
|
|
241
|
-
r.name
|
|
242
|
-
)}${on_done_redirect_str}`,
|
|
243
|
-
req.__("Configure")
|
|
244
|
-
)
|
|
245
|
-
: "",
|
|
246
|
-
},
|
|
247
|
-
!tagId
|
|
248
|
-
? {
|
|
249
|
-
label: "",
|
|
250
|
-
key: (r) => view_dropdown(r, req, on_done_redirect_str),
|
|
251
|
-
}
|
|
252
|
-
: {
|
|
253
|
-
label: req.__("Remove From Tag"),
|
|
254
|
-
key: (r) =>
|
|
255
|
-
post_delete_btn(
|
|
256
|
-
`/tag-entries/remove/views/${r.id}/${tagId}`,
|
|
257
|
-
req,
|
|
258
|
-
`${r.name} from this tag`
|
|
259
|
-
),
|
|
283
|
+
const tags = await Tag.find();
|
|
284
|
+
const tag_entries = await TagEntry.find({
|
|
285
|
+
not: { view_id: null },
|
|
286
|
+
});
|
|
287
|
+
const tagsById = {};
|
|
288
|
+
tags.forEach((t) => (tagsById[t.id] = t));
|
|
289
|
+
|
|
290
|
+
const tagBadges = (view) => {
|
|
291
|
+
const myTags = tag_entries.filter((te) => te.view_id === view.id);
|
|
292
|
+
return myTags
|
|
293
|
+
.map((te) => tagBadge(tagsById[te.tag_id], "views"))
|
|
294
|
+
.join(nbsp);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
mkTable(
|
|
299
|
+
[
|
|
300
|
+
{
|
|
301
|
+
label: req.__("Name"),
|
|
302
|
+
key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
|
|
303
|
+
sortlink: !tagId
|
|
304
|
+
? `set_state_field('_sortby', 'name', this)`
|
|
305
|
+
: undefined,
|
|
306
|
+
},
|
|
307
|
+
...(tagId
|
|
308
|
+
? []
|
|
309
|
+
: [
|
|
310
|
+
{
|
|
311
|
+
label: tagsDropdown(
|
|
312
|
+
tags,
|
|
313
|
+
filterOnTag ? `Tag:${filterOnTag.name}` : undefined
|
|
314
|
+
),
|
|
315
|
+
key: (r) => tagBadges(r),
|
|
260
316
|
},
|
|
261
|
-
|
|
262
|
-
views,
|
|
317
|
+
]),
|
|
263
318
|
{
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
319
|
+
label: req.__("Pattern"),
|
|
320
|
+
key: "viewtemplate",
|
|
321
|
+
sortlink: !tagId
|
|
322
|
+
? `set_state_field('_sortby', 'viewtemplate', this)`
|
|
323
|
+
: undefined,
|
|
324
|
+
},
|
|
325
|
+
...(notable
|
|
326
|
+
? []
|
|
327
|
+
: [
|
|
328
|
+
{
|
|
329
|
+
label: req.__("Table"),
|
|
330
|
+
key: (r) => link(`/table/${r.table}`, r.table),
|
|
331
|
+
sortlink: !tagId
|
|
332
|
+
? `set_state_field('_sortby', 'table', this)`
|
|
333
|
+
: undefined,
|
|
334
|
+
},
|
|
335
|
+
]),
|
|
336
|
+
{
|
|
337
|
+
label: req.__("Role to access"),
|
|
338
|
+
key: (row) =>
|
|
339
|
+
row.id
|
|
340
|
+
? editViewRoleForm(row, roles, req, on_done_redirect_str)
|
|
341
|
+
: "admin",
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
label: "",
|
|
345
|
+
key: (r) =>
|
|
346
|
+
r.id && r.viewtemplateObj?.configuration_workflow
|
|
347
|
+
? link(
|
|
348
|
+
`/viewedit/config/${encodeURIComponent(
|
|
349
|
+
r.name
|
|
350
|
+
)}${on_done_redirect_str}`,
|
|
351
|
+
req.__("Configure")
|
|
352
|
+
)
|
|
353
|
+
: "",
|
|
354
|
+
},
|
|
355
|
+
!tagId
|
|
356
|
+
? {
|
|
357
|
+
label: "",
|
|
358
|
+
key: (r) => view_dropdown(r, req, on_done_redirect_str),
|
|
359
|
+
}
|
|
360
|
+
: {
|
|
361
|
+
label: req.__("Remove From Tag"),
|
|
362
|
+
key: (r) =>
|
|
363
|
+
post_delete_btn(
|
|
364
|
+
`/tag-entries/remove/views/${r.id}/${tagId}`,
|
|
365
|
+
req,
|
|
366
|
+
`${r.name} from this tag`
|
|
367
|
+
),
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
views,
|
|
371
|
+
{
|
|
372
|
+
hover: true,
|
|
373
|
+
tableClass: listClass(tagId, showList),
|
|
374
|
+
tableId: domId,
|
|
375
|
+
}
|
|
376
|
+
) +
|
|
377
|
+
(views.length == 0 && !filterOnTag
|
|
378
|
+
? div(
|
|
379
|
+
{ class: listClass(tagId, showList), id: domId },
|
|
380
|
+
h4(req.__("No views defined")),
|
|
381
|
+
p(req.__("Views define how table rows are displayed to the user"))
|
|
382
|
+
)
|
|
383
|
+
: "")
|
|
384
|
+
);
|
|
274
385
|
};
|
|
275
386
|
|
|
276
387
|
const page_group_dropdown = (page_group, req) =>
|
|
@@ -371,13 +482,42 @@ const editPageRoleForm = (page, roles, req, isGroup) =>
|
|
|
371
482
|
* @param {object} req
|
|
372
483
|
* @returns {div}
|
|
373
484
|
*/
|
|
374
|
-
const getPageList = (
|
|
485
|
+
const getPageList = async (
|
|
486
|
+
rows,
|
|
487
|
+
roles,
|
|
488
|
+
req,
|
|
489
|
+
{ tagId, domId, showList, filterOnTag } = {}
|
|
490
|
+
) => {
|
|
491
|
+
const tags = await Tag.find();
|
|
492
|
+
const tag_entries = await TagEntry.find({
|
|
493
|
+
not: { page_id: null },
|
|
494
|
+
});
|
|
495
|
+
const tagsById = {};
|
|
496
|
+
tags.forEach((t) => (tagsById[t.id] = t));
|
|
497
|
+
|
|
498
|
+
const tagBadges = (page) => {
|
|
499
|
+
const myTags = tag_entries.filter((te) => te.page_id === page.id);
|
|
500
|
+
return myTags
|
|
501
|
+
.map((te) => tagBadge(tagsById[te.tag_id], "pages"))
|
|
502
|
+
.join(nbsp);
|
|
503
|
+
};
|
|
375
504
|
return mkTable(
|
|
376
505
|
[
|
|
377
506
|
{
|
|
378
507
|
label: req.__("Name"),
|
|
379
508
|
key: (r) => link(`/page/${r.name}`, r.name),
|
|
380
509
|
},
|
|
510
|
+
...(tagId
|
|
511
|
+
? []
|
|
512
|
+
: [
|
|
513
|
+
{
|
|
514
|
+
label: tagsDropdown(
|
|
515
|
+
tags,
|
|
516
|
+
filterOnTag ? `Tag:${filterOnTag.name}` : undefined
|
|
517
|
+
),
|
|
518
|
+
key: (r) => tagBadges(r),
|
|
519
|
+
},
|
|
520
|
+
]),
|
|
381
521
|
{
|
|
382
522
|
label: req.__("Role to access"),
|
|
383
523
|
key: (row) => editPageRoleForm(row, roles, req),
|
|
@@ -442,11 +582,40 @@ const getPageGroupList = (rows, roles, req) => {
|
|
|
442
582
|
);
|
|
443
583
|
};
|
|
444
584
|
|
|
445
|
-
const getTriggerList = (
|
|
585
|
+
const getTriggerList = async (
|
|
586
|
+
triggers,
|
|
587
|
+
req,
|
|
588
|
+
{ tagId, domId, showList, filterOnTag } = {}
|
|
589
|
+
) => {
|
|
446
590
|
const base_url = get_base_url(req);
|
|
591
|
+
const tags = await Tag.find();
|
|
592
|
+
|
|
593
|
+
const tag_entries = await TagEntry.find({
|
|
594
|
+
not: { trigger_id: null },
|
|
595
|
+
});
|
|
596
|
+
const tagsById = {};
|
|
597
|
+
tags.forEach((t) => (tagsById[t.id] = t));
|
|
598
|
+
|
|
599
|
+
const tagBadges = (trigger) => {
|
|
600
|
+
const myTags = tag_entries.filter((te) => te.trigger_id === trigger.id);
|
|
601
|
+
return myTags
|
|
602
|
+
.map((te) => tagBadge(tagsById[te.tag_id], "triggers"))
|
|
603
|
+
.join(nbsp);
|
|
604
|
+
};
|
|
447
605
|
return mkTable(
|
|
448
606
|
[
|
|
449
607
|
{ label: req.__("Name"), key: "name" },
|
|
608
|
+
...(tagId
|
|
609
|
+
? []
|
|
610
|
+
: [
|
|
611
|
+
{
|
|
612
|
+
label: tagsDropdown(
|
|
613
|
+
tags,
|
|
614
|
+
filterOnTag ? `Tag:${filterOnTag.name}` : undefined
|
|
615
|
+
),
|
|
616
|
+
key: (r) => tagBadges(r),
|
|
617
|
+
},
|
|
618
|
+
]),
|
|
450
619
|
{ label: req.__("Action"), key: "action" },
|
|
451
620
|
{
|
|
452
621
|
label: req.__("Table or Channel"),
|
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,
|
|
@@ -293,7 +295,18 @@ router.get(
|
|
|
293
295
|
"/",
|
|
294
296
|
isAdmin,
|
|
295
297
|
error_catcher(async (req, res) => {
|
|
296
|
-
const
|
|
298
|
+
const pageq = {};
|
|
299
|
+
let filterOnTag;
|
|
300
|
+
|
|
301
|
+
if (req.query._tag) {
|
|
302
|
+
const tagEntries = await TagEntry.find({
|
|
303
|
+
tag_id: +req.query._tag,
|
|
304
|
+
not: { page_id: null },
|
|
305
|
+
});
|
|
306
|
+
pageq.id = { in: tagEntries.map((te) => te.page_id).filter(Boolean) };
|
|
307
|
+
filterOnTag = await Tag.findOne({ id: +req.query._tag });
|
|
308
|
+
}
|
|
309
|
+
const pages = await Page.find(pageq, { orderBy: "name", nocase: true });
|
|
297
310
|
const pageGroups = await PageGroup.find(
|
|
298
311
|
{},
|
|
299
312
|
{ orderBy: "name", nocase: true }
|
|
@@ -311,7 +324,7 @@ router.get(
|
|
|
311
324
|
title: req.__("Your pages"),
|
|
312
325
|
class: "mt-0",
|
|
313
326
|
contents: div(
|
|
314
|
-
getPageList(pages, roles, req),
|
|
327
|
+
await getPageList(pages, roles, req, { filterOnTag }),
|
|
315
328
|
a(
|
|
316
329
|
{
|
|
317
330
|
href: `/pageedit/new`,
|
|
@@ -677,9 +690,11 @@ router.post(
|
|
|
677
690
|
|
|
678
691
|
if (id && req.body.layout) {
|
|
679
692
|
await Page.update(+id, { layout: req.body.layout });
|
|
680
|
-
res.json({
|
|
693
|
+
res.json({
|
|
694
|
+
success: "ok",
|
|
695
|
+
});
|
|
681
696
|
} else {
|
|
682
|
-
res.json({ error: "
|
|
697
|
+
res.json({ error: req.__("Unable to save: No page or no layout") });
|
|
683
698
|
}
|
|
684
699
|
})
|
|
685
700
|
);
|