@saltcorn/server 1.0.0-beta.9 → 1.0.0-rc.10

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.
@@ -190,7 +190,8 @@ function pjax_to(href, e) {
190
190
  initialize_page();
191
191
  },
192
192
  error: function (res) {
193
- notifyAlert({ type: "danger", text: res.responseText });
193
+ if (!checkNetworkError(res))
194
+ notifyAlert({ type: "danger", text: res.responseText });
194
195
  },
195
196
  });
196
197
  }
@@ -224,8 +225,10 @@ function ajax_done(res, viewname) {
224
225
  function spin_action_link(e) {
225
226
  const $e = $(e);
226
227
  const width = $e.width();
228
+ const height = $e.height();
229
+
227
230
  $e.attr("data-innerhtml-prespin", $e.html());
228
- $e.html('<i class="fas fa-spinner fa-spin"></i>').width(width);
231
+ $e.html('<i class="fas fa-spinner fa-spin"></i>').width(width).height(height);
229
232
  }
230
233
 
231
234
  function reset_spinners() {
@@ -264,7 +267,8 @@ function view_post(viewnameOrElem, route, data, onDone, sendState) {
264
267
  reset_spinners();
265
268
  })
266
269
  .fail(function (res) {
267
- notifyAlert({ type: "danger", text: res.responseText });
270
+ if (!checkNetworkError(res))
271
+ notifyAlert({ type: "danger", text: res.responseText });
268
272
  reset_spinners();
269
273
  });
270
274
  }
@@ -346,16 +350,18 @@ function expand_thumbnail(img_id, filename) {
346
350
  }
347
351
 
348
352
  function ajax_modal(url, opts = {}) {
349
- ensure_modal_exists_and_closed();
350
- $("#scmodal").removeClass("no-submit-reload");
351
- $("#scmodal").attr("data-on-close-reload-view", opts.reload_view || null);
352
-
353
- if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
354
353
  $.ajax(url, {
355
354
  headers: {
356
355
  SaltcornModalRequest: "true",
357
356
  },
358
357
  success: function (res, textStatus, request) {
358
+ ensure_modal_exists_and_closed();
359
+ $("#scmodal").removeClass("no-submit-reload");
360
+ $("#scmodal").attr("data-on-close-reload-view", opts.reload_view || null);
361
+
362
+ if (opts.submitReload === false)
363
+ $("#scmodal").addClass("no-submit-reload");
364
+
359
365
  var title = request.getResponseHeader("Page-Title");
360
366
  var width = request.getResponseHeader("SaltcornModalWidth");
361
367
  var minwidth = request.getResponseHeader("SaltcornModalMinWidth");
@@ -388,7 +394,7 @@ function ajax_modal(url, opts = {}) {
388
394
  ? {
389
395
  error: opts.onError,
390
396
  }
391
- : {}),
397
+ : { error: checkNetworkError }),
392
398
  });
393
399
  }
394
400
  function closeModal() {
@@ -426,6 +432,20 @@ function saveAndContinue(e, k, event) {
426
432
  )
427
433
  return;
428
434
  var form = $(e).closest("form");
435
+ let focusedEl = null;
436
+ if (!event || !event.srcElement) {
437
+ const el = form.find("select[sc-received-focus]")[0];
438
+ if (el) {
439
+ el.removeAttribute("sc-received-focus");
440
+ if (el.getAttribute("previous-val") === el.value) return;
441
+ }
442
+ } else if (
443
+ event.srcElement.tagName === "SELECT" &&
444
+ event.srcElement.getAttribute("previous-val") !== undefined
445
+ ) {
446
+ focusedEl = event.srcElement;
447
+ }
448
+
429
449
  const valres = form[0].reportValidity();
430
450
  if (!valres) return;
431
451
  submitWithEmptyAction(form[0]);
@@ -448,10 +468,12 @@ function saveAndContinue(e, k, event) {
448
468
  reloadEmbeddedEditOwnViews(form, res.id);
449
469
  }
450
470
  common_done(res, form.attr("data-viewname"));
471
+ if (focusedEl) focusedEl.setAttribute("previous-val", focusedEl.value);
451
472
  },
452
473
  error: function (request) {
453
474
  var ct = request.getResponseHeader("content-type") || "";
454
- if (ct.startsWith && ct.startsWith("application/json")) {
475
+ if (checkNetworkError(request)) {
476
+ } else if (ct.startsWith && ct.startsWith("application/json")) {
455
477
  notifyAlert({ type: "danger", text: request.responseJSON.error });
456
478
  } else {
457
479
  $("#page-inner-content").html(request.responseText);
@@ -500,6 +522,7 @@ function applyViewConfig(e, url, k, event) {
500
522
  },
501
523
  data: JSON.stringify(cfg),
502
524
  error: function (request) {
525
+ checkNetworkError(request);
503
526
  window.savingViewConfig = false;
504
527
  ajax_indicate_error(e, request);
505
528
  },
@@ -578,6 +601,7 @@ function ajaxSubmitForm(e, force_no_reload) {
578
601
  else common_done(res, form.attr("data-viewname"));
579
602
  },
580
603
  error: function (request) {
604
+ checkNetworkError(request);
581
605
  var title = request.getResponseHeader("Page-Title");
582
606
  if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
583
607
  var body = request.responseText;
@@ -594,6 +618,9 @@ function ajax_post_json(url, data, args = {}) {
594
618
  ...args,
595
619
  });
596
620
  }
621
+
622
+ let scNetworkErrorSignaled = false;
623
+
597
624
  function ajax_post(url, args) {
598
625
  $.ajax(url, {
599
626
  type: "POST",
@@ -603,10 +630,31 @@ function ajax_post(url, args) {
603
630
  ...(args || {}),
604
631
  })
605
632
  .done(ajax_done)
606
- .fail((e) =>
607
- ajax_done(e.responseJSON || { error: "Unknown error: " + e.responseText })
608
- );
633
+ .fail((e, ...more) => {
634
+ if (!checkNetworkError(e))
635
+ return ajax_done(
636
+ e.responseJSON || { error: "Unknown error: " + e.responseText }
637
+ );
638
+ });
639
+ }
640
+
641
+ function checkNetworkError(e) {
642
+ if (e.readyState == 0 && !e.responseText && !e.responseJSON) {
643
+ //network error
644
+ if (scNetworkErrorSignaled) return true;
645
+ scNetworkErrorSignaled = true;
646
+ setTimeout(() => {
647
+ scNetworkErrorSignaled = false;
648
+ }, 1000);
649
+ console.error("Network error", e);
650
+ notifyAlert({
651
+ type: "danger",
652
+ text: "Network connection error",
653
+ });
654
+ return true;
655
+ }
609
656
  }
657
+
610
658
  function ajax_post_btn(e, reload_on_done, reload_delay) {
611
659
  var form = $(e).closest("form");
612
660
  var url = form.attr("action");
@@ -620,6 +668,7 @@ function ajax_post_btn(e, reload_on_done, reload_delay) {
620
668
  success: function () {
621
669
  if (reload_on_done) location.reload();
622
670
  },
671
+ error: checkNetworkError,
623
672
  complete: function () {
624
673
  if (reload_delay)
625
674
  setTimeout(function () {
@@ -641,6 +690,7 @@ function api_action_call(name, body) {
641
690
  success: function (res) {
642
691
  common_done(res.data);
643
692
  },
693
+ error: checkNetworkError,
644
694
  });
645
695
  }
646
696
 
@@ -840,7 +890,7 @@ function build_mobile_app(button) {
840
890
 
841
891
  if (
842
892
  params.useDocker &&
843
- !cordovaBuilderAvailable &&
893
+ !window.cordovaBuilderAvailable &&
844
894
  !confirm(
845
895
  "Docker is selected but the Cordova builder seems not to be installed. " +
846
896
  "Do you really want to continue?"
@@ -848,6 +898,30 @@ function build_mobile_app(button) {
848
898
  ) {
849
899
  return;
850
900
  }
901
+ if (
902
+ isSbadmin2 &&
903
+ !confirm(
904
+ "It seems you are using the standard sbadmin2 layout. " +
905
+ "This layout is not optimized for mobile, consider using any-bootstrap-theme. " +
906
+ "Do you really want to continue?"
907
+ )
908
+ ) {
909
+ return;
910
+ }
911
+ const notSupportedPlugins = params.includedPlugins.filter(
912
+ (plugin) => !window.pluginsReadyForMobile.includes(plugin)
913
+ );
914
+ if (
915
+ notSupportedPlugins.length > 0 &&
916
+ !confirm(
917
+ `It seems that the plugins '${notSupportedPlugins.join(
918
+ ", "
919
+ )}' are not ready for mobile. Do you really want to continue?`
920
+ )
921
+ ) {
922
+ return;
923
+ }
924
+
851
925
  ajax_post("/admin/build-mobile-app", {
852
926
  data: params,
853
927
  success: (data) => {
@@ -919,8 +993,8 @@ function check_cordova_builder() {
919
993
  $.ajax("/admin/mobile-app/check-cordova-builder", {
920
994
  type: "GET",
921
995
  success: function (res) {
922
- cordovaBuilderAvailable = !!res.installed;
923
- if (cordovaBuilderAvailable) {
996
+ window.cordovaBuilderAvailable = !!res.installed;
997
+ if (window.cordovaBuilderAvailable) {
924
998
  $("#dockerBuilderStatusId").html(
925
999
  `<span>
926
1000
  installed<i class="ps-2 fas fa-check text-success"></i>
@@ -1014,7 +1088,7 @@ function toggle_tbl_sync() {
1014
1088
  function toggle_android_platform() {
1015
1089
  if ($("#androidCheckboxId")[0].checked === true) {
1016
1090
  $("#dockerCheckboxId").attr("hidden", false);
1017
- $("#dockerCheckboxId").attr("checked", cordovaBuilderAvailable);
1091
+ $("#dockerCheckboxId").attr("checked", window.cordovaBuilderAvailable);
1018
1092
  $("#dockerLabelId").removeClass("d-none");
1019
1093
  } else {
1020
1094
  $("#dockerCheckboxId").attr("hidden", true);
package/routes/actions.js CHANGED
@@ -57,21 +57,6 @@ const {
57
57
  blocklyToolbox,
58
58
  } = require("../markup/blockly.js");
59
59
 
60
- /**
61
- * @returns {Promise<object>}
62
- */
63
- const getActions = async () => {
64
- return Object.entries(getState().actions).map(([k, v]) => {
65
- const hasConfig = !!v.configFields;
66
- const requireRow = !!v.requireRow;
67
- return {
68
- name: k,
69
- hasConfig,
70
- requireRow,
71
- };
72
- });
73
- };
74
-
75
60
  /**
76
61
  * Show list of Actions (Triggers) (HTTP GET)
77
62
  * @name get
@@ -96,7 +81,7 @@ router.get(
96
81
  triggers = triggers.filter((t) => tagged_trigger_ids.has(t.id));
97
82
  filterOnTag = await Tag.findOne({ id: +req.query._tag });
98
83
  }
99
- const actions = await getActions();
84
+ const actions = Trigger.abbreviated_actions;
100
85
  send_events_page({
101
86
  res,
102
87
  req,
@@ -156,7 +141,7 @@ const triggerForm = async (req, trigger) => {
156
141
  value: r.id,
157
142
  label: r.role,
158
143
  }));
159
- const actions = await getActions();
144
+ const actions = Trigger.abbreviated_actions;
160
145
  const tables = await Table.find({});
161
146
  let id;
162
147
  let form_action;
@@ -168,14 +153,13 @@ const triggerForm = async (req, trigger) => {
168
153
  const hasChannel = Object.entries(getState().eventTypes)
169
154
  .filter(([k, v]) => v.hasChannel)
170
155
  .map(([k, v]) => k);
171
- const allActions = actions.map((t) => t.name);
172
- allActions.push("Multi-step action");
156
+
157
+ const allActions = Trigger.action_options({ notRequireRow: false });
173
158
  const table_triggers = ["Insert", "Update", "Delete", "Validate"];
174
159
  const action_options = {};
175
- const actionsNotRequiringRow = actions
176
- .filter((a) => !a.requireRow)
177
- .map((t) => t.name);
178
- actionsNotRequiringRow.push("Multi-step action");
160
+ const actionsNotRequiringRow = Trigger.action_options({
161
+ notRequireRow: true,
162
+ });
179
163
 
180
164
  Trigger.when_options.forEach((t) => {
181
165
  if (table_triggers.includes(t)) action_options[t] = allActions;
@@ -237,6 +221,13 @@ const triggerForm = async (req, trigger) => {
237
221
  showIf: { when_trigger: "Daily" },
238
222
  sublabel: req.__("UTC timezone"),
239
223
  },
224
+ {
225
+ name: "channel",
226
+ label: req.__("Time to run"),
227
+ input_type: "time_of_week",
228
+ showIf: { when_trigger: "Weekly" },
229
+ sublabel: req.__("UTC timezone"),
230
+ },
240
231
  {
241
232
  name: "channel",
242
233
  label: req.__("Channel"),
@@ -555,7 +546,10 @@ router.get(
555
546
  let trigger;
556
547
  let id = parseInt(idorname);
557
548
  if (id) trigger = await Trigger.findOne({ id });
558
- else trigger = await Trigger.findOne({ name: idorname });
549
+ else {
550
+ trigger = await Trigger.findOne({ name: idorname });
551
+ id = trigger.id;
552
+ }
559
553
 
560
554
  if (!trigger) {
561
555
  req.flash("warning", req.__("Action not found"));
@@ -761,7 +755,7 @@ router.post(
761
755
  req.flash("success", req.__("Action configuration saved"));
762
756
  res.redirect(
763
757
  req.query.on_done_redirect &&
764
- is_relative_url(req.query.on_done_redirect)
758
+ is_relative_url("/" + req.query.on_done_redirect)
765
759
  ? `/${req.query.on_done_redirect}`
766
760
  : "/actions/"
767
761
  );