@saltcorn/server 0.9.0-beta.0 → 0.9.0-beta.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.
@@ -6,14 +6,14 @@ to the user. 
6
6
 
7
7
  Some examples:
8
8
 
9
- {{# for (const fml of table.getFormulaExamples("Bool")) { }} * `{{= fml}}`
9
+ {{# for (const fml of table.getFormulaExamples("Bool")) { }} * `{{ fml}}`
10
10
  {{# } }}
11
11
 
12
- This formula can use any of the fields in table {{= table.name }} as variables:
12
+ This formula can use any of the fields in table {{ table.name }} as variables:
13
13
 
14
14
  | Field | Variable name | Type |
15
15
  | ----- | ------------- | ---- |
16
- {{# for (const field of table.fields) { }} | {{= field.label }} | `{{= field.name }}` | {{= field.pretty_type }} |
16
+ {{# for (const field of table.fields) { }} | {{ field.label }} | `{{ field.name }}` | {{ field.pretty_type }} |
17
17
  {{# } }}
18
18
 
19
19
 
@@ -32,7 +32,7 @@ The first-order join fields you can use in the constraint formula are:
32
32
 
33
33
  {{# for (const field of table.fields.filter(f=>f.is_fkey && f.reftable_name)) { }}
34
34
  {{# const reftable = Table.findOne( field.reftable_name); }}
35
- * Through {{=field.label}} key field: {{= table.fields.map(jf=>`\`${field.name}.${jf.name}\``).join(", ") }}
35
+ * Through {{field.label}} key field: {{ reftable.fields.map(jf=>`\`${field.name}.${jf.name}\``).join(", ") }}
36
36
 
37
37
 
38
38
  {{# } }}
package/help/index.js CHANGED
@@ -6,10 +6,11 @@ const MarkdownIt = require("markdown-it"),
6
6
  md = new MarkdownIt();
7
7
 
8
8
  const { pre } = require("@saltcorn/markup/tags");
9
+ const path = require("path");
9
10
 
10
11
  const get_md_file = async (topic) => {
11
12
  try {
12
- const fp = require.resolve(`./${File.normalise(topic)}.tmd`);
13
+ const fp = path.join(__dirname, `${File.normalise(topic)}.tmd`);
13
14
  const fileBuf = await fs.readFile(fp);
14
15
  return fileBuf.toString();
15
16
  } catch (e) {
@@ -17,9 +18,8 @@ const get_md_file = async (topic) => {
17
18
  }
18
19
  };
19
20
 
20
- _.templateSettings = {
21
- evaluate: /\{\{#(.+?)\}\}/g,
22
- interpolate: /\{\{=(.+?)\}\}/g,
21
+ md.renderer.rules.table_open = function (tokens, idx) {
22
+ return '<table class="help-md">';
23
23
  };
24
24
 
25
25
  const get_help_markup = async (topic, query, req) => {
@@ -30,7 +30,10 @@ const get_help_markup = async (topic, query, req) => {
30
30
  }
31
31
  const mdTemplate = await get_md_file(topic);
32
32
  if (!mdTemplate) return { markup: "Topic not found" };
33
- const template = _.template(mdTemplate);
33
+ const template = _.template(mdTemplate, {
34
+ evaluate: /\{\{#(.+?)\}\}/g,
35
+ interpolate: /\{\{([^#].+?)\}\}/g,
36
+ });
34
37
  const mdTopic = template(context);
35
38
  const markup = md.render(mdTopic);
36
39
  return { markup };
package/load_plugins.js CHANGED
@@ -176,9 +176,10 @@ const loadAllPlugins = async () => {
176
176
  * @param plugin
177
177
  * @param force
178
178
  * @param noSignalOrDB
179
+ * @param manager - optional plugin manager
179
180
  * @returns {Promise<void>}
180
181
  */
181
- const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB) => {
182
+ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
182
183
  const tenants_unsafe_plugins = getRootState().getConfig(
183
184
  "tenants_unsafe_plugins",
184
185
  false
@@ -200,7 +201,8 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB) => {
200
201
  }
201
202
  const { version, plugin_module, location } = await requirePlugin(
202
203
  plugin,
203
- force
204
+ force,
205
+ manager,
204
206
  );
205
207
 
206
208
  // install dependecies
package/locales/en.json CHANGED
@@ -1250,5 +1250,7 @@
1250
1250
  "New user view": "New user view",
1251
1251
  "A view to show to new users, to finalise registration (if Edit) or as a welcome view": "A view to show to new users, to finalise registration (if Edit) or as a welcome view",
1252
1252
  "View decoration": "View decoration",
1253
- "Title formula": "Title formula"
1253
+ "Title formula": "Title formula",
1254
+ "Show if true": "Show if true",
1255
+ "Formula. Leave blank to always show": "Formula. Leave blank to always show"
1254
1256
  }
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.0-beta.0",
3
+ "version": "0.9.0-beta.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
- "@saltcorn/base-plugin": "0.9.0-beta.0",
10
- "@saltcorn/builder": "0.9.0-beta.0",
11
- "@saltcorn/data": "0.9.0-beta.0",
12
- "@saltcorn/admin-models": "0.9.0-beta.0",
13
- "@saltcorn/filemanager": "0.9.0-beta.0",
14
- "@saltcorn/markup": "0.9.0-beta.0",
15
- "@saltcorn/sbadmin2": "0.9.0-beta.0",
9
+ "@saltcorn/base-plugin": "0.9.0-beta.1",
10
+ "@saltcorn/builder": "0.9.0-beta.1",
11
+ "@saltcorn/data": "0.9.0-beta.1",
12
+ "@saltcorn/admin-models": "0.9.0-beta.1",
13
+ "@saltcorn/filemanager": "0.9.0-beta.1",
14
+ "@saltcorn/markup": "0.9.0-beta.1",
15
+ "@saltcorn/sbadmin2": "0.9.0-beta.1",
16
16
  "@socket.io/cluster-adapter": "^0.2.1",
17
17
  "@socket.io/sticky": "^1.0.1",
18
18
  "adm-zip": "0.5.10",
@@ -335,18 +335,21 @@ function splitTargetMatch(elemValue, target, keySpec) {
335
335
  return elemValueShort === target;
336
336
  }
337
337
 
338
- function get_form_record(e, select_labels) {
338
+ function get_form_record(e_in, select_labels) {
339
339
  const rec = {};
340
- e.closest(".form-namespace")
341
- .find("input[name],select[name]")
342
- .each(function () {
343
- const name = $(this).attr("data-fieldname") || $(this).attr("name");
344
- if (select_labels && $(this).prop("tagName").toLowerCase() === "select")
345
- rec[name] = $(this).find("option:selected").text();
346
- else if ($(this).prop("type") === "checkbox")
347
- rec[name] = $(this).prop("checked");
348
- else rec[name] = $(this).val();
349
- });
340
+
341
+ const e = e_in.viewname
342
+ ? $(`form[data-viewname=${e_in.viewname}]`)
343
+ : e_in.closest(".form-namespace");
344
+
345
+ e.find("input[name],select[name],textarea[name]").each(function () {
346
+ const name = $(this).attr("data-fieldname") || $(this).attr("name");
347
+ if (select_labels && $(this).prop("tagName").toLowerCase() === "select")
348
+ rec[name] = $(this).find("option:selected").text();
349
+ else if ($(this).prop("type") === "checkbox")
350
+ rec[name] = $(this).prop("checked");
351
+ else rec[name] = $(this).val();
352
+ });
350
353
  return rec;
351
354
  }
352
355
  function showIfFormulaInputs(e, fml) {
@@ -953,7 +956,7 @@ function press_store_button(clicked) {
953
956
  $(btn).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
954
957
  }
955
958
 
956
- function common_done(res, isWeb = true) {
959
+ function common_done(res, viewname, isWeb = true) {
957
960
  const handle = (element, fn) => {
958
961
  if (Array.isArray(element)) for (const current of element) fn(current);
959
962
  else fn(element);
@@ -982,6 +985,17 @@ function common_done(res, isWeb = true) {
982
985
  });
983
986
  });
984
987
  }
988
+ if (res.set_fields && viewname) {
989
+ Object.keys(res.set_fields).forEach((k) => {
990
+ const form = $(`form[data-viewname=${viewname}]`);
991
+ const input = form.find(
992
+ `input[name=${k}], textarea[name=${k}], select[name=${k}]`
993
+ );
994
+ if (input.attr("type") === "checkbox")
995
+ input.prop("checked", res.set_fields[k]);
996
+ else input.val(res.set_fields[k]);
997
+ });
998
+ }
985
999
  if (res.goto && !isWeb)
986
1000
  // TODO ch
987
1001
  notifyAlert({
@@ -472,3 +472,17 @@ div.unread-notify {
472
472
  cursor: pointer;
473
473
  text-decoration: underline;
474
474
  }
475
+
476
+ table.help-md thead {
477
+ border-bottom: 1px solid black;
478
+ }
479
+
480
+ table.help-md {
481
+ margin-bottom: 1em;
482
+ }
483
+
484
+ table.help-md td:nth-child(2),
485
+ table.help-md th:nth-child(2) {
486
+ padding-left: 10px;
487
+ padding-right: 10px;
488
+ }
@@ -198,8 +198,8 @@ function clear_state(omit_fields_str, e) {
198
198
  pjax_to(newUrl, e);
199
199
  }
200
200
 
201
- function ajax_done(res) {
202
- common_done(res);
201
+ function ajax_done(res, viewname) {
202
+ common_done(res, viewname);
203
203
  }
204
204
 
205
205
  function view_post(viewname, route, data, onDone, sendState) {
@@ -220,7 +220,7 @@ function view_post(viewname, route, data, onDone, sendState) {
220
220
  })
221
221
  .done(function (res) {
222
222
  if (onDone) onDone(res);
223
- ajax_done(res);
223
+ ajax_done(res, viewname);
224
224
  })
225
225
  .fail(function (res) {
226
226
  notifyAlert({ type: "danger", text: res.responseText });
@@ -491,7 +491,7 @@ function ajaxSubmitForm(e) {
491
491
  var no_reload = $("#scmodal").hasClass("no-submit-reload");
492
492
  $("#scmodal").modal("hide");
493
493
  if (!no_reload) location.reload();
494
- else common_done(res);
494
+ else common_done(res, form.attr("data-viewname"));
495
495
  },
496
496
  error: function (request) {
497
497
  var title = request.getResponseHeader("Page-Title");
package/routes/admin.js CHANGED
@@ -1514,6 +1514,8 @@ router.get(
1514
1514
  const plugins = (await Plugin.find()).filter(
1515
1515
  (plugin) => ["base", "sbadmin2"].indexOf(plugin.name) < 0
1516
1516
  );
1517
+ const builderSettings =
1518
+ getState().getConfig("mobile_builder_settings") || {};
1517
1519
  send_admin_page({
1518
1520
  res,
1519
1521
  req,
@@ -1574,7 +1576,14 @@ router.get(
1574
1576
  onClick: "showEntrySelect('view')",
1575
1577
  },
1576
1578
  div(
1577
- { class: "nav-link active", id: "viewNavLinkID" },
1579
+ {
1580
+ class: `nav-link ${
1581
+ !builderSettings.entryPointType || builderSettings.entryPointType === "view"
1582
+ ? "active"
1583
+ : ""
1584
+ }`,
1585
+ id: "viewNavLinkID",
1586
+ },
1578
1587
  req.__("View")
1579
1588
  )
1580
1589
  ),
@@ -1584,7 +1593,14 @@ router.get(
1584
1593
  onClick: "showEntrySelect('page')",
1585
1594
  },
1586
1595
  div(
1587
- { class: "nav-link", id: "pageNavLinkID" },
1596
+ {
1597
+ class: `nav-link ${
1598
+ builderSettings.entryPointType === "page"
1599
+ ? "active"
1600
+ : ""
1601
+ }`,
1602
+ id: "pageNavLinkID",
1603
+ },
1588
1604
  req.__("Page")
1589
1605
  )
1590
1606
  )
@@ -1592,25 +1608,54 @@ router.get(
1592
1608
  // select entry-view
1593
1609
  select(
1594
1610
  {
1595
- class: "form-select",
1596
- name: "entryPoint",
1611
+ class: `form-select ${
1612
+ builderSettings.entryPointType === "page"
1613
+ ? "d-none"
1614
+ : ""
1615
+ }`,
1616
+ ...(!builderSettings.entryPointType || builderSettings.entryPointType === "view"
1617
+ ? { name: "entryPoint" }
1618
+ : {}),
1597
1619
  id: "viewInputID",
1598
1620
  },
1599
1621
  views
1600
1622
  .map((view) =>
1601
- option({ value: view.name }, view.name)
1623
+ option(
1624
+ {
1625
+ value: view.name,
1626
+ selected:
1627
+ builderSettings.entryPointType === "view" &&
1628
+ builderSettings.entryPoint === view.name,
1629
+ },
1630
+ view.name
1631
+ )
1602
1632
  )
1603
1633
  .join(",")
1604
1634
  ),
1605
1635
  // select entry-page
1606
1636
  select(
1607
1637
  {
1608
- class: "form-select d-none",
1638
+ class: `form-select ${
1639
+ !builderSettings.entryPointType || builderSettings.entryPointType === "view"
1640
+ ? "d-none"
1641
+ : ""
1642
+ }`,
1643
+ ...(builderSettings.entryPointType === "page"
1644
+ ? { name: "entryPoint" }
1645
+ : {}),
1609
1646
  id: "pageInputID",
1610
1647
  },
1611
1648
  pages
1612
1649
  .map((page) =>
1613
- option({ value: page.name }, page.name)
1650
+ option(
1651
+ {
1652
+ value: page.name,
1653
+ selected:
1654
+ builderSettings.entryPointType === "page" &&
1655
+ builderSettings.entryPoint === page.name,
1656
+ },
1657
+ page.name
1658
+ )
1614
1659
  )
1615
1660
  .join("")
1616
1661
  )
@@ -1631,6 +1676,7 @@ router.get(
1631
1676
  name: "androidPlatform",
1632
1677
  id: "androidCheckboxId",
1633
1678
  onClick: "toggle_android_platform()",
1679
+ checked: builderSettings.androidPlatform === "on",
1634
1680
  })
1635
1681
  )
1636
1682
  ),
@@ -1645,6 +1691,7 @@ router.get(
1645
1691
  class: "form-check-input",
1646
1692
  name: "iOSPlatform",
1647
1693
  id: "iOSCheckboxId",
1694
+ checked: builderSettings.iOSPlatform === "on",
1648
1695
  })
1649
1696
  )
1650
1697
  )
@@ -1658,7 +1705,8 @@ router.get(
1658
1705
  class: "form-check-input",
1659
1706
  name: "useDocker",
1660
1707
  id: "dockerCheckboxId",
1661
- hidden: true,
1708
+ hidden: builderSettings.androidPlatform !== "on",
1709
+ checked: builderSettings.useDocker === "on",
1662
1710
  })
1663
1711
  )
1664
1712
  ),
@@ -1680,6 +1728,7 @@ router.get(
1680
1728
  name: "appName",
1681
1729
  id: "appNameInputId",
1682
1730
  placeholder: "SaltcornMobileApp",
1731
+ value: builderSettings.appName || "",
1683
1732
  })
1684
1733
  )
1685
1734
  ),
@@ -1701,6 +1750,7 @@ router.get(
1701
1750
  name: "appVersion",
1702
1751
  id: "appVersionInputId",
1703
1752
  placeholder: "1.0.0",
1753
+ value: builderSettings.appVersion || "",
1704
1754
  })
1705
1755
  )
1706
1756
  ),
@@ -1721,6 +1771,7 @@ router.get(
1721
1771
  class: "form-control",
1722
1772
  name: "serverURL",
1723
1773
  id: "serverURLInputId",
1774
+ value: builderSettings.serverURL || "",
1724
1775
  placeholder: getState().getConfig("base_url") || "",
1725
1776
  })
1726
1777
  )
@@ -1746,7 +1797,14 @@ router.get(
1746
1797
  [
1747
1798
  option({ value: "" }, ""),
1748
1799
  ...images.map((image) =>
1749
- option({ value: image.location }, image.filename)
1800
+ option(
1801
+ {
1802
+ value: image.location,
1803
+ selected:
1804
+ builderSettings.appIcon === image.location,
1805
+ },
1806
+ image.filename
1807
+ )
1750
1808
  ),
1751
1809
  ].join("")
1752
1810
  )
@@ -1772,7 +1830,14 @@ router.get(
1772
1830
  [
1773
1831
  option({ value: "" }, ""),
1774
1832
  ...pages.map((page) =>
1775
- option({ value: page.name }, page.name)
1833
+ option(
1834
+ {
1835
+ value: page.name,
1836
+ selected:
1837
+ builderSettings.splashPage === page.name,
1838
+ },
1839
+ page.name
1840
+ )
1776
1841
  ),
1777
1842
  ].join("")
1778
1843
  )
@@ -1788,8 +1853,7 @@ router.get(
1788
1853
  id: "autoPublLoginId",
1789
1854
  class: "form-check-input me-2",
1790
1855
  name: "autoPublicLogin",
1791
- value: "autoPublicLogin",
1792
- checked: false,
1856
+ checked: builderSettings.autoPublicLogin === "on",
1793
1857
  }),
1794
1858
  label(
1795
1859
  {
@@ -1810,9 +1874,8 @@ router.get(
1810
1874
  id: "offlineModeBoxId",
1811
1875
  class: "form-check-input me-2",
1812
1876
  name: "allowOfflineMode",
1813
- value: "allowOfflineMode",
1814
1877
  onClick: "toggle_tbl_sync()",
1815
- checked: true,
1878
+ checked: builderSettings.allowOfflineMode === "on",
1816
1879
  }),
1817
1880
  label(
1818
1881
  {
@@ -1828,6 +1891,7 @@ router.get(
1828
1891
  {
1829
1892
  id: "tblSyncSelectorId",
1830
1893
  class: "row pb-3",
1894
+ hidden: builderSettings.allowOfflineMode !== "on",
1831
1895
  },
1832
1896
  div(
1833
1897
  label(
@@ -1864,6 +1928,12 @@ router.get(
1864
1928
  id: `${table.name}_unsynched_opt`,
1865
1929
  value: table.name,
1866
1930
  label: table.name,
1931
+ hidden:
1932
+ builderSettings.synchedTables?.indexOf(
1933
+ table.name
1934
+ ) >= 0
1935
+ ? true
1936
+ : false,
1867
1937
  })
1868
1938
  )
1869
1939
  )
@@ -1908,7 +1978,12 @@ router.get(
1908
1978
  id: `${table.name}_synched_opt`,
1909
1979
  value: table.name,
1910
1980
  label: table.name,
1911
- hidden: "true",
1981
+ hidden:
1982
+ builderSettings.synchedTables?.indexOf(
1983
+ table.name
1984
+ ) >= 0
1985
+ ? false
1986
+ : true,
1912
1987
  })
1913
1988
  )
1914
1989
  )
@@ -1954,7 +2029,12 @@ router.get(
1954
2029
  id: `${plugin.name}_excluded_opt`,
1955
2030
  value: plugin.name,
1956
2031
  label: plugin.name,
1957
- hidden: "true",
2032
+ hidden:
2033
+ builderSettings.excludedPlugins?.indexOf(
2034
+ plugin.name
2035
+ ) >= 0
2036
+ ? false
2037
+ : true,
1958
2038
  })
1959
2039
  )
1960
2040
  )
@@ -1999,7 +2079,12 @@ router.get(
1999
2079
  id: `${plugin.name}_included_opt`,
2000
2080
  value: plugin.name,
2001
2081
  label: plugin.name,
2002
- // hidden: "true",
2082
+ hidden:
2083
+ builderSettings.excludedPlugins?.indexOf(
2084
+ plugin.name
2085
+ ) >= 0
2086
+ ? true
2087
+ : false,
2003
2088
  })
2004
2089
  )
2005
2090
  )
@@ -2116,6 +2201,8 @@ router.post(
2116
2201
  synchedTables,
2117
2202
  includedPlugins,
2118
2203
  } = req.body;
2204
+ if (!includedPlugins) includedPlugins = [];
2205
+ if (!synchedTables) synchedTables = [];
2119
2206
  if (!androidPlatform && !iOSPlatform) {
2120
2207
  return res.json({
2121
2208
  error: req.__("Please select at least one platform (android or iOS)."),
@@ -2167,9 +2254,9 @@ router.post(
2167
2254
  if (splashPage) spawnParams.push("--splashPage", splashPage);
2168
2255
  if (allowOfflineMode) spawnParams.push("--allowOfflineMode");
2169
2256
  if (autoPublicLogin) spawnParams.push("--autoPublicLogin");
2170
- if (synchedTables?.length > 0)
2257
+ if (synchedTables.length > 0)
2171
2258
  spawnParams.push("--synchedTables", ...synchedTables.map((tbl) => tbl));
2172
- if (includedPlugins?.length > 0)
2259
+ if (includedPlugins.length > 0)
2173
2260
  spawnParams.push(
2174
2261
  "--includedPlugins",
2175
2262
  ...includedPlugins.map((pluginName) => pluginName)
@@ -2180,6 +2267,30 @@ router.post(
2180
2267
  ) {
2181
2268
  spawnParams.push("--tenantAppName", db.getTenantSchema());
2182
2269
  }
2270
+ const excludedPlugins = (await Plugin.find())
2271
+ .filter(
2272
+ (plugin) =>
2273
+ ["base", "sbadmin2"].indexOf(plugin.name) < 0 &&
2274
+ includedPlugins.indexOf(plugin.name) < 0
2275
+ )
2276
+ .map((plugin) => plugin.name);
2277
+ await getState().setConfig("mobile_builder_settings", {
2278
+ entryPoint,
2279
+ entryPointType,
2280
+ androidPlatform,
2281
+ iOSPlatform,
2282
+ useDocker,
2283
+ appName,
2284
+ appVersion,
2285
+ appIcon,
2286
+ serverURL,
2287
+ splashPage,
2288
+ autoPublicLogin,
2289
+ allowOfflineMode,
2290
+ synchedTables: synchedTables,
2291
+ includedPlugins: includedPlugins,
2292
+ excludedPlugins,
2293
+ });
2183
2294
  // end http call, return the out directory name
2184
2295
  // the gui polls for results
2185
2296
  res.json({ build_dir_name: outDirName });