@saltcorn/server 0.9.6-beta.14 → 0.9.6-beta.15

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 CHANGED
@@ -1427,8 +1427,14 @@
1427
1427
  "xcodebuild": "xcodebuild",
1428
1428
  "Provisioning Profile": "Provisioning Profile",
1429
1429
  "Registry editor": "Registry editor",
1430
+ "Mobile HTML": "Mobile HTML",
1431
+ "HTML for the item in the bottom navigation bar. Currently, only supported by the metronic theme.": "HTML for the item in the bottom navigation bar. Currently, only supported by the metronic theme.",
1430
1432
  "A short name that will be in the page URL": "A short name that will be in the page URL",
1431
1433
  "A longer description that is not visible but appears in the page header and is indexed by search engines": "A longer description that is not visible but appears in the page header and is indexed by search engines",
1432
1434
  "User role required to access page": "User role required to access page",
1433
- "Example: <code>`/view/TheOtherView?id=${id}`</code>": "Example: <code>`/view/TheOtherView?id=${id}`</code>"
1434
- }
1435
+ "Example: <code>`/view/TheOtherView?id=${id}`</code>": "Example: <code>`/view/TheOtherView?id=${id}`</code>",
1436
+ "Older": "Older",
1437
+ "Newest": "Newest",
1438
+ "Delete all read": "Delete all read",
1439
+ "Trigger %s duplicated as %s": "Trigger %s duplicated as %s"
1440
+ }
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.6-beta.14",
3
+ "version": "0.9.6-beta.15",
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.6-beta.14",
11
- "@saltcorn/builder": "0.9.6-beta.14",
12
- "@saltcorn/data": "0.9.6-beta.14",
13
- "@saltcorn/admin-models": "0.9.6-beta.14",
14
- "@saltcorn/filemanager": "0.9.6-beta.14",
15
- "@saltcorn/markup": "0.9.6-beta.14",
16
- "@saltcorn/plugins-loader": "0.9.6-beta.14",
17
- "@saltcorn/sbadmin2": "0.9.6-beta.14",
10
+ "@saltcorn/base-plugin": "0.9.6-beta.15",
11
+ "@saltcorn/builder": "0.9.6-beta.15",
12
+ "@saltcorn/data": "0.9.6-beta.15",
13
+ "@saltcorn/admin-models": "0.9.6-beta.15",
14
+ "@saltcorn/filemanager": "0.9.6-beta.15",
15
+ "@saltcorn/markup": "0.9.6-beta.15",
16
+ "@saltcorn/plugins-loader": "0.9.6-beta.15",
17
+ "@saltcorn/sbadmin2": "0.9.6-beta.15",
18
18
  "@socket.io/cluster-adapter": "^0.2.1",
19
19
  "@socket.io/sticky": "^1.0.1",
20
20
  "adm-zip": "0.5.10",
@@ -8,6 +8,23 @@ jQuery.fn.swapWith = function (to) {
8
8
  });
9
9
  };
10
10
 
11
+ function monospace_block_click(e) {
12
+ let e1 = $(e).next("pre");
13
+ let mine = $(e).html();
14
+ $(e).html($(e1).html());
15
+ $(e1).html(mine);
16
+ }
17
+
18
+ function copy_monospace_block(e) {
19
+ let e1 = $(e).next("pre");
20
+ let e2 = $(e1).next("pre");
21
+ if (!e2.length) return navigator.clipboard.writeText($(el).text());
22
+ const e1t = e1.text();
23
+ const e2t = e2.text();
24
+ if (e1t.length > e2t.length) return navigator.clipboard.writeText(e1t);
25
+ else return navigator.clipboard.writeText(e2t);
26
+ }
27
+
11
28
  function setScreenInfoCookie() {
12
29
  document.cookie = `_sc_screen_info_=${JSON.stringify({
13
30
  width: window.screen.width,
@@ -837,11 +854,12 @@ function initialize_page() {
837
854
  const url = new URL(path);
838
855
  path = `${url.pathname}${url.search}`;
839
856
  }
840
- if (path.startsWith("/view/")) {
857
+ if (path.startsWith("/view/") || path.startsWith("/page/")) {
841
858
  const jThis = $(this);
842
859
  const skip = jThis.attr("skip-mobile-adjust");
843
860
  if (!skip) {
844
- jThis.attr("href", `javascript:execLink('${path}')`);
861
+ jThis.removeAttr("href");
862
+ jThis.attr("onclick", `execLink('${path}')`);
845
863
  if (jThis.find("i,img").length === 0 && !jThis.css("color")) {
846
864
  jThis.css(
847
865
  "color",
@@ -514,3 +514,25 @@ ul.katetree {
514
514
  ul.katetree details ul {
515
515
  list-style-type: none;
516
516
  }
517
+
518
+ pre.monospace-block {
519
+ font-family: monospace;
520
+ color: unset;
521
+ background: unset;
522
+ padding: unset;
523
+ border-radius: unset;
524
+ }
525
+
526
+ .d-none-prefer {
527
+ display: none;
528
+ }
529
+
530
+ button.monospace-copy-btn:has(+ pre.monospace-block:hover) {
531
+ display: block !important ;
532
+ }
533
+ button.monospace-copy-btn:hover {
534
+ display: block !important ;
535
+ }
536
+ button.monospace-copy-btn {
537
+ position: absolute;
538
+ }
@@ -23,6 +23,7 @@ const relevantPackages = [
23
23
  "saltcorn-admin-models",
24
24
  "saltcorn-markup",
25
25
  "saltcorn-sbadmin2",
26
+ "saltcorn-types",
26
27
  "server",
27
28
  "sqlite",
28
29
  "filemanager",
package/routes/actions.js CHANGED
@@ -887,3 +887,24 @@ router.get(
887
887
  }
888
888
  })
889
889
  );
890
+
891
+ /**
892
+ * @name post/clone/:id
893
+ * @function
894
+ * @memberof module:routes/actions~actionsRouter
895
+ * @function
896
+ */
897
+ router.post(
898
+ "/clone/:id",
899
+ isAdmin,
900
+ error_catcher(async (req, res) => {
901
+ const { id } = req.params;
902
+ const trig = await Trigger.findOne({ id });
903
+ const newtrig = await trig.clone();
904
+ req.flash(
905
+ "success",
906
+ req.__("Trigger %s duplicated as %s", trig.name, newtrig.name)
907
+ );
908
+ res.redirect(`/actions`);
909
+ })
910
+ );
@@ -304,6 +304,18 @@ const viewsList = async (
304
304
  ? `set_state_field('_sortby', 'name', this)`
305
305
  : undefined,
306
306
  },
307
+ {
308
+ label: "",
309
+ key: (r) =>
310
+ r.id && r.viewtemplateObj?.configuration_workflow
311
+ ? link(
312
+ `/viewedit/config/${encodeURIComponent(
313
+ r.name
314
+ )}${on_done_redirect_str}`,
315
+ req.__("Configure")
316
+ )
317
+ : "",
318
+ },
307
319
  ...(tagId
308
320
  ? []
309
321
  : [
@@ -340,18 +352,6 @@ const viewsList = async (
340
352
  ? editViewRoleForm(row, roles, req, on_done_redirect_str)
341
353
  : "admin",
342
354
  },
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
355
  !tagId
356
356
  ? {
357
357
  label: "",
@@ -428,13 +428,6 @@ const page_dropdown = (page, req) =>
428
428
  },
429
429
  '<i class="fas fa-running"></i>&nbsp;' + req.__("Run")
430
430
  ),
431
- a(
432
- {
433
- class: "dropdown-item",
434
- href: `/pageedit/edit-properties/${encodeURIComponent(page.name)}`,
435
- },
436
- '<i class="fas fa-edit"></i>&nbsp;' + req.__("Edit properties")
437
- ),
438
431
  post_dropdown_item(
439
432
  `/pageedit/add-to-menu/${page.id}`,
440
433
  '<i class="fas fa-bars"></i>&nbsp;' + req.__("Add to menu"),
@@ -507,6 +500,22 @@ const getPageList = async (
507
500
  label: req.__("Name"),
508
501
  key: (r) => link(`/page/${encodeURIComponent(r.name)}`, r.name),
509
502
  },
503
+ {
504
+ label: "",
505
+ key: (r) =>
506
+ link(
507
+ `/pageedit/edit/${encodeURIComponent(r.name)}`,
508
+ req.__("Configure")
509
+ ),
510
+ },
511
+ {
512
+ label: "",
513
+ key: (r) =>
514
+ link(
515
+ `/pageedit/edit-properties/${encodeURIComponent(r.name)}`,
516
+ req.__("Edit")
517
+ ),
518
+ },
510
519
  ...(tagId
511
520
  ? []
512
521
  : [
@@ -522,11 +531,7 @@ const getPageList = async (
522
531
  label: req.__("Role to access"),
523
532
  key: (row) => editPageRoleForm(row, roles, req),
524
533
  },
525
- {
526
- label: req.__("Edit"),
527
- key: (r) =>
528
- link(`/pageedit/edit/${encodeURIComponent(r.name)}`, req.__("Edit")),
529
- },
534
+
530
535
  !tagId
531
536
  ? {
532
537
  label: "",
@@ -600,6 +605,11 @@ const trigger_dropdown = (trigger, req, on_done_redirect_str = "") =>
600
605
  },
601
606
  '<i class="fas fa-undo-alt"></i>&nbsp;' + req.__("Restore")
602
607
  ),
608
+ post_dropdown_item(
609
+ `/actions/clone/${trigger.id}`,
610
+ '<i class="far fa-copy"></i>&nbsp;' + req.__("Duplicate"),
611
+ req
612
+ ),
603
613
  div({ class: "dropdown-divider" }),
604
614
 
605
615
  post_dropdown_item(
@@ -634,6 +644,14 @@ const getTriggerList = async (
634
644
  return mkTable(
635
645
  [
636
646
  { label: req.__("Name"), key: "name" },
647
+ {
648
+ label: req.__("Test run"),
649
+ key: (r) => link(`/actions/testrun/${r.id}`, req.__("Test run")),
650
+ },
651
+ {
652
+ label: req.__("Configure"),
653
+ key: (r) => link(`/actions/configure/${r.id}`, req.__("Configure")),
654
+ },
637
655
  ...(tagId
638
656
  ? []
639
657
  : [
@@ -667,14 +685,6 @@ const getTriggerList = async (
667
685
  ? a({ href: `/table/${r.table_name}` }, r.table_name)
668
686
  : r.channel,
669
687
  },
670
- {
671
- label: req.__("Test run"),
672
- key: (r) => link(`/actions/testrun/${r.id}`, req.__("Test run")),
673
- },
674
- {
675
- label: req.__("Configure"),
676
- key: (r) => link(`/actions/configure/${r.id}`, req.__("Configure")),
677
- },
678
688
  !tagId
679
689
  ? {
680
690
  label: "",
package/routes/menu.js CHANGED
@@ -258,6 +258,18 @@ const menuForm = async (req) => {
258
258
  type: "Bool",
259
259
  class: "item-menu",
260
260
  required: false,
261
+ default: false,
262
+ },
263
+ {
264
+ name: "mobile_item_html",
265
+ label: req.__("Mobile HTML"),
266
+ sublabel: req.__(
267
+ "HTML for the item in the bottom navigation bar. Currently, only supported by the metronic theme."
268
+ ),
269
+ type: "String",
270
+ class: "item-menu",
271
+ input_type: "textarea",
272
+ showIf: { disable_on_mobile: false, location: "Mobile Bottom" },
261
273
  },
262
274
  {
263
275
  name: "target_blank",
@@ -13,7 +13,7 @@ const { getState } = require("@saltcorn/data/db/state");
13
13
  const Form = require("@saltcorn/data/models/form");
14
14
  const File = require("@saltcorn/data/models/file");
15
15
  const User = require("@saltcorn/data/models/user");
16
- const { renderForm } = require("@saltcorn/markup");
16
+ const { renderForm, post_btn } = require("@saltcorn/markup");
17
17
 
18
18
  const router = new Router();
19
19
  module.exports = router;
@@ -31,22 +31,50 @@ router.get(
31
31
  "/",
32
32
  loggedIn,
33
33
  error_catcher(async (req, res) => {
34
- const nots = await Notification.find(
35
- { user_id: req.user.id },
36
- { orderBy: "id", orderDesc: true, limit: 20 }
37
- );
34
+ const { after } = req.query;
35
+ const where = { user_id: req.user.id };
36
+ if (after) where.id = { lt: after };
37
+ const nots = await Notification.find(where, {
38
+ orderBy: "id",
39
+ orderDesc: true,
40
+ limit: 20,
41
+ });
38
42
  await Notification.mark_as_read({
39
43
  id: { in: nots.filter((n) => !n.read).map((n) => n.id) },
40
44
  });
45
+ const form = notificationSettingsForm();
46
+ const user = await User.findOne({ id: req.user?.id });
47
+ form.values = { notify_email: user?._attributes?.notify_email };
41
48
  const notifyCards = nots.length
42
49
  ? nots.map((not) => ({
43
50
  type: "card",
44
51
  class: [!not.read && "unread-notify"],
52
+ id: `notify-${not.id}`,
45
53
  contents: [
46
54
  div(
47
55
  { class: "d-flex" },
48
56
  span({ class: "fw-bold" }, not.title),
49
- span({ class: "ms-2 text-muted" }, moment(not.created).fromNow())
57
+ span(
58
+ {
59
+ class: "ms-2 text-muted",
60
+ title: not.created.toLocaleString(req.getLocale()),
61
+ },
62
+ moment(not.created).fromNow()
63
+ ),
64
+ div(
65
+ { class: "ms-auto" },
66
+ post_btn(
67
+ `/notifications/delete/${not.id}`,
68
+ "",
69
+ req.csrfToken(),
70
+ {
71
+ icon: "fas fa-times-circle",
72
+ klass: "btn-link text-muted text-decoration-none p-0",
73
+ ajax: true,
74
+ onClick: `$('#notify-${not.id}').remove()`,
75
+ }
76
+ )
77
+ )
50
78
  ),
51
79
  not.body && p(not.body),
52
80
  not.link && a({ href: not.link }, "Link"),
@@ -58,6 +86,35 @@ router.get(
58
86
  contents: [h5(req.__("No notifications"))],
59
87
  },
60
88
  ];
89
+ const pageLinks = div(
90
+ { class: "d-flex mt-3 mb-3" },
91
+ nots.length == 20
92
+ ? div(
93
+ after &&
94
+ a(
95
+ { href: `/notifications`, class: "me-2" },
96
+ "&larr; " + req.__("Newest")
97
+ ),
98
+ a(
99
+ { href: `/notifications?after=${nots[19].id}` },
100
+ req.__("Older") + " &rarr;"
101
+ )
102
+ )
103
+ : div(),
104
+ nots.length > 0 &&
105
+ div(
106
+ { class: "ms-auto" },
107
+ post_btn(
108
+ `/notifications/delete/read`,
109
+ req.__("Delete all read"),
110
+ req.csrfToken(),
111
+ {
112
+ icon: "fas fa-trash",
113
+ klass: "btn-sm btn-danger",
114
+ }
115
+ )
116
+ )
117
+ );
61
118
  res.sendWrap(req.__("Notifications"), {
62
119
  above: [
63
120
  {
@@ -72,10 +129,10 @@ router.get(
72
129
  type: "card",
73
130
  contents: [
74
131
  req.__("Receive notifications by:"),
75
- renderForm(notificationSettingsForm(), req.csrfToken()),
132
+ renderForm(form, req.csrfToken()),
76
133
  ],
77
134
  },
78
- { above: notifyCards },
135
+ { above: [...notifyCards, pageLinks] },
79
136
  ],
80
137
  },
81
138
  ],
@@ -109,6 +166,23 @@ router.post(
109
166
  })
110
167
  );
111
168
 
169
+ router.post(
170
+ "/delete/:idlike",
171
+ loggedIn,
172
+ error_catcher(async (req, res) => {
173
+ const { idlike } = req.params;
174
+ if (idlike == "read") {
175
+ await Notification.deleteRead(req.user.id);
176
+ } else {
177
+ const id = +idlike;
178
+ const notif = await Notification.findOne({ id });
179
+ if (notif?.user_id == req.user?.id) await notif.delete();
180
+ }
181
+ if (req.xhr) res.json({ success: "ok" });
182
+ else res.redirect("/notifications");
183
+ })
184
+ );
185
+
112
186
  router.get(
113
187
  "/manifest.json",
114
188
  error_catcher(async (req, res) => {
package/routes/tables.js CHANGED
@@ -754,6 +754,14 @@ router.get(
754
754
  r.typename +
755
755
  span({ class: "badge bg-danger ms-1" }, "Unknown type"),
756
756
  },
757
+ ...(table.external
758
+ ? []
759
+ : [
760
+ {
761
+ label: req.__("Edit"),
762
+ key: (r) => link(`/field/${r.id}`, req.__("Edit")),
763
+ },
764
+ ]),
757
765
  {
758
766
  label: "",
759
767
  key: (r) => typeBadges(r, req),
@@ -763,14 +771,6 @@ router.get(
763
771
  key: (r) => attribBadges(r),
764
772
  },
765
773
  { label: req.__("Variable name"), key: (t) => code(t.name) },
766
- ...(table.external
767
- ? []
768
- : [
769
- {
770
- label: req.__("Edit"),
771
- key: (r) => link(`/field/${r.id}`, req.__("Edit")),
772
- },
773
- ]),
774
774
  ...(table.external || db.isSQLite
775
775
  ? []
776
776
  : [