@saltcorn/server 0.9.1-beta.9 → 0.9.2-rc.1

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/auth/routes.js CHANGED
@@ -1586,9 +1586,17 @@ router.post(
1586
1586
  */
1587
1587
  router.post(
1588
1588
  "/settings",
1589
+ setTenant,
1589
1590
  loggedIn,
1590
1591
  error_catcher(async (req, res) => {
1591
- const user = await User.findOne({ id: req.user.id });
1592
+ const user = await User.findOne({ id: req.user?.id });
1593
+ if (!user) {
1594
+ res.sendWrap(
1595
+ req.__("User settings") || "User settings",
1596
+ "Must be logged in"
1597
+ );
1598
+ return;
1599
+ }
1592
1600
  if (req.body.new_password && user.password) {
1593
1601
  const pwform = changPwForm(req);
1594
1602
 
@@ -1615,12 +1623,24 @@ router.post(
1615
1623
  if (user_settings_form) {
1616
1624
  const view = await View.findOne({ name: user_settings_form });
1617
1625
  if (view) {
1626
+ const fakeRes = {
1627
+ status() {},
1628
+ sendWrap() {},
1629
+ json() {},
1630
+ redirect() {},
1631
+ };
1618
1632
  await view.runPost({ id: user.id }, req.body, {
1619
1633
  req,
1620
- res,
1634
+ res: fakeRes,
1621
1635
  redirect: "/auth/settings",
1622
1636
  });
1623
- req.flash("success", req.__("User settings changed"));
1637
+ const u = await User.findForSession({ id: user.id });
1638
+ req.login(u.session_object, function (err) {
1639
+ if (err) req.flash("danger", err);
1640
+ else req.flash("success", req.__("User settings changed"));
1641
+
1642
+ res.redirect("/auth/settings");
1643
+ });
1624
1644
  }
1625
1645
  } else {
1626
1646
  res.redirect("/auth/settings");
@@ -68,4 +68,7 @@ variable name by a dollar sign (in pages and Show views). For instance if your U
68
68
  you can access the value of `foo` (here 1) as `$foo`. In Filter views, there is no
69
69
  underlying row and the purpose of the view is fundamentally to manipulate the state
70
70
  so the state in extra state formulae is accessed without dollar sign; in the
71
- previous example `foo` instead of `$foo`.
71
+ previous example `foo` instead of `$foo`.
72
+
73
+ Use `session_id` to refer to the current session ID string (unique for the user's session
74
+ whether the are logged in or not).
package/load_plugins.js CHANGED
@@ -29,6 +29,15 @@ const staticDependencies = {
29
29
  "@saltcorn/data/models/fieldrepeat": require("@saltcorn/data/models/fieldrepeat"),
30
30
  "@saltcorn/data/models/table": require("@saltcorn/data/models/table"),
31
31
  "@saltcorn/data/models/form": require("@saltcorn/data/models/form"),
32
+ "@saltcorn/data/models/discovery": require("@saltcorn/data/models/discovery"),
33
+ "@saltcorn/data/models/config": require("@saltcorn/data/models/config"),
34
+ "@saltcorn/data/models/library": require("@saltcorn/data/models/library"),
35
+ "@saltcorn/data/models/model": require("@saltcorn/data/models/model"),
36
+ "@saltcorn/data/models/model_instance": require("@saltcorn/data/models/model_instance"),
37
+ "@saltcorn/data/models/notification": require("@saltcorn/data/models/notification"),
38
+ "@saltcorn/data/models/role": require("@saltcorn/data/models/role"),
39
+ "@saltcorn/data/models/tag": require("@saltcorn/data/models/tag"),
40
+ "@saltcorn/data/models/tag_entry": require("@saltcorn/data/models/tag_entry"),
32
41
  "@saltcorn/data/models/view": require("@saltcorn/data/models/view"),
33
42
  "@saltcorn/data/models/page": require("@saltcorn/data/models/page"),
34
43
  "@saltcorn/data/models/file": require("@saltcorn/data/models/file"),
@@ -202,7 +211,7 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
202
211
  const { version, plugin_module, location } = await requirePlugin(
203
212
  plugin,
204
213
  force,
205
- manager,
214
+ manager
206
215
  );
207
216
 
208
217
  // install dependecies
package/locales/en.json CHANGED
@@ -1278,5 +1278,12 @@
1278
1278
  "HTML file to use as page content": "HTML file to use as page content",
1279
1279
  "Offline mode: cannot load file": "Offline mode: cannot load file",
1280
1280
  "None - use drag and drop builder": "None - use drag and drop builder",
1281
- "Do not pick or compare time": "Do not pick or compare time"
1281
+ "Do not pick or compare time": "Do not pick or compare time",
1282
+ "Installed theme": "Installed theme",
1283
+ "Theme for role": "Theme for role",
1284
+ "Set theme for each user role »": "Set theme for each user role »",
1285
+ "Available themes": "Available themes",
1286
+ "Install more themes »": "Install more themes »",
1287
+ "Configure action": "Configure action",
1288
+ "No changes detected, snapshot skipped": "No changes detected, snapshot skipped"
1282
1289
  }
package/markup/admin.js CHANGED
@@ -77,11 +77,14 @@ const add_edit_bar = ({
77
77
  req,
78
78
  viewtemplate,
79
79
  table,
80
+ view,
80
81
  cfgUrl,
81
82
  }) => {
83
+ if (req && req.headers.localizedstate)
84
+ return { above: [contents], noWrapTop: true };
82
85
  if (role > 1 && req && req.xhr) return { above: [contents] }; //make sure not put in card
83
86
  if (role > 1) return contents;
84
- if (req && req.headers.localizedstate) return { above: [contents] };
87
+
85
88
  let viewSpec = "";
86
89
  if (viewtemplate) viewSpec = viewtemplate;
87
90
  if (table) {
@@ -89,20 +92,26 @@ const add_edit_bar = ({
89
92
  if (tbl)
90
93
  viewSpec = `${viewSpec} on <a href="/table/${table}">${tbl.name}</a>`;
91
94
  }
95
+ const singleton = view?.viewtemplateObj?.singleton;
92
96
 
93
97
  const bar = div(
94
98
  { class: "alert alert-light d-print-none admin-edit-bar" },
95
99
  title,
96
100
  what && span({ class: "ms-1 me-2 badge bg-primary" }, what),
97
- a({ class: "ms-2", href: url }, "Edit&nbsp;", i({ class: "fas fa-edit" })),
98
- cfgUrl
101
+ !singleton &&
102
+ a(
103
+ { class: "ms-2", href: url },
104
+ "Edit&nbsp;",
105
+ i({ class: "fas fa-edit" })
106
+ ),
107
+ cfgUrl && !singleton
99
108
  ? a(
100
109
  { class: "ms-1 me-3", href: cfgUrl },
101
110
  "Configure&nbsp;",
102
111
  i({ class: "fas fa-cog" })
103
112
  )
104
113
  : "",
105
- viewSpec
114
+ !singleton && viewSpec
106
115
  );
107
116
 
108
117
  if (contents.above) {
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.1-beta.9",
3
+ "version": "0.9.2-rc.1",
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.1-beta.9",
11
- "@saltcorn/builder": "0.9.1-beta.9",
12
- "@saltcorn/data": "0.9.1-beta.9",
13
- "@saltcorn/admin-models": "0.9.1-beta.9",
14
- "@saltcorn/filemanager": "0.9.1-beta.9",
15
- "@saltcorn/markup": "0.9.1-beta.9",
16
- "@saltcorn/sbadmin2": "0.9.1-beta.9",
10
+ "@saltcorn/base-plugin": "0.9.2-rc.1",
11
+ "@saltcorn/builder": "0.9.2-rc.1",
12
+ "@saltcorn/data": "0.9.2-rc.1",
13
+ "@saltcorn/admin-models": "0.9.2-rc.1",
14
+ "@saltcorn/filemanager": "0.9.2-rc.1",
15
+ "@saltcorn/markup": "0.9.2-rc.1",
16
+ "@saltcorn/sbadmin2": "0.9.2-rc.1",
17
17
  "@socket.io/cluster-adapter": "^0.2.1",
18
18
  "@socket.io/sticky": "^1.0.1",
19
19
  "adm-zip": "0.5.10",
@@ -1 +1,3 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs=e()}(this,(function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",f="month",h="quarter",c="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return"["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return-t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,f),s=n-i<0,u=e.clone().add(r+(s?-1:1),f);return+(-(r+(n-i)/(s?i-u:u-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(t){return{M:f,y:c,w:o,d:a,D:d,h:u,m:s,s:i,ms:r,Q:h}[t]||String(t||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},g="en",D={};D[g]=M;var p=function(t){return t instanceof _},S=function t(e,n,r){var i;if(!e)return g;if("string"==typeof e){var s=e.toLowerCase();D[s]&&(i=s),n&&(D[s]=n,i=s);var u=e.split("-");if(!i&&u.length>1)return t(u[0])}else{var a=e.name;D[a]=e,i=a}return!r&&i&&(g=i),i||!r&&g},w=function(t,e){if(p(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},O=v;O.l=S,O.i=p,O.w=function(t,e){return w(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=S(t.locale,null,!0),this.parse(t)}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(O.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return O},m.isValid=function(){return!(this.$d.toString()===l)},m.isSame=function(t,e){var n=w(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return w(t)<this.startOf(e)},m.isBefore=function(t,e){return this.endOf(e)<w(t)},m.$g=function(t,e,n){return O.u(t)?this[e]:this.set(n,t)},m.unix=function(){return Math.floor(this.valueOf()/1e3)},m.valueOf=function(){return this.$d.getTime()},m.startOf=function(t,e){var n=this,r=!!O.u(e)||e,h=O.p(t),l=function(t,e){var i=O.w(n.$u?Date.UTC(n.$y,e,t):new Date(n.$y,e,t),n);return r?i:i.endOf(a)},$=function(t,e){return O.w(n.toDate()[t].apply(n.toDate("s"),(r?[0,0,0,0]:[23,59,59,999]).slice(e)),n)},y=this.$W,M=this.$M,m=this.$D,v="set"+(this.$u?"UTC":"");switch(h){case c:return r?l(1,0):l(31,11);case f:return r?l(1,M):l(0,M+1);case o:var g=this.$locale().weekStart||0,D=(y<g?y+7:y)-g;return l(r?m-D:m+(6-D),M);case a:case d:return $(v+"Hours",0);case u:return $(v+"Minutes",1);case s:return $(v+"Seconds",2);case i:return $(v+"Milliseconds",3);default:return this.clone()}},m.endOf=function(t){return this.startOf(t,!1)},m.$set=function(t,e){var n,o=O.p(t),h="set"+(this.$u?"UTC":""),l=(n={},n[a]=h+"Date",n[d]=h+"Date",n[f]=h+"Month",n[c]=h+"FullYear",n[u]=h+"Hours",n[s]=h+"Minutes",n[i]=h+"Seconds",n[r]=h+"Milliseconds",n)[o],$=o===a?this.$D+(e-this.$W):e;if(o===f||o===c){var y=this.clone().set(d,1);y.$d[l]($),y.init(),this.$d=y.set(d,Math.min(this.$D,y.daysInMonth())).$d}else l&&this.$d[l]($);return this.init(),this},m.set=function(t,e){return this.clone().$set(t,e)},m.get=function(t){return this[O.p(t)]()},m.add=function(r,h){var d,l=this;r=Number(r);var $=O.p(h),y=function(t){var e=w(l);return O.w(e.date(e.date()+Math.round(t*r)),l)};if($===f)return this.set(f,this.$M+r);if($===c)return this.set(c,this.$y+r);if($===a)return y(1);if($===o)return y(7);var M=(d={},d[s]=e,d[u]=n,d[i]=t,d)[$]||1,m=this.$d.getTime()+r*M;return O.w(m,this)},m.subtract=function(t,e){return this.add(-1*t,e)},m.format=function(t){var e=this,n=this.$locale();if(!this.isValid())return n.invalidDate||l;var r=t||"YYYY-MM-DDTHH:mm:ssZ",i=O.z(this),s=this.$H,u=this.$m,a=this.$M,o=n.weekdays,f=n.months,h=function(t,n,i,s){return t&&(t[n]||t(e,r))||i[n].slice(0,s)},c=function(t){return O.s(s%12||12,t,"0")},d=n.meridiem||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r},$={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:O.s(a+1,2,"0"),MMM:h(n.monthsShort,a,f,3),MMMM:h(f,a),D:this.$D,DD:O.s(this.$D,2,"0"),d:String(this.$W),dd:h(n.weekdaysMin,this.$W,o,2),ddd:h(n.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(s),HH:O.s(s,2,"0"),h:c(1),hh:c(2),a:d(s,u,!0),A:d(s,u,!1),m:String(u),mm:O.s(u,2,"0"),s:String(this.$s),ss:O.s(this.$s,2,"0"),SSS:O.s(this.$ms,3,"0"),Z:i};return r.replace(y,(function(t,e){return e||$[t]||i.replace(":","")}))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(r,d,l){var $,y=O.p(d),M=w(r),m=(M.utcOffset()-this.utcOffset())*e,v=this-M,g=O.m(this,M);return g=($={},$[c]=g/12,$[f]=g,$[h]=g/3,$[o]=(v-m)/6048e5,$[a]=(v-m)/864e5,$[u]=v/n,$[s]=v/e,$[i]=v/t,$)[y]||v,l?g:O.a(g)},m.daysInMonth=function(){return this.endOf(f).$D},m.$locale=function(){return D[this.$L]},m.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=S(t,e,!0);return r&&(n.$L=r),n},m.clone=function(){return O.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},M}(),T=_.prototype;return w.prototype=T,[["$ms",r],["$s",i],["$m",s],["$H",u],["$W",a],["$M",f],["$y",c],["$D",d]].forEach((function(t){T[t[1]]=function(e){return this.$g(e,t[0],t[1])}})),w.extend=function(t,e){return t.$i||(t(e,_,w),t.$i=!0),w},w.locale=S,w.isDayjs=p,w.unix=function(t){return w(1e3*t)},w.en=D[g],w.Ls=D,w.p={},w}));
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs=e()}(this,(function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",f="month",h="quarter",c="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return"["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return-t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,f),s=n-i<0,u=e.clone().add(r+(s?-1:1),f);return+(-(r+(n-i)/(s?i-u:u-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(t){return{M:f,y:c,w:o,d:a,D:d,h:u,m:s,s:i,ms:r,Q:h}[t]||String(t||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},g="en",D={};D[g]=M;var p=function(t){return t instanceof _},S=function t(e,n,r){var i;if(!e)return g;if("string"==typeof e){var s=e.toLowerCase();D[s]&&(i=s),n&&(D[s]=n,i=s);var u=e.split("-");if(!i&&u.length>1)return t(u[0])}else{var a=e.name;D[a]=e,i=a}return!r&&i&&(g=i),i||!r&&g},w=function(t,e){if(p(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},O=v;O.l=S,O.i=p,O.w=function(t,e){return w(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=S(t.locale,null,!0),this.parse(t)}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(O.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return O},m.isValid=function(){return!(this.$d.toString()===l)},m.isSame=function(t,e){var n=w(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return w(t)<this.startOf(e)},m.isBefore=function(t,e){return this.endOf(e)<w(t)},m.$g=function(t,e,n){return O.u(t)?this[e]:this.set(n,t)},m.unix=function(){return Math.floor(this.valueOf()/1e3)},m.valueOf=function(){return this.$d.getTime()},m.startOf=function(t,e){var n=this,r=!!O.u(e)||e,h=O.p(t),l=function(t,e){var i=O.w(n.$u?Date.UTC(n.$y,e,t):new Date(n.$y,e,t),n);return r?i:i.endOf(a)},$=function(t,e){return O.w(n.toDate()[t].apply(n.toDate("s"),(r?[0,0,0,0]:[23,59,59,999]).slice(e)),n)},y=this.$W,M=this.$M,m=this.$D,v="set"+(this.$u?"UTC":"");switch(h){case c:return r?l(1,0):l(31,11);case f:return r?l(1,M):l(0,M+1);case o:var g=this.$locale().weekStart||0,D=(y<g?y+7:y)-g;return l(r?m-D:m+(6-D),M);case a:case d:return $(v+"Hours",0);case u:return $(v+"Minutes",1);case s:return $(v+"Seconds",2);case i:return $(v+"Milliseconds",3);default:return this.clone()}},m.endOf=function(t){return this.startOf(t,!1)},m.$set=function(t,e){var n,o=O.p(t),h="set"+(this.$u?"UTC":""),l=(n={},n[a]=h+"Date",n[d]=h+"Date",n[f]=h+"Month",n[c]=h+"FullYear",n[u]=h+"Hours",n[s]=h+"Minutes",n[i]=h+"Seconds",n[r]=h+"Milliseconds",n)[o],$=o===a?this.$D+(e-this.$W):e;if(o===f||o===c){var y=this.clone().set(d,1);y.$d[l]($),y.init(),this.$d=y.set(d,Math.min(this.$D,y.daysInMonth())).$d}else l&&this.$d[l]($);return this.init(),this},m.set=function(t,e){return this.clone().$set(t,e)},m.get=function(t){return this[O.p(t)]()},m.add=function(r,h){var d,l=this;r=Number(r);var $=O.p(h),y=function(t){var e=w(l);return O.w(e.date(e.date()+Math.round(t*r)),l)};if($===f)return this.set(f,this.$M+r);if($===c)return this.set(c,this.$y+r);if($===a)return y(1);if($===o)return y(7);var M=(d={},d[s]=e,d[u]=n,d[i]=t,d)[$]||1,m=this.$d.getTime()+r*M;return O.w(m,this)},m.subtract=function(t,e){return this.add(-1*t,e)},m.format=function(t){var e=this,n=this.$locale();if(!this.isValid())return n.invalidDate||l;var r=t||"YYYY-MM-DDTHH:mm:ssZ",i=O.z(this),s=this.$H,u=this.$m,a=this.$M,o=n.weekdays,f=n.months,h=function(t,n,i,s){return t&&(t[n]||t(e,r))||i[n].slice(0,s)},c=function(t){return O.s(s%12||12,t,"0")},d=n.meridiem||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r},$={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:O.s(a+1,2,"0"),MMM:h(n.monthsShort,a,f,3),MMMM:h(f,a),D:this.$D,DD:O.s(this.$D,2,"0"),d:String(this.$W),dd:h(n.weekdaysMin,this.$W,o,2),ddd:h(n.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(s),HH:O.s(s,2,"0"),h:c(1),hh:c(2),a:d(s,u,!0),A:d(s,u,!1),m:String(u),mm:O.s(u,2,"0"),s:String(this.$s),ss:O.s(this.$s,2,"0"),SSS:O.s(this.$ms,3,"0"),Z:i};return r.replace(y,(function(t,e){return e||$[t]||i.replace(":","")}))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(r,d,l){var $,y=O.p(d),M=w(r),m=(M.utcOffset()-this.utcOffset())*e,v=this-M,g=O.m(this,M);return g=($={},$[c]=g/12,$[f]=g,$[h]=g/3,$[o]=(v-m)/6048e5,$[a]=(v-m)/864e5,$[u]=v/n,$[s]=v/e,$[i]=v/t,$)[y]||v,l?g:O.a(g)},m.daysInMonth=function(){return this.endOf(f).$D},m.$locale=function(){return D[this.$L]},m.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=S(t,e,!0);return r&&(n.$L=r),n},m.clone=function(){return O.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},M}(),T=_.prototype;return w.prototype=T,[["$ms",r],["$s",i],["$m",s],["$H",u],["$W",a],["$M",f],["$y",c],["$D",d]].forEach((function(t){T[t[1]]=function(e){return this.$g(e,t[0],t[1])}})),w.extend=function(t,e){return t.$i||(t(e,_,w),t.$i=!0),w},w.locale=S,w.isDayjs=p,w.unix=function(t){return w(1e3*t)},w.en=D[g],w.Ls=D,w.p={},w}));
2
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_plugin_advancedFormat=t()}(this,(function(){"use strict";return function(e,t){var r=t.prototype,n=r.format;r.format=function(e){var t=this,r=this.$locale();if(!this.isValid())return n.bind(this)(e);var s=this.$utils(),a=(e||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,(function(e){switch(e){case"Q":return Math.ceil((t.$M+1)/3);case"Do":return r.ordinal(t.$D);case"gggg":return t.weekYear();case"GGGG":return t.isoWeekYear();case"wo":return r.ordinal(t.week(),"W");case"w":case"ww":return s.s(t.week(),"w"===e?1:2,"0");case"W":case"WW":return s.s(t.isoWeek(),"W"===e?1:2,"0");case"k":case"kk":return s.s(String(0===t.$H?24:t.$H),"k"===e?1:2,"0");case"X":return Math.floor(t.$d.getTime()/1e3);case"x":return t.$d.getTime();case"z":return"["+t.offsetName()+"]";case"zzz":return"["+t.offsetName("long")+"]";default:return e}}));return n.bind(this)(a)}}}));
3
+ dayjs.extend(window.dayjs_plugin_advancedFormat)
@@ -34,6 +34,11 @@ function add_repeater(nm) {
34
34
  $(element).attr("name", newnm).attr("id", newid);
35
35
  });
36
36
  newe.appendTo($("div.repeats-" + nm));
37
+ newe.find("[data-on-cloned]").each(function (ix, element) {
38
+ (function (str) {
39
+ return eval(str);
40
+ }).call(element, $(element).attr("data-on-cloned"));
41
+ });
37
42
  }
38
43
 
39
44
  const _apply_showif_plugins = [];
@@ -160,7 +165,7 @@ function apply_showif() {
160
165
  e.prop("data-fetch-options-current-set", qs);
161
166
  const toAppend = [];
162
167
  if (!dynwhere.required)
163
- toAppend.push({ label: dynwhere.neutral_label || "" });
168
+ toAppend.push({ label: dynwhere.neutral_label || "", value: "" });
164
169
  let currentDataOption = undefined;
165
170
  const dataOptions = [];
166
171
  //console.log(success);
@@ -196,7 +201,7 @@ function apply_showif() {
196
201
  .map(
197
202
  ({ label, value, selected }) =>
198
203
  `<option${selected ? ` selected` : ""}${
199
- value ? ` value="${value}"` : ""
204
+ typeof value !== "undefined" ? ` value="${value}"` : ""
200
205
  }>${label || ""}</option>`
201
206
  )
202
207
  .join("")
@@ -416,6 +421,8 @@ function get_form_record(e_in, select_labels) {
416
421
  }
417
422
  function showIfFormulaInputs(e, fml) {
418
423
  const rec = get_form_record(e);
424
+ if (window._sc_loglevel > 4)
425
+ console.log(`show if fml ${fml} form_record`, rec);
419
426
  try {
420
427
  return new Function(
421
428
  "row",
@@ -423,7 +430,11 @@ function showIfFormulaInputs(e, fml) {
423
430
  "return " + fml
424
431
  )(rec, rec);
425
432
  } catch (e) {
426
- throw new Error(`Error in evaluating showIf formula ${fml}: ${e.message}`);
433
+ throw new Error(
434
+ `Error in evaluating showIf formula ${fml} with values ${JSON.stringify(
435
+ rec
436
+ )}: ${e.message}`
437
+ );
427
438
  }
428
439
  }
429
440
 
@@ -604,7 +615,7 @@ function initialize_page() {
604
615
  schema = JSON.parse(decodeURIComponent(schema));
605
616
  }
606
617
  if (type === "Date") {
607
- console.log("timeelsems", $(this).find("span.current time"));
618
+ //console.log("timeelsems", $(this).find("span.current time"));
608
619
  current =
609
620
  $(this).attr("data-inline-edit-current") ||
610
621
  $(this).find("span.current time").attr("datetime"); // ||
@@ -1043,7 +1054,10 @@ function notifyAlert(note, spin) {
1043
1054
  type = note.type;
1044
1055
  }
1045
1056
  const { id, html } = buildToast(txt, type, spin);
1046
- $("#toasts-area").append(html);
1057
+ let $modal = $("#scmodal");
1058
+ if ($modal.length && $modal.hasClass("show"))
1059
+ $("#modal-toasts-area").append(html);
1060
+ else $("#toasts-area").append(html);
1047
1061
  if (type === "success") {
1048
1062
  setTimeout(() => {
1049
1063
  $(`#${id}`).removeClass("show");
@@ -1074,28 +1088,41 @@ function restore_old_button(btnId) {
1074
1088
  btn.removeData("old-text");
1075
1089
  }
1076
1090
 
1077
- function common_done(res, viewname, isWeb = true) {
1078
- const handle = (element, fn) => {
1079
- if (Array.isArray(element)) for (const current of element) fn(current);
1080
- else fn(element);
1091
+ async function common_done(res, viewname, isWeb = true) {
1092
+ if (window._sc_loglevel > 4)
1093
+ console.log("ajax result directives", viewname, res);
1094
+ const handle = async (element, fn) => {
1095
+ if (Array.isArray(element))
1096
+ for (const current of element) await fn(current);
1097
+ else await fn(element);
1098
+ };
1099
+ const eval_it = async (s) => {
1100
+ if (res.row && res.field_names) {
1101
+ const f = new Function(`viewname, row, {${res.field_names}}`, s);
1102
+ const evalres = await f(viewname, res.row, res.row);
1103
+ if (evalres) await common_done(evalres, viewname, isWeb);
1104
+ } else {
1105
+ const f = new Function(`viewname`, s);
1106
+ const evalres = await f(viewname);
1107
+ if (evalres) await common_done(evalres, viewname, isWeb);
1108
+ }
1081
1109
  };
1082
- if (res.notify) handle(res.notify, notifyAlert);
1110
+ if (res.notify) await handle(res.notify, notifyAlert);
1083
1111
  if (res.error)
1084
- handle(res.error, (text) => notifyAlert({ type: "danger", text: text }));
1085
-
1086
- if (res.eval_js && res.row && res.field_names) {
1087
- const f = new Function(`row, {${res.field_names}}`, res.eval_js);
1088
- const evalres = f(res.row, res.row);
1089
- if (evalres) common_done(evalres, viewname, isWeb);
1090
- } else if (res.eval_js) {
1091
- handle(res.eval_js, eval);
1092
- }
1112
+ await handle(res.error, (text) =>
1113
+ notifyAlert({ type: "danger", text: text })
1114
+ );
1115
+ if (res.notify_success)
1116
+ await handle(res.notify_success, (text) =>
1117
+ notifyAlert({ type: "success", text: text })
1118
+ );
1119
+ if (res.eval_js) await handle(res.eval_js, eval_it);
1093
1120
 
1094
1121
  if (res.reload_page) {
1095
1122
  (isWeb ? location : parent).reload(); //TODO notify to cookie if reload or goto
1096
1123
  }
1097
1124
  if (res.download) {
1098
- handle(res.download, (download) => {
1125
+ await handle(res.download, (download) => {
1099
1126
  const dataurl = `data:${
1100
1127
  download.mimetype || "application/octet-stream"
1101
1128
  };base64,${download.blob}`;
@@ -487,3 +487,7 @@ table.help-md th:nth-child(2) {
487
487
  padding-left: 10px;
488
488
  padding-right: 10px;
489
489
  }
490
+
491
+ .progress-bar-radial {
492
+ color: unset;
493
+ }
@@ -135,6 +135,36 @@ $(function () {
135
135
  });
136
136
  });
137
137
 
138
+ function reload_embedded_view(viewname) {
139
+ if (window._sc_loglevel > 4)
140
+ console.log(
141
+ "reload_embedded_view",
142
+ viewname,
143
+ "found",
144
+ $(`[data-sc-embed-viewname="${viewname}"]`).length
145
+ );
146
+ $(`[data-sc-embed-viewname="${viewname}"]`).each(function () {
147
+ const $e = $(this);
148
+ const url =
149
+ $e.attr("data-sc-local-state") || $e.attr("data-sc-view-source");
150
+ if (!url) return;
151
+ const headers = {
152
+ pjaxpageload: "true",
153
+ localizedstate: "true", //no admin bar
154
+ };
155
+ $.ajax(url, {
156
+ headers,
157
+ success: function (res, textStatus, request) {
158
+ $e.html(res);
159
+ initialize_page();
160
+ },
161
+ error: function (res) {
162
+ notifyAlert({ type: "danger", text: res.responseText });
163
+ },
164
+ });
165
+ });
166
+ }
167
+
138
168
  function pjax_to(href, e) {
139
169
  let $modal = $("#scmodal");
140
170
  const inModal = $modal.length && $modal.hasClass("show");
@@ -257,6 +287,7 @@ function globalErrorCatcher(message, source, lineno, colno, error) {
257
287
  }
258
288
 
259
289
  function close_saltcorn_modal() {
290
+ $("#scmodal").off("hidden.bs.modal");
260
291
  var myModalEl = document.getElementById("scmodal");
261
292
  if (!myModalEl) return;
262
293
  var modal = bootstrap.Modal.getInstance(myModalEl);
@@ -281,6 +312,13 @@ function ensure_modal_exists_and_closed() {
281
312
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
282
313
  </button>
283
314
  </div>
315
+ <div
316
+ id="modal-toasts-area"
317
+ class="toast-container position-fixed top-0 end-0 p-2 "
318
+ style: "z-index: 7000;"
319
+ aria-live="polite"
320
+ aria-atomic="true">
321
+ </div>
284
322
  </div>
285
323
  <div class="modal-body">
286
324
  <p>Modal body text goes here.</p>
@@ -292,9 +330,9 @@ function ensure_modal_exists_and_closed() {
292
330
  // remove reload handler added by edit, for when we have popup link
293
331
  // in autosave edit in popup
294
332
  $("#scmodal").off("hidden.bs.modal");
295
-
296
333
  close_saltcorn_modal();
297
334
  }
335
+ $("#modal-toasts-area").empty();
298
336
  }
299
337
 
300
338
  function expand_thumbnail(img_id, filename) {
@@ -308,8 +346,10 @@ function expand_thumbnail(img_id, filename) {
308
346
 
309
347
  function ajax_modal(url, opts = {}) {
310
348
  ensure_modal_exists_and_closed();
349
+ $("#scmodal").removeClass("no-submit-reload");
350
+ $("#scmodal").attr("data-on-close-reload-view", opts.reload_view || null);
351
+
311
352
  if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
312
- else $("#scmodal").removeClass("no-submit-reload");
313
353
  $.ajax(url, {
314
354
  headers: {
315
355
  SaltcornModalRequest: "true",
@@ -364,6 +404,11 @@ function submitWithAjax(e) {
364
404
  notifyAlert({ type: "danger", text: res.responseJSON.error });
365
405
  });
366
406
  }
407
+ function saveAndContinueAsync(e) {
408
+ return new Promise((resolve, reject) => {
409
+ saveAndContinue(e, (x) => resolve(x));
410
+ });
411
+ }
367
412
 
368
413
  function saveAndContinue(e, k) {
369
414
  var form = $(e).closest("form");
@@ -390,6 +435,9 @@ function saveAndContinue(e, k) {
390
435
  if (res.notify) {
391
436
  notifyAlert(res.notify);
392
437
  }
438
+ if (res.notify_success) {
439
+ notifyAlert({ type: "success", text: res.notify });
440
+ }
393
441
  if (res.reload_page) {
394
442
  location.reload(); //TODO notify to cookie if reload or goto
395
443
  }
@@ -506,8 +554,15 @@ function ajaxSubmitForm(e) {
506
554
  contentType: false,
507
555
  success: function (res) {
508
556
  var no_reload = $("#scmodal").hasClass("no-submit-reload");
557
+ const on_close_reload_view = $("#scmodal").attr(
558
+ "data-on-close-reload-view"
559
+ );
509
560
  $("#scmodal").modal("hide");
510
- if (!no_reload) location.reload();
561
+ if (on_close_reload_view) {
562
+ const viewE = $(`[data-sc-embed-viewname="${on_close_reload_view}"]`);
563
+ if (viewE.length) reload_embedded_view(on_close_reload_view);
564
+ else location.reload();
565
+ } else if (!no_reload) location.reload();
511
566
  else common_done(res, form.attr("data-viewname"));
512
567
  },
513
568
  error: function (request) {
package/routes/actions.js CHANGED
@@ -400,7 +400,7 @@ router.get(
400
400
  { class: "ms-3" },
401
401
  trigger.action,
402
402
  table ? ` on ` + a({ href: `/table/${table.name}` }, table.name) : ""
403
- );
403
+ ) + a({href: `/actions/testrun/${id}`, class: "ms-2" }, req.__("Test run")+"&nbsp;&raquo;");
404
404
  if (!action) {
405
405
  req.flash("warning", req.__("Action not found"));
406
406
  res.redirect(`/actions/`);
@@ -478,7 +478,9 @@ router.get(
478
478
  res.redirect(`/actions/`);
479
479
  } else {
480
480
  // get configuration fields
481
- const cfgFields = await getActionConfigFields(action, table);
481
+ const cfgFields = await getActionConfigFields(action, table, {
482
+ mode: "trigger",
483
+ });
482
484
  // create form
483
485
  const form = new Form({
484
486
  action: addOnDoneRedirect(`/actions/configure/${id}`, req),
@@ -523,7 +525,9 @@ router.post(
523
525
  const table = trigger.table_id
524
526
  ? Table.findOne({ id: trigger.table_id })
525
527
  : null;
526
- const cfgFields = await getActionConfigFields(action, table);
528
+ const cfgFields = await getActionConfigFields(action, table, {
529
+ mode: "trigger",
530
+ });
527
531
  const form = new Form({
528
532
  action: `/actions/configure/${id}`,
529
533
  fields: cfgFields,
@@ -614,7 +618,7 @@ router.get(
614
618
  let table, row;
615
619
  if (trigger.table_id) {
616
620
  table = Table.findOne({ id: trigger.table_id });
617
- row = await table.getRow({});
621
+ row = await table.getRow({}, {orderBy: "RANDOM()"});
618
622
  }
619
623
  let runres;
620
624
 
@@ -661,6 +665,10 @@ router.get(
661
665
  { href: `/actions`, class: "mt-4 btn btn-primary me-1" },
662
666
  "&laquo;&nbsp;" + req.__("back to actions")
663
667
  ),
668
+ a(
669
+ { href: `/actions/configure/${trigger.id}`, class: "mt-4 btn btn-primary me-1" },
670
+ req.__("Configure action")
671
+ ),
664
672
  a(
665
673
  {
666
674
  href: `/actions/testrun/${id}`,
@@ -123,40 +123,46 @@ const view_dropdown = (view, req, on_done_redirect_str = "") =>
123
123
  },
124
124
  '<i class="fas fa-running"></i>&nbsp;' + req.__("Run")
125
125
  ),
126
- a(
127
- {
128
- class: "dropdown-item",
129
- href: `/viewedit/edit/${encodeURIComponent(
130
- view.name
131
- )}${on_done_redirect_str}`,
132
- },
133
- '<i class="fas fa-edit"></i>&nbsp;' + req.__("Edit")
134
- ),
126
+ view.id &&
127
+ a(
128
+ {
129
+ class: "dropdown-item",
130
+ href: `/viewedit/edit/${encodeURIComponent(
131
+ view.name
132
+ )}${on_done_redirect_str}`,
133
+ },
134
+ '<i class="fas fa-edit"></i>&nbsp;' + req.__("Edit")
135
+ ),
135
136
  post_dropdown_item(
136
- `/viewedit/add-to-menu/${view.id}${on_done_redirect_str}`,
137
+ `/viewedit/add-to-menu/${encodeURIComponent(
138
+ view.name
139
+ )}${on_done_redirect_str}`,
137
140
  '<i class="fas fa-bars"></i>&nbsp;' + req.__("Add to menu"),
138
141
  req
139
142
  ),
140
- post_dropdown_item(
141
- `/viewedit/clone/${view.id}${on_done_redirect_str}`,
142
- '<i class="far fa-copy"></i>&nbsp;' + req.__("Duplicate"),
143
- req
144
- ),
145
- a(
146
- {
147
- class: "dropdown-item",
148
- href: `javascript:ajax_modal('/admin/snapshot-restore/view/${view.name}')`,
149
- },
150
- '<i class="fas fa-undo-alt"></i>&nbsp;' + req.__("Restore")
151
- ),
152
- div({ class: "dropdown-divider" }),
153
- post_dropdown_item(
154
- `/viewedit/delete/${view.id}${on_done_redirect_str}`,
155
- '<i class="far fa-trash-alt"></i>&nbsp;' + req.__("Delete"),
156
- req,
157
- true,
158
- view.name
159
- ),
143
+ view.id &&
144
+ post_dropdown_item(
145
+ `/viewedit/clone/${view.id}${on_done_redirect_str}`,
146
+ '<i class="far fa-copy"></i>&nbsp;' + req.__("Duplicate"),
147
+ req
148
+ ),
149
+ view.id &&
150
+ a(
151
+ {
152
+ class: "dropdown-item",
153
+ href: `javascript:ajax_modal('/admin/snapshot-restore/view/${view.name}')`,
154
+ },
155
+ '<i class="fas fa-undo-alt"></i>&nbsp;' + req.__("Restore")
156
+ ),
157
+ view.id && div({ class: "dropdown-divider" }),
158
+ view.id &&
159
+ post_dropdown_item(
160
+ `/viewedit/delete/${view.id}${on_done_redirect_str}`,
161
+ '<i class="far fa-trash-alt"></i>&nbsp;' + req.__("Delete"),
162
+ req,
163
+ true,
164
+ view.name
165
+ ),
160
166
  ]);
161
167
 
162
168
  const setTableRefs = async (views) => {
@@ -222,17 +228,21 @@ const viewsList = async (
222
228
  {
223
229
  label: req.__("Role to access"),
224
230
  key: (row) =>
225
- editViewRoleForm(row, roles, req, on_done_redirect_str),
231
+ row.id
232
+ ? editViewRoleForm(row, roles, req, on_done_redirect_str)
233
+ : "admin",
226
234
  },
227
235
  {
228
236
  label: "",
229
237
  key: (r) =>
230
- link(
231
- `/viewedit/config/${encodeURIComponent(
232
- r.name
233
- )}${on_done_redirect_str}`,
234
- req.__("Configure")
235
- ),
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
+ : "",
236
246
  },
237
247
  !tagId
238
248
  ? {
package/routes/fields.js CHANGED
@@ -1169,7 +1169,10 @@ router.post(
1169
1169
  }
1170
1170
 
1171
1171
  const field = table.getField(fieldName);
1172
-
1172
+ if (!field) {
1173
+ res.send("");
1174
+ return;
1175
+ }
1173
1176
  const fieldViewConfigForms = await calcfldViewConfig([field], false, 0);
1174
1177
  const formFields = fieldViewConfigForms[field.name][fv_name];
1175
1178
  if (!formFields) {
@@ -12,8 +12,9 @@ const View = require("@saltcorn/data/models/view");
12
12
  const User = require("@saltcorn/data/models/user");
13
13
  const File = require("@saltcorn/data/models/file");
14
14
  const Page = require("@saltcorn/data/models/page");
15
+ const Plugin = require("@saltcorn/data/models/plugin");
15
16
  const { link, mkTable } = require("@saltcorn/markup");
16
- const { div, a, p, i } = require("@saltcorn/markup/tags");
17
+ const { div, a, p, i, h5, span } = require("@saltcorn/markup/tags");
17
18
  const Table = require("@saltcorn/data/models/table");
18
19
  const { get_cached_packs } = require("@saltcorn/admin-models/models/pack");
19
20
  // const { restore_backup } = require("../markup/admin");
@@ -310,10 +311,80 @@ const packTab = (req, packlist) =>
310
311
  { noHeader: true }
311
312
  ),
312
313
  a(
313
- { href: `/plugins?set=packs`, class: "btn btn-primary" },
314
+ { href: `/plugins?set=packs`, class: "btn btn-sm btn-primary" },
314
315
  req.__("Go to pack store »")
315
316
  )
316
317
  );
318
+
319
+ const themeCard = (req, roleMap) => {
320
+ const state_layouts = getState().layouts;
321
+ const state_layout_names = Object.keys(state_layouts);
322
+ const layout_by_role = getState().getConfig("layout_by_role");
323
+ const used_layout_by_role = {};
324
+ Object.keys(roleMap).forEach((role_id) => {
325
+ used_layout_by_role[role_id] =
326
+ layout_by_role[role_id] ||
327
+ state_layout_names[state_layout_names.length - 1];
328
+ });
329
+ const themes_available = Plugin.get_cached_plugins().filter(
330
+ (p) => p.has_theme && !state_layout_names.includes(p.name)
331
+ );
332
+ const layouts = Object.entries(getState().layouts)
333
+ .filter(([nm, v]) => nm !== "emergency")
334
+ .map(([name, layout]) => {
335
+ let plugin = getState().plugins[name];
336
+ const for_role = Object.entries(used_layout_by_role)
337
+ .filter(([role, rname]) => rname === name)
338
+ .map(([role, rname]) =>
339
+ span({ class: "badge bg-info" }, roleMap[role])
340
+ );
341
+
342
+ return {
343
+ name,
344
+ layout,
345
+ plugin,
346
+ for_role,
347
+ edit_cfg_link: plugin?.configuration_workflow
348
+ ? a(
349
+ {
350
+ href: `/plugins/configure/${encodeURIComponent(name)}`,
351
+ },
352
+ i({ class: "fa fa-cog ms-2" })
353
+ )
354
+ : "",
355
+ };
356
+ });
357
+ const show_installable = themes_available.length > 0 || layouts.length == 1;
358
+ return div(
359
+ { class: "pb-3 pt-2 pe-4" },
360
+ mkTable(
361
+ [
362
+ {
363
+ label: req.__("Installed theme"),
364
+ key: ({ name, edit_cfg_link }) => `${name}${edit_cfg_link}`,
365
+ },
366
+ {
367
+ label: req.__("Theme for role"),
368
+ key: ({ for_role }) => for_role.join(" "),
369
+ },
370
+ ],
371
+ layouts
372
+ ),
373
+ a({ href: "/roleadmin" }, req.__("Set theme for each user role »")),
374
+ show_installable && h5({ class: "mt-2" }, req.__("Available themes")),
375
+ show_installable &&
376
+ div(
377
+ themes_available
378
+ .map((p) => span({ class: "badge bg-secondary" }, p.name))
379
+ .join(" ")
380
+ ),
381
+ show_installable &&
382
+ a(
383
+ { href: `/plugins?set=themes`, class: "mt-2" },
384
+ req.__("Install more themes »")
385
+ )
386
+ );
387
+ };
317
388
  /**
318
389
  * Help Card
319
390
  * @param req
@@ -412,10 +483,12 @@ const welcome_page = async (req) => {
412
483
  users.length > 4
413
484
  ? {
414
485
  Users: await usersTab(req, users, roleMap),
486
+ Theme: themeCard(req, roleMap),
415
487
  Help: helpCard(req),
416
488
  }
417
489
  : {
418
490
  Help: helpCard(req),
491
+ Theme: themeCard(req, roleMap),
419
492
  Users: await usersTab(req, users, roleMap),
420
493
  },
421
494
  },
package/routes/menu.js CHANGED
@@ -307,7 +307,16 @@ const menuForm = async (req) => {
307
307
  name: "location",
308
308
  label: req.__("Location"),
309
309
  showIf: {
310
- type: ["View", "Page", "Link", "Header", "Dynamic", "Action"],
310
+ type: [
311
+ "View",
312
+ "Page",
313
+ "Link",
314
+ "Header",
315
+ "Dynamic",
316
+ "Search",
317
+ "Separator",
318
+ "Action",
319
+ ],
311
320
  },
312
321
  sublabel: req.__("Not all themes support all locations"),
313
322
  class: "item-menu",
@@ -317,7 +326,7 @@ const menuForm = async (req) => {
317
326
  //default: "Standard",
318
327
  attributes: {
319
328
  inline: true,
320
- options: "Standard, Mobile Bottom",
329
+ options: "Standard, Mobile Bottom, Secondary Menu",
321
330
  },
322
331
  },
323
332
  ],
@@ -167,7 +167,9 @@ const pageBuilderData = async (req, context) => {
167
167
  for (const name of actions) {
168
168
  const action = stateActions[name];
169
169
  if (action && action.configFields) {
170
- actionConfigForms[name] = await getActionConfigFields(action);
170
+ actionConfigForms[name] = await getActionConfigFields(action, null, {
171
+ mode: "page",
172
+ });
171
173
  }
172
174
  }
173
175
  const library = (await Library.find({})).filter((l) => l.suitableFor("page"));
@@ -214,9 +216,11 @@ const pageBuilderData = async (req, context) => {
214
216
  images,
215
217
  pages,
216
218
  actions,
219
+ builtInActions: ["GoBack"],
217
220
  library,
218
221
  min_role: context.min_role,
219
222
  actionConfigForms,
223
+ allowMultiStepAction: true,
220
224
  page_name: context.name,
221
225
  page_id: context.id,
222
226
  mode: "page",
package/routes/plugins.js CHANGED
@@ -1243,6 +1243,8 @@ router.post(
1243
1243
  }
1244
1244
  await load_plugins.loadAndSaveNewPlugin(plugin, forceReInstall);
1245
1245
  const plugin_module = getState().plugins[name];
1246
+ await getState().refresh_views();
1247
+
1246
1248
  if (plugin_module && plugin_module.configuration_workflow) {
1247
1249
  const plugin_db = await Plugin.findOne({ name });
1248
1250
  req.flash(
package/routes/view.js CHANGED
@@ -10,7 +10,7 @@ const View = require("@saltcorn/data/models/view");
10
10
  const Table = require("@saltcorn/data/models/table");
11
11
  const Trigger = require("@saltcorn/data/models/trigger");
12
12
 
13
- const { text, style } = require("@saltcorn/markup/tags");
13
+ const { text, style, div } = require("@saltcorn/markup/tags");
14
14
  const {
15
15
  isAdmin,
16
16
  error_catcher,
@@ -70,11 +70,11 @@ router.get(
70
70
  }
71
71
  const isModal = req.headers?.saltcornmodalrequest;
72
72
 
73
- const contents = await view.run_possibly_on_page(query, req, res);
73
+ const contents0 = await view.run_possibly_on_page(query, req, res);
74
74
  const title =
75
75
  isModal && view.attributes?.popup_title
76
76
  ? view.attributes?.popup_title
77
- : scan_for_page_title(contents, view.name);
77
+ : scan_for_page_title(contents0, view.name);
78
78
  if (isModal && view.attributes?.popup_width)
79
79
  res.set(
80
80
  "SaltcornModalWidth",
@@ -95,9 +95,21 @@ router.get(
95
95
  name: viewname,
96
96
  render_time: ms,
97
97
  });
98
- if (typeof contents === "object" && contents.goto)
99
- res.redirect(contents.goto);
100
- else
98
+ if (typeof contents0 === "object" && contents0.goto)
99
+ res.redirect(contents0.goto);
100
+ else {
101
+ const qs = "";
102
+ const contents =
103
+ typeof contents0 === "string" && !req.xhr
104
+ ? div(
105
+ {
106
+ class: "d-inline",
107
+ "data-sc-embed-viewname": view.name,
108
+ "data-sc-view-source": req.originalUrl,
109
+ },
110
+ contents0
111
+ )
112
+ : contents0;
101
113
  res.sendWrap(
102
114
  title,
103
115
  add_edit_bar({
@@ -108,10 +120,12 @@ router.get(
108
120
  cfgUrl: `/viewedit/config/${encodeURIComponent(view.name)}`,
109
121
  contents,
110
122
  req,
123
+ view,
111
124
  viewtemplate: view.viewtemplate,
112
125
  table: view.table_id || view.exttable_name,
113
126
  })
114
127
  );
128
+ }
115
129
  })
116
130
  );
117
131
 
@@ -118,6 +118,9 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
118
118
  .filter(([k, v]) => !v.tableless)
119
119
  .map(([k, v]) => k);
120
120
  const slugOptions = await Table.allSlugOptions();
121
+ const viewpatternOptions = Object.values(getState().viewtemplates)
122
+ .filter((vt) => !vt.singleton)
123
+ .map((vt) => vt.name);
121
124
  return new Form({
122
125
  action: addOnDoneRedirect("/viewedit/save", req),
123
126
  submitLabel: req.__("Configure") + " &raquo;",
@@ -151,7 +154,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
151
154
  topic: "View patterns",
152
155
  context: {},
153
156
  },
154
- options: Object.keys(getState().viewtemplates),
157
+ options: viewpatternOptions,
155
158
  attributes: {
156
159
  explainers: mapObjectValues(
157
160
  getState().viewtemplates,
@@ -661,11 +664,11 @@ router.post(
661
664
  * @function
662
665
  */
663
666
  router.post(
664
- "/add-to-menu/:id",
667
+ "/add-to-menu/:viewname",
665
668
  isAdmin,
666
669
  error_catcher(async (req, res) => {
667
- const { id } = req.params;
668
- const view = await View.findOne({ id });
670
+ const { viewname } = req.params;
671
+ const view = await View.findOne({ name: viewname });
669
672
  await add_to_menu({
670
673
  label: view.name,
671
674
  type: "View",
@@ -643,4 +643,43 @@ describe("many to many relations", () => {
643
643
  .expect(toInclude("album A"))
644
644
  .expect(toNotInclude("album B"));
645
645
  });
646
+
647
+ it("fan_club feed with query", async () => {
648
+ const app = await getApp({ disableCsrf: true });
649
+ const loginCookie = await getAdminLoginCookie();
650
+
651
+ const queryObj_1 = {
652
+ relation:
653
+ ".pressing_job.album.artist_plays_on_album$album.artist.fan_club$artist",
654
+ srcId: 1,
655
+ };
656
+ await request(app)
657
+ .get(
658
+ `/view/fan_club_feed?_inbound_relation_path_=${encodeURIComponent(
659
+ JSON.stringify(queryObj_1)
660
+ )}`
661
+ )
662
+ .set("Cookie", loginCookie)
663
+ .expect(toInclude("crazy fan club"))
664
+ .expect(toInclude("another club"))
665
+ .expect(toInclude("fan club"))
666
+ .expect(toInclude("fan club official"));
667
+
668
+ const queryObj_2 = {
669
+ relation:
670
+ ".pressing_job.album.artist_plays_on_album$album.artist.fan_club$artist",
671
+ srcId: 2,
672
+ };
673
+ await request(app)
674
+ .get(
675
+ `/view/fan_club_feed?_inbound_relation_path_=${encodeURIComponent(
676
+ JSON.stringify(queryObj_2)
677
+ )}`
678
+ )
679
+ .set("Cookie", loginCookie)
680
+ .expect(toInclude("crazy fan club"))
681
+ .expect(toNotInclude("another club"))
682
+ .expect(toInclude("fan club"))
683
+ .expect(toInclude("fan club official"));
684
+ });
646
685
  });