@saltcorn/server 0.7.4-beta.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,7 +34,12 @@ function add_repeater(nm) {
34
34
  });
35
35
  newe.appendTo($("div.repeats-" + nm));
36
36
  }
37
- // "e.closest('.form-namespace').find('.coltype').val()==='Field';"
37
+
38
+ const _apply_showif_plugins = []
39
+
40
+ const add_apply_showif_plugin = p => {
41
+ _apply_showif_plugins.push(p)
42
+ }
38
43
  function apply_showif() {
39
44
  $("[data-show-if]").each(function (ix, element) {
40
45
  var e = $(element);
@@ -86,8 +91,7 @@ function apply_showif() {
86
91
  } else {
87
92
  e.append(
88
93
  $(
89
- `<option ${
90
- `${current}` === `${o.value}` ? "selected" : ""
94
+ `<option ${`${current}` === `${o.value}` ? "selected" : ""
91
95
  } value="${o.value}">${o.label}</option>`
92
96
  )
93
97
  );
@@ -111,28 +115,54 @@ function apply_showif() {
111
115
  e.change(function (ec) {
112
116
  e.attr("data-selected", ec.target.value);
113
117
  });
114
- $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
115
- if (resp.success) {
116
- e.empty();
117
- if (!dynwhere.required) e.append($(`<option></option>`));
118
- resp.success.forEach((r) => {
119
- e.append(
120
- $(
121
- `<option ${
122
- `${current}` === `${r[dynwhere.refname]}` ? "selected" : ""
123
- } value="${r[dynwhere.refname]}">${
124
- dynwhere.label_formula
125
- ? new Function(
126
- `{${Object.keys(r).join(",")}}`,
127
- "return " + dynwhere.label_formula
128
- )(r)
129
- : r[dynwhere.summary_field]
130
- }</option>`
131
- )
132
- );
133
- });
118
+
119
+ const currentOptionsSet = e.prop('data-fetch-options-current-set')
120
+ if (currentOptionsSet === qs) return;
121
+
122
+ const activate = (success, qs) => {
123
+ e.empty();
124
+ e.prop('data-fetch-options-current-set', qs)
125
+ if (!dynwhere.required) e.append($(`<option></option>`));
126
+ let currentDataOption = undefined;
127
+ const dataOptions = []
128
+ success.forEach((r) => {
129
+ const label = dynwhere.label_formula
130
+ ? new Function(
131
+ `{${Object.keys(r).join(",")}}`,
132
+ "return " + dynwhere.label_formula
133
+ )(r)
134
+ : r[dynwhere.summary_field]
135
+ const value = r[dynwhere.refname]
136
+ const selected = `${current}` === `${r[dynwhere.refname]}`
137
+ dataOptions.push({ text: label, value });
138
+ if (selected) currentDataOption = value;
139
+ const html = `<option ${selected ? "selected" : ""
140
+ } value="${value}">${label}</option>`
141
+ e.append(
142
+ $(html)
143
+ );
144
+ });
145
+ element.dispatchEvent(new Event('RefreshSelectOptions'))
146
+ if (e.hasClass("selectized") && $().selectize) {
147
+ e.selectize()[0].selectize.clearOptions();
148
+ e.selectize()[0].selectize.addOption(dataOptions);
149
+ if (typeof currentDataOption !== "undefined")
150
+ e.selectize()[0].selectize.setValue(currentDataOption);
151
+
134
152
  }
135
- });
153
+ }
154
+
155
+ const cache = e.prop('data-fetch-options-cache') || {}
156
+ if (cache[qs]) {
157
+ activate(cache[qs], qs)
158
+ } else
159
+ $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
160
+ if (resp.success) {
161
+ activate(resp.success, qs)
162
+ const cacheNow = e.prop('data-fetch-options-cache') || {}
163
+ e.prop('data-fetch-options-cache', { ...cacheNow, [qs]: resp.success })
164
+ }
165
+ });
136
166
  });
137
167
 
138
168
  $("[data-source-url]").each(function (ix, element) {
@@ -148,7 +178,17 @@ function apply_showif() {
148
178
  },
149
179
  });
150
180
  });
181
+ _apply_showif_plugins.forEach(p => p())
182
+ }
183
+
184
+ function splitTargetMatch(elemValue, target, keySpec) {
185
+ if (!elemValue) return false;
186
+ const [fld, keySpec1] = keySpec.split("|_")
187
+ const [sep, pos] = keySpec1.split("_")
188
+ const elemValueShort = elemValue.split(sep)[pos]
189
+ return elemValueShort === target;
151
190
  }
191
+
152
192
  function get_form_record(e, select_labels) {
153
193
  const rec = {};
154
194
  e.closest("form")
@@ -466,16 +506,14 @@ function notifyAlert(note, spin) {
466
506
  }
467
507
 
468
508
  $("#alerts-area")
469
- .append(`<div class="alert alert-${type} alert-dismissible fade show ${
470
- spin ? "d-flex align-items-center" : ""
471
- }" role="alert">
509
+ .append(`<div class="alert alert-${type} alert-dismissible fade show ${spin ? "d-flex align-items-center" : ""
510
+ }" role="alert">
472
511
  ${txt}
473
- ${
474
- spin
475
- ? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
476
- : `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
512
+ ${spin
513
+ ? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
514
+ : `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
477
515
  </button>`
478
- }
516
+ }
479
517
  </div>`);
480
518
  }
481
519
 
@@ -492,9 +530,8 @@ function common_done(res, isWeb = true) {
492
530
  (isWeb ? location : parent.location).reload(); //TODO notify to cookie if reload or goto
493
531
  }
494
532
  if (res.download) {
495
- const dataurl = `data:${
496
- res.download.mimetype || "application/octet-stream"
497
- };base64,${res.download.blob}`;
533
+ const dataurl = `data:${res.download.mimetype || "application/octet-stream"
534
+ };base64,${res.download.blob}`;
498
535
  fetch(dataurl)
499
536
  .then((res) => res.blob())
500
537
  .then((blob) => {
@@ -515,6 +552,9 @@ function common_done(res, isWeb = true) {
515
552
  if (res.target === "_blank") window.open(res.goto, "_blank").focus();
516
553
  else window.location.href = res.goto;
517
554
  }
555
+ if (res.popup) {
556
+ ajax_modal(res.popup)
557
+ }
518
558
  }
519
559
 
520
560
  const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
@@ -523,10 +563,11 @@ const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
523
563
  const setVal = (k, ix, v) => {
524
564
  const $e = form.find(`input[name="${k}_${ix}"]`);
525
565
  if ($e.length) $e.val(v);
526
- else
527
- form.append(
528
- `<input type="hidden" data-repeater-ix="${ix}" name="${k}_${ix}" value="${v}"></input>`
529
- );
566
+ else {
567
+ const $ne = $(`<input type="hidden" data-repeater-ix="${ix}" name="${k}_${ix}"></input>`);
568
+ $ne.val(v);
569
+ form.append($ne);
570
+ }
530
571
  };
531
572
  vs.forEach((v, ix) => {
532
573
  Object.entries(v).forEach(([k, v]) => {
@@ -578,6 +619,8 @@ const columnSummary = (col) => {
578
619
  return `Field ${col.field_name} ${col.fieldview || ""}`;
579
620
  case "Link":
580
621
  return `Link ${col.link_text}`;
622
+ case "FormulaValue":
623
+ return `Formula ${col.formula}`;
581
624
  case "JoinField":
582
625
  return `Join ${col.join_field}`;
583
626
  case "ViewLink":
@@ -585,7 +628,7 @@ const columnSummary = (col) => {
585
628
  case "Action":
586
629
  return `Action ${col.action_label || col.action_name}`;
587
630
  case "Aggregation":
588
- return `${col.stat} ${col.agg_field} ${col.agg_relation}`;
631
+ return `${col.stat} ${col.agg_field.split("@")[0]} ${col.agg_relation}`;
589
632
  default:
590
633
  return "Unknown";
591
634
  }
@@ -649,9 +692,9 @@ function room_older(viewname, room_id, btn) {
649
692
  function init_room(viewname, room_id) {
650
693
  const socket = parent?.config?.server_path
651
694
  ? io(parent.config.server_path, {
652
- query: `jwt=${localStorage.getItem("auth_jwt")}`,
653
- transports: ["websocket"],
654
- })
695
+ query: `jwt=${localStorage.getItem("auth_jwt")}`,
696
+ transports: ["websocket"],
697
+ })
655
698
  : io({ transports: ["websocket"] });
656
699
 
657
700
  socket.emit("join_room", [viewname, room_id]);
@@ -680,3 +723,23 @@ function cancel_form(form) {
680
723
  $(form).append(`<input type="hidden" name="_cancel" value="on">`);
681
724
  $(form).submit();
682
725
  }
726
+
727
+ function split_paste_handler(e) {
728
+ e.preventDefault();
729
+ let clipboardData = e.clipboardData || window.clipboardData || e.originalEvent.clipboardData;
730
+
731
+ const lines = clipboardData.getData('text').split(/\r\n/g)
732
+
733
+ const form = $(e.target).closest('form')
734
+
735
+ let matched = false;
736
+
737
+ form.find('input:not(:disabled):not([readonly]):not(:hidden)').each(function (ix, element) {
738
+ if (!matched && element === e.target) matched = true;
739
+ if (matched && lines.length > 0) {
740
+ $(element).val(lines.shift())
741
+ }
742
+ })
743
+
744
+
745
+ }
@@ -299,7 +299,9 @@ section.range-slider input[type="range"]::-moz-focus-outer {
299
299
  padding: 0.1rem 0.4rem !important;
300
300
  }
301
301
 
302
- table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
302
+ table.table-inner-grid,
303
+ table.table-inner-grid th,
304
+ table.table-inner-grid td {
303
305
  border: 1px solid black;
304
306
  border-collapse: collapse;
305
307
  }
@@ -307,18 +309,18 @@ table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
307
309
  /* https://codepen.io/pezmotion/pen/RQERdm */
308
310
 
309
311
  .editStarRating {
310
- direction: rtl;
311
- unicode-bidi: bidi-override;
312
- color: #ddd;
312
+ direction: rtl;
313
+ unicode-bidi: bidi-override;
314
+ color: #ddd;
313
315
  }
314
316
  .editStarRating input {
315
- display: none;
317
+ display: none;
316
318
  }
317
319
  .editStarRating label:hover,
318
320
  .editStarRating label:hover ~ label,
319
321
  .editStarRating input:checked + label,
320
322
  .editStarRating input:checked + label ~ label {
321
- color: #ffc107;
323
+ color: #ffc107;
322
324
  }
323
325
 
324
326
  .CodeMirror {
@@ -326,12 +328,12 @@ table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
326
328
  }
327
329
 
328
330
  /* copied from bootstrap and adjusted to show the arrow on the left */
329
- .card .card-header-left-collapse[data-bs-toggle=collapse] {
331
+ .card .card-header-left-collapse[data-bs-toggle="collapse"] {
330
332
  text-decoration: none;
331
333
  position: relative;
332
334
  padding: 0.75rem 3.25rem 0.75rem 1.25rem;
333
335
  }
334
- .card .card-header-left-collapse[data-bs-toggle=collapse]::before {
336
+ .card .card-header-left-collapse[data-bs-toggle="collapse"]::before {
335
337
  position: absolute;
336
338
  left: 0;
337
339
  top: 0;
@@ -341,9 +343,13 @@ table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
341
343
  font-family: "Font Awesome 5 Free";
342
344
  color: #d1d3e2;
343
345
  }
344
- .card .card-header-left-collapse[data-bs-toggle=collapse].collapsed {
346
+ .card .card-header-left-collapse[data-bs-toggle="collapse"].collapsed {
345
347
  border-radius: 0.35rem;
346
348
  }
347
- .card .card-header-left-collapse[data-bs-toggle=collapse].collapsed::before {
349
+ .card .card-header-left-collapse[data-bs-toggle="collapse"].collapsed::before {
348
350
  content: "\f105";
349
351
  }
352
+
353
+ .d-inline-maybe {
354
+ display: inline;
355
+ }
@@ -51,12 +51,19 @@ function removeQueryStringParameter(uri1, key) {
51
51
  return uri + hash;
52
52
  }
53
53
 
54
+ function get_current_state_url() {
55
+ let $modal = $("#scmodal");
56
+ if ($modal.length === 0 || !$modal.hasClass("show"))
57
+ return window.location.href;
58
+ else return $modal.prop("data-modal-state");
59
+ }
60
+
54
61
  function select_id(id) {
55
- pjax_to(updateQueryStringParameter(window.location.href, "id", id));
62
+ pjax_to(updateQueryStringParameter(get_current_state_url(), "id", id));
56
63
  }
57
64
 
58
65
  function set_state_field(key, value) {
59
- pjax_to(updateQueryStringParameter(window.location.href, key, value));
66
+ pjax_to(updateQueryStringParameter(get_current_state_url(), key, value));
60
67
  }
61
68
 
62
69
  function check_state_field(that) {
@@ -65,13 +72,13 @@ function check_state_field(that) {
65
72
  const value = that.value;
66
73
  var separator = window.location.href.indexOf("?") !== -1 ? "&" : "?";
67
74
  let dest;
68
- if (checked) dest = window.location.href + `${separator}${name}=${value}`;
69
- else dest = window.location.href.replace(`${name}=${value}`, "");
75
+ if (checked) dest = get_current_state_url() + `${separator}${name}=${value}`;
76
+ else dest = get_current_state_url().replace(`${name}=${value}`, "");
70
77
  pjax_to(dest.replace("&&", "&").replace("?&", "?"));
71
78
  }
72
79
 
73
80
  function set_state_fields(kvs) {
74
- var newhref = window.location.href;
81
+ var newhref = get_current_state_url();
75
82
  Object.entries(kvs).forEach((kv) => {
76
83
  if (kv[1].unset && kv[1].unset === true)
77
84
  newhref = removeQueryStringParameter(newhref, kv[0]);
@@ -80,7 +87,7 @@ function set_state_fields(kvs) {
80
87
  pjax_to(newhref.replace("&&", "&").replace("?&", "?"));
81
88
  }
82
89
  function unset_state_field(key) {
83
- pjax_to(removeQueryStringParameter(window.location.href, key));
90
+ pjax_to(removeQueryStringParameter(get_current_state_url(), key));
84
91
  }
85
92
 
86
93
  let loadPage = true;
@@ -93,7 +100,11 @@ $(function () {
93
100
  });
94
101
 
95
102
  function pjax_to(href) {
96
- if (!$("#page-inner-content").length) window.location.href = href;
103
+ let $modal = $("#scmodal");
104
+ const inModal = $modal.length && $modal.hasClass("show")
105
+ let $dest = inModal ? $("#scmodal .modal-body") : $("#page-inner-content");
106
+
107
+ if (!$dest.length) window.location.href = href;
97
108
  else {
98
109
  loadPage = false;
99
110
  $.ajax(href, {
@@ -101,18 +112,21 @@ function pjax_to(href) {
101
112
  pjaxpageload: "true",
102
113
  },
103
114
  success: function (res, textStatus, request) {
104
- window.history.pushState({ url: href }, "", href);
115
+ if (!inModal) window.history.pushState({ url: href }, "", href);
105
116
  setTimeout(() => {
106
117
  loadPage = true;
107
118
  }, 0);
108
- if (res.includes("<!--SCPT:")) {
119
+ if (!inModal && res.includes("<!--SCPT:")) {
109
120
  const start = res.indexOf("<!--SCPT:");
110
121
  const end = res.indexOf("-->", start);
111
122
  document.title = res.substring(start + 9, end);
112
123
  }
113
- $("#page-inner-content").html(res);
124
+ $dest.html(res);
114
125
  initialize_page();
115
126
  },
127
+ error: function (res) {
128
+ notifyAlert({ type: "danger", text: res.responseText });
129
+ }
116
130
  });
117
131
  }
118
132
  }
@@ -120,8 +134,24 @@ function pjax_to(href) {
120
134
  function href_to(href) {
121
135
  window.location.href = href;
122
136
  }
123
- function clear_state() {
124
- pjax_to(window.location.href.split("?")[0]);
137
+ function clear_state(omit_fields_str) {
138
+ let newUrl = get_current_state_url().split("?")[0]
139
+ const hash = get_current_state_url().split("#")[1]
140
+ if (omit_fields_str) {
141
+ const omit_fields = omit_fields_str.split(',').map(s => s.trim())
142
+ let qs = (get_current_state_url().split("?")[1] || "").split("#")[0]
143
+ let params = new URLSearchParams(qs);
144
+ newUrl = newUrl + '?'
145
+ omit_fields.forEach(f => {
146
+ if (params.get(f))
147
+ newUrl = updateQueryStringParameter(newUrl, f, params.get(f));
148
+ })
149
+
150
+ }
151
+ if (hash)
152
+ newUrl += '#' + hash;
153
+
154
+ pjax_to(newUrl);
125
155
  }
126
156
 
127
157
  function ajax_done(res) {
@@ -210,6 +240,7 @@ function ajax_modal(url, opts = {}) {
210
240
  var title = request.getResponseHeader("Page-Title");
211
241
  if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
212
242
  $("#scmodal .modal-body").html(res);
243
+ $("#scmodal").prop("data-modal-state", url);
213
244
  new bootstrap.Modal($("#scmodal")).show();
214
245
  initialize_page();
215
246
  (opts.onOpen || function () { })(res);
@@ -286,10 +317,11 @@ function ajaxSubmitForm(e) {
286
317
  data: new FormData(form[0]),
287
318
  processData: false,
288
319
  contentType: false,
289
- success: function () {
320
+ success: function (res) {
290
321
  var no_reload = $("#scmodal").hasClass("no-submit-reload");
291
322
  $("#scmodal").modal("hide");
292
323
  if (!no_reload) location.reload();
324
+ else common_done(res);
293
325
  },
294
326
  error: function (request) {
295
327
  var title = request.getResponseHeader("Page-Title");
@@ -413,12 +445,17 @@ async function fill_formula_btn_click(btn, k) {
413
445
  }
414
446
  }
415
447
  }
416
- const val = new Function(
417
- `{${Object.keys(rec).join(",")}}`,
418
- "return " + formula
419
- )(rec);
420
- $(btn).closest(".input-group").find("input").val(val);
421
- if (k) k();
448
+ try {
449
+ const val = new Function(
450
+ `{${Object.keys(rec).join(",")}}`,
451
+ "return " + formula
452
+ )(rec);
453
+ $(btn).closest(".input-group").find("input").val(val);
454
+ if (k) k();
455
+ } catch (e) {
456
+ notifyAlert({ type: "danger", text: `Error evaluating fill formula: ${e.message}` })
457
+ console.error(e)
458
+ }
422
459
  }
423
460
 
424
461
  /*
@@ -468,3 +505,7 @@ Copyright (c) 2015 Jeff Green
468
505
  });
469
506
  };
470
507
  })(jQuery);
508
+
509
+ // Copyright (c) 2011 Marcus Ekwall, http://writeless.se/
510
+ // https://github.com/mekwall/jquery-throttle
511
+ (function (a) { var b = a.jQuery || a.me || (a.me = {}), i = function (e, f, g, h, c, a) { f || (f = 100); var d = !1, j = !1, i = typeof g === "function", l = function (a, b) { d = setTimeout(function () { d = !1; if (h || c) e.apply(a, b), c && (j = +new Date); i && g.apply(a, b) }, f) }, k = function () { if (!d || a) { if (!d && !h && (!c || +new Date - j > f)) e.apply(this, arguments), c && (j = +new Date); (a || !c) && clearTimeout(d); l(this, arguments) } }; if (b.guid) k.guid = e.guid = e.guid || b.guid++; return k }; b.throttle = i; b.debounce = function (a, b, g, h, c) { return i(a, b, g, h, c, !0) } })(this);
package/routes/actions.js CHANGED
@@ -5,7 +5,12 @@
5
5
  * @subcategory routes
6
6
  */
7
7
  const Router = require("express-promise-router");
8
- const { isAdmin, error_catcher, get_base_url } = require("./utils.js");
8
+ const {
9
+ isAdmin,
10
+ error_catcher,
11
+ get_base_url,
12
+ addOnDoneRedirect,
13
+ } = require("./utils.js");
9
14
  const { getState } = require("@saltcorn/data/db/state");
10
15
  const Trigger = require("@saltcorn/data/models/trigger");
11
16
  const { getTriggerList } = require("./common_lists");
@@ -149,6 +154,7 @@ const triggerForm = async (req, trigger) => {
149
154
  id = trigger.id;
150
155
  form_action = `/actions/edit/${id}`;
151
156
  } else form_action = "/actions/new";
157
+ form_action = addOnDoneRedirect(form_action, req);
152
158
  const hasChannel = Object.entries(getState().eventTypes)
153
159
  .filter(([k, v]) => v.hasChannel)
154
160
  .map(([k, v]) => k);
@@ -323,7 +329,7 @@ router.post(
323
329
  const tr = await Trigger.create(form.values);
324
330
  id = tr.id;
325
331
  }
326
- res.redirect(`/actions/configure/${id}`);
332
+ res.redirect(addOnDoneRedirect(`/actions/configure/${id}`, req));
327
333
  }
328
334
  })
329
335
  );
@@ -389,7 +395,7 @@ router.get(
389
395
  } else if (trigger.action === "blocks") {
390
396
  const locale = req.getLocale();
391
397
  const form = new Form({
392
- action: `/actions/configure/${id}`,
398
+ action: addOnDoneRedirect(`/actions/configure/${id}`, req),
393
399
  fields: action.configFields,
394
400
  noSubmitButton: true,
395
401
  id: "blocklyForm",
@@ -464,7 +470,7 @@ router.get(
464
470
  const cfgFields = await getActionConfigFields(action, table);
465
471
  // create form
466
472
  const form = new Form({
467
- action: `/actions/configure/${id}`,
473
+ action: addOnDoneRedirect(`/actions/configure/${id}`, req),
468
474
  fields: cfgFields,
469
475
  });
470
476
  // populate form values
@@ -522,7 +528,11 @@ router.post(
522
528
  } else {
523
529
  await Trigger.update(trigger.id, { configuration: form.values });
524
530
  req.flash("success", "Action configuration saved");
525
- res.redirect(`/actions/`);
531
+ res.redirect(
532
+ req.query.on_done_redirect
533
+ ? `/${req.query.on_done_redirect}`
534
+ : "/actions/"
535
+ );
526
536
  }
527
537
  })
528
538
  );