@saltcorn/server 0.7.4-beta.3 → 0.8.0-beta.0

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.
@@ -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,24 @@ 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
+ }
356
+
357
+ .w-unset {
358
+ width: unset;
359
+ }
360
+
361
+ .preview-text {
362
+ transform: rotate(-30deg);
363
+ z-index: 11;
364
+ margin-left: 30px;
365
+ margin-top: -5px;
366
+ }
@@ -1,8 +1,18 @@
1
- function sortby(k, desc) {
2
- set_state_fields({ _sortby: k, _sortdesc: desc ? "on" : { unset: true } });
1
+ function sortby(k, desc, viewIdentifier) {
2
+ set_state_fields({
3
+ [viewIdentifier ? `_${viewIdentifier}_sortby` : "_sortby"]: k,
4
+ [viewIdentifier ? `_${viewIdentifier}_sortdesc` : "_sortdesc"]: desc
5
+ ? "on"
6
+ : { unset: true },
7
+ });
3
8
  }
4
- function gopage(n, pagesize, extra = {}) {
5
- set_state_fields({ ...extra, _page: n, _pagesize: pagesize });
9
+ function gopage(n, pagesize, viewIdentifier, extra = {}) {
10
+ const cfg = {
11
+ ...extra,
12
+ [viewIdentifier ? `_${viewIdentifier}_page` : "_page"]: n,
13
+ [viewIdentifier ? `_${viewIdentifier}_pagesize` : "_pagesize"]: pagesize,
14
+ };
15
+ set_state_fields(cfg);
6
16
  }
7
17
 
8
18
  if (localStorage.getItem("reload_on_init")) {
@@ -77,8 +87,21 @@ function check_state_field(that) {
77
87
  pjax_to(dest.replace("&&", "&").replace("?&", "?"));
78
88
  }
79
89
 
90
+ function invalidate_pagings(href) {
91
+ let newhref = href;
92
+ const queryObj = Object.fromEntries(new URL(newhref).searchParams.entries());
93
+ const toRemove = Object.keys(queryObj).filter((val) => is_paging_param(val));
94
+ for (const k of toRemove) {
95
+ newhref = removeQueryStringParameter(newhref, k);
96
+ }
97
+ return newhref;
98
+ }
99
+
80
100
  function set_state_fields(kvs) {
81
- var newhref = get_current_state_url();
101
+ let newhref = get_current_state_url();
102
+ if (Object.keys(kvs).some((k) => !is_paging_param(k))) {
103
+ newhref = invalidate_pagings(newhref);
104
+ }
82
105
  Object.entries(kvs).forEach((kv) => {
83
106
  if (kv[1].unset && kv[1].unset === true)
84
107
  newhref = removeQueryStringParameter(newhref, kv[0]);
@@ -94,14 +117,15 @@ let loadPage = true;
94
117
  $(function () {
95
118
  $(window).bind("popstate", function (event) {
96
119
  const ensure_no_final_hash = (s) => (s.endsWith("#") ? s.slice(0, -1) : s);
97
- if (loadPage)
98
- window.location.assign(ensure_no_final_hash(window.location.href));
120
+ const newUrl = ensure_no_final_hash(window.location.href);
121
+ if (loadPage && newUrl !== window.location.href)
122
+ window.location.assign(newUrl);
99
123
  });
100
124
  });
101
125
 
102
126
  function pjax_to(href) {
103
127
  let $modal = $("#scmodal");
104
- const inModal = $modal.length && $modal.hasClass("show")
128
+ const inModal = $modal.length && $modal.hasClass("show");
105
129
  let $dest = inModal ? $("#scmodal .modal-body") : $("#page-inner-content");
106
130
 
107
131
  if (!$dest.length) window.location.href = href;
@@ -126,7 +150,7 @@ function pjax_to(href) {
126
150
  },
127
151
  error: function (res) {
128
152
  notifyAlert({ type: "danger", text: res.responseText });
129
- }
153
+ },
130
154
  });
131
155
  }
132
156
  }
@@ -135,21 +159,19 @@ function href_to(href) {
135
159
  window.location.href = href;
136
160
  }
137
161
  function clear_state(omit_fields_str) {
138
- let newUrl = get_current_state_url().split("?")[0]
139
- const hash = get_current_state_url().split("#")[1]
162
+ let newUrl = get_current_state_url().split("?")[0];
163
+ const hash = get_current_state_url().split("#")[1];
140
164
  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]
165
+ const omit_fields = omit_fields_str.split(",").map((s) => s.trim());
166
+ let qs = (get_current_state_url().split("?")[1] || "").split("#")[0];
143
167
  let params = new URLSearchParams(qs);
144
- newUrl = newUrl + '?'
145
- omit_fields.forEach(f => {
168
+ newUrl = newUrl + "?";
169
+ omit_fields.forEach((f) => {
146
170
  if (params.get(f))
147
171
  newUrl = updateQueryStringParameter(newUrl, f, params.get(f));
148
- })
149
-
172
+ });
150
173
  }
151
- if (hash)
152
- newUrl += '#' + hash;
174
+ if (hash) newUrl += "#" + hash;
153
175
 
154
176
  pjax_to(newUrl);
155
177
  }
@@ -170,12 +192,14 @@ function view_post(viewname, route, data, onDone) {
170
192
  ? "application/x-www-form-urlencoded"
171
193
  : "application/json",
172
194
  data: typeof data === "string" ? data : JSON.stringify(data),
173
- }).done(function (res) {
174
- if (onDone) onDone(res);
175
- ajax_done(res);
176
- }).fail(function (res) {
177
- notifyAlert({ type: "danger", text: res.responseText });
178
- });
195
+ })
196
+ .done(function (res) {
197
+ if (onDone) onDone(res);
198
+ ajax_done(res);
199
+ })
200
+ .fail(function (res) {
201
+ notifyAlert({ type: "danger", text: res.responseText });
202
+ });
179
203
  }
180
204
  var logged_errors = [];
181
205
  function globalErrorCatcher(message, source, lineno, colno, error) {
@@ -254,6 +278,8 @@ function ajax_modal(url, opts = {}) {
254
278
 
255
279
  function saveAndContinue(e, k) {
256
280
  var form = $(e).closest("form");
281
+ const valres = form[0].reportValidity()
282
+ if (!valres) return;
257
283
  submitWithEmptyAction(form[0]);
258
284
  var url = form.attr("action");
259
285
  var form_data = form.serialize();
@@ -300,12 +326,47 @@ function applyViewConfig(e, url, k) {
300
326
  error: function (request) { },
301
327
  success: function (res) {
302
328
  k && k(res);
329
+ !k && updateViewPreview();
303
330
  },
304
331
  });
305
332
 
306
333
  return false;
307
334
  }
308
335
 
336
+ function updateViewPreview() {
337
+ const $preview = $("#viewcfg-preview[data-preview-url]");
338
+ if ($preview.length > 0) {
339
+ const url = $preview.attr("data-preview-url");
340
+ $preview.css({ opacity: 0.5 });
341
+ $.ajax(url, {
342
+ type: "POST",
343
+ headers: {
344
+ "CSRF-Token": _sc_globalCsrf,
345
+ },
346
+
347
+ error: function (request) { },
348
+ success: function (res) {
349
+ $preview.css({ opacity: 1.0 });
350
+
351
+ //disable elements in preview
352
+ $preview.html(res);
353
+ $preview.find("a").attr("href", "#");
354
+ $preview
355
+ .find("[onclick], button, a, input, select")
356
+ .attr("onclick", "return false");
357
+
358
+ $preview.find("textarea").attr("disabled", true);
359
+ $preview.find("input").attr("readonly", true);
360
+
361
+ //disable functions preview migght try to call
362
+ set_state_field = () => { }
363
+ set_state_fields = () => { }
364
+
365
+ },
366
+ });
367
+ }
368
+ }
369
+
309
370
  function ajaxSubmitForm(e) {
310
371
  var form = $(e).closest("form");
311
372
  var url = form.attr("action");
@@ -317,10 +378,11 @@ function ajaxSubmitForm(e) {
317
378
  data: new FormData(form[0]),
318
379
  processData: false,
319
380
  contentType: false,
320
- success: function () {
381
+ success: function (res) {
321
382
  var no_reload = $("#scmodal").hasClass("no-submit-reload");
322
383
  $("#scmodal").modal("hide");
323
384
  if (!no_reload) location.reload();
385
+ else common_done(res);
324
386
  },
325
387
  error: function (request) {
326
388
  var title = request.getResponseHeader("Page-Title");
@@ -420,6 +482,17 @@ function test_formula(tablename, stored) {
420
482
  });
421
483
  }
422
484
 
485
+ function create_new_folder(folder) {
486
+ const name = window.prompt("Name of the new folder");
487
+ if (name)
488
+ ajax_post(`/files/new-folder`, {
489
+ data: { name, folder },
490
+ success: (data) => {
491
+ location.reload();
492
+ },
493
+ });
494
+ }
495
+
423
496
  async function fill_formula_btn_click(btn, k) {
424
497
  const formula = decodeURIComponent($(btn).attr("data-formula"));
425
498
  const free_vars = JSON.parse(
@@ -444,61 +517,135 @@ async function fill_formula_btn_click(btn, k) {
444
517
  }
445
518
  }
446
519
  }
447
- const val = new Function(
448
- `{${Object.keys(rec).join(",")}}`,
449
- "return " + formula
450
- )(rec);
451
- $(btn).closest(".input-group").find("input").val(val);
452
- if (k) k();
453
- }
454
-
455
- /*
456
- https://github.com/jeffdavidgreen/bootstrap-html5-history-tabs/blob/master/bootstrap-history-tabs.js
457
- Copyright (c) 2015 Jeff Green
458
- */
459
-
460
- +(function ($) {
461
- "use strict";
462
- $.fn.historyTabs = function () {
463
- var that = this;
464
- window.addEventListener("popstate", function (event) {
465
- if (event.state) {
466
- $(that)
467
- .filter('[href="' + event.state.url + '"]')
468
- .tab("show");
469
- }
520
+ try {
521
+ const val = new Function(
522
+ `{${Object.keys(rec).join(",")}}`,
523
+ "return " + formula
524
+ )(rec);
525
+ $(btn).closest(".input-group").find("input").val(val);
526
+ if (k) k();
527
+ } catch (e) {
528
+ notifyAlert({
529
+ type: "danger",
530
+ text: `Error evaluating fill formula: ${e.message}`,
470
531
  });
471
- return this.each(function (index, element) {
472
- $(element).on("show.bs.tab", function () {
473
- var stateObject = { url: $(this).attr("href") };
474
-
475
- if (window.location.hash && stateObject.url !== window.location.hash) {
476
- window.history.pushState(
477
- stateObject,
478
- document.title,
479
- window.location.pathname +
480
- window.location.search +
481
- $(this).attr("href")
482
- );
532
+ console.error(e);
533
+ }
534
+ }
535
+
536
+ function removeSpinner(elementId, orginalHtml) {
537
+ $(`#${elementId}`).html(orginalHtml);
538
+ }
539
+
540
+ function poll_mobile_build_finished(outDirName, pollCount, orginalBtnHtml) {
541
+ $.ajax("/admin/build-mobile-app/finished", {
542
+ type: "GET",
543
+ data: { build_dir: outDirName },
544
+ success: function (res) {
545
+ if (!res.finished) {
546
+ if (pollCount >= 50) {
547
+ removeSpinner("buildMobileAppBtnId", orginalBtnHtml);
548
+ notifyAlert({
549
+ type: "danger",
550
+ text: "unable to get the build results",
551
+ });
483
552
  } else {
484
- window.history.replaceState(
485
- stateObject,
486
- document.title,
487
- window.location.pathname +
488
- window.location.search +
489
- $(this).attr("href")
490
- );
553
+ setTimeout(() => {
554
+ poll_mobile_build_finished(outDirName, ++pollCount, orginalBtnHtml);
555
+ }, 5000);
491
556
  }
492
- });
493
- if (!window.location.hash && $(element).is(".active")) {
494
- // Shows the first element if there are no query parameters.
495
- $(element).tab("show");
496
- } else if ($(this).attr("href") === window.location.hash) {
497
- $(element).tab("show");
557
+ } else {
558
+ href_to(
559
+ `build-mobile-app/result?build_dir_name=${encodeURIComponent(
560
+ outDirName
561
+ )}`
562
+ );
498
563
  }
564
+ },
565
+ });
566
+ }
567
+
568
+ function build_mobile_app(button) {
569
+ const form = $(button).closest("form");
570
+ const params = {};
571
+ form.serializeArray().forEach((item) => {
572
+ params[item.name] = item.value;
573
+ });
574
+ ajax_post("/admin/build-mobile-app", {
575
+ data: params,
576
+ success: (data) => {
577
+ if (data.build_dir_name) {
578
+ handleMessages();
579
+ const orginalBtnHtml = $("#buildMobileAppBtnId").html();
580
+ press_store_button(button);
581
+ poll_mobile_build_finished(data.build_dir_name, 0, orginalBtnHtml);
582
+ }
583
+ },
584
+ });
585
+ }
586
+
587
+ (() => {
588
+ const e = document.querySelector("[data-sidebar-toggler]");
589
+ let closed = localStorage.getItem("sidebarClosed") === "true";
590
+ if (e) {
591
+ if (closed) {
592
+ e.dispatchEvent(new Event("click"));
593
+ }
594
+ e.addEventListener("click", () => {
595
+ closed = !closed;
596
+ localStorage.setItem("sidebarClosed", `${closed}`);
499
597
  });
500
- };
501
- })(jQuery);
598
+ }
599
+ })()
600
+
601
+
602
+ /*
603
+ https://github.com/jeffdavidgreen/bootstrap-html5-history-tabs/blob/master/bootstrap-history-tabs.js
604
+ Copyright (c) 2015 Jeff Green
605
+ */
606
+
607
+ + (function ($) {
608
+ "use strict";
609
+ $.fn.historyTabs = function () {
610
+ var that = this;
611
+ window.addEventListener("popstate", function (event) {
612
+ if (event.state) {
613
+ $(that)
614
+ .filter('[href="' + event.state.url + '"]')
615
+ .tab("show");
616
+ }
617
+ });
618
+ return this.each(function (index, element) {
619
+ $(element).on("show.bs.tab", function () {
620
+ var stateObject = { url: $(this).attr("href") };
621
+
622
+ if (window.location.hash && stateObject.url !== window.location.hash) {
623
+ window.history.pushState(
624
+ stateObject,
625
+ document.title,
626
+ window.location.pathname +
627
+ window.location.search +
628
+ $(this).attr("href")
629
+ );
630
+ } else {
631
+ window.history.replaceState(
632
+ stateObject,
633
+ document.title,
634
+ window.location.pathname +
635
+ window.location.search +
636
+ $(this).attr("href")
637
+ );
638
+ }
639
+ });
640
+ if (!window.location.hash && $(element).is(".active")) {
641
+ // Shows the first element if there are no query parameters.
642
+ $(element).tab("show");
643
+ } else if ($(this).attr("href") === window.location.hash) {
644
+ $(element).tab("show");
645
+ }
646
+ });
647
+ };
648
+ })(jQuery);
502
649
 
503
650
  // Copyright (c) 2011 Marcus Ekwall, http://writeless.se/
504
651
  // https://github.com/mekwall/jquery-throttle
@@ -23,6 +23,7 @@ const relevantPackages = [
23
23
  "saltcorn-sbadmin2",
24
24
  "server",
25
25
  "sqlite",
26
+ "filemanager"
26
27
  ];
27
28
 
28
29
  /**
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");
@@ -87,7 +92,6 @@ router.get(
87
92
  error_catcher(async (req, res) => {
88
93
  const triggers = await Trigger.findAllWithTableName();
89
94
  const actions = await getActions();
90
- const base_url = get_base_url(req);
91
95
  send_events_page({
92
96
  res,
93
97
  req,
@@ -149,6 +153,7 @@ const triggerForm = async (req, trigger) => {
149
153
  id = trigger.id;
150
154
  form_action = `/actions/edit/${id}`;
151
155
  } else form_action = "/actions/new";
156
+ form_action = addOnDoneRedirect(form_action, req);
152
157
  const hasChannel = Object.entries(getState().eventTypes)
153
158
  .filter(([k, v]) => v.hasChannel)
154
159
  .map(([k, v]) => k);
@@ -323,7 +328,7 @@ router.post(
323
328
  const tr = await Trigger.create(form.values);
324
329
  id = tr.id;
325
330
  }
326
- res.redirect(`/actions/configure/${id}`);
331
+ res.redirect(addOnDoneRedirect(`/actions/configure/${id}`, req));
327
332
  }
328
333
  })
329
334
  );
@@ -382,6 +387,11 @@ router.get(
382
387
  error_catcher(async (req, res) => {
383
388
  const { id } = req.params;
384
389
  const trigger = await Trigger.findOne({ id });
390
+ if (!trigger) {
391
+ req.flash("warning", req.__("Action not found"));
392
+ res.redirect(`/actions/`);
393
+ return
394
+ }
385
395
  const action = getState().actions[trigger.action];
386
396
  if (!action) {
387
397
  req.flash("warning", req.__("Action not found"));
@@ -389,7 +399,7 @@ router.get(
389
399
  } else if (trigger.action === "blocks") {
390
400
  const locale = req.getLocale();
391
401
  const form = new Form({
392
- action: `/actions/configure/${id}`,
402
+ action: addOnDoneRedirect(`/actions/configure/${id}`, req),
393
403
  fields: action.configFields,
394
404
  noSubmitButton: true,
395
405
  id: "blocklyForm",
@@ -464,7 +474,7 @@ router.get(
464
474
  const cfgFields = await getActionConfigFields(action, table);
465
475
  // create form
466
476
  const form = new Form({
467
- action: `/actions/configure/${id}`,
477
+ action: addOnDoneRedirect(`/actions/configure/${id}`, req),
468
478
  fields: cfgFields,
469
479
  });
470
480
  // populate form values
@@ -522,7 +532,11 @@ router.post(
522
532
  } else {
523
533
  await Trigger.update(trigger.id, { configuration: form.values });
524
534
  req.flash("success", "Action configuration saved");
525
- res.redirect(`/actions/`);
535
+ res.redirect(
536
+ req.query.on_done_redirect
537
+ ? `/${req.query.on_done_redirect}`
538
+ : "/actions/"
539
+ );
526
540
  }
527
541
  })
528
542
  );