@saltcorn/server 0.8.0-beta.4 → 0.8.1-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.
Files changed (46) hide show
  1. package/app.js +7 -6
  2. package/auth/admin.js +260 -217
  3. package/auth/index.js +20 -20
  4. package/auth/roleadmin.js +2 -9
  5. package/auth/routes.js +193 -139
  6. package/auth/testhelp.js +62 -55
  7. package/fixture_persons.js +1 -1
  8. package/index.js +22 -22
  9. package/locales/en.json +13 -1
  10. package/locales/fr.json +14 -2
  11. package/locales/ru.json +25 -19
  12. package/markup/admin.js +22 -15
  13. package/markup/blockly.js +1 -1
  14. package/markup/expression_blurb.js +15 -15
  15. package/markup/forms.js +21 -22
  16. package/markup/index.js +20 -20
  17. package/markup/plugin-store.js +4 -4
  18. package/package.json +8 -8
  19. package/public/diagram_utils.js +22 -9
  20. package/public/saltcorn-common.js +128 -68
  21. package/public/saltcorn.css +6 -0
  22. package/public/saltcorn.js +68 -20
  23. package/restart_watcher.js +157 -157
  24. package/routes/actions.js +4 -11
  25. package/routes/admin.js +14 -6
  26. package/routes/api.js +11 -18
  27. package/routes/common_lists.js +127 -130
  28. package/routes/delete.js +2 -2
  29. package/routes/edit.js +1 -1
  30. package/routes/fields.js +48 -2
  31. package/routes/files.js +112 -94
  32. package/routes/homepage.js +1 -1
  33. package/routes/infoarch.js +1 -1
  34. package/routes/list.js +6 -5
  35. package/routes/packs.js +1 -2
  36. package/routes/pageedit.js +1 -1
  37. package/routes/tag_entries.js +1 -1
  38. package/routes/tenant.js +2 -1
  39. package/routes/utils.js +3 -1
  40. package/routes/view.js +14 -2
  41. package/routes/viewedit.js +35 -0
  42. package/s3storage.js +13 -11
  43. package/serve.js +35 -31
  44. package/systemd.js +23 -21
  45. package/tests/fields.test.js +23 -0
  46. package/wrapper.js +46 -45
@@ -9,6 +9,8 @@ let tagFilterEnabled = false;
9
9
 
10
10
  let activePopper = null;
11
11
 
12
+ const maxPreviewHeight = "350px";
13
+
12
14
  function initMouseOver() {
13
15
  cy.on("mouseover", "node", (event) => {
14
16
  const node = event.target;
@@ -41,7 +43,7 @@ function initMouseOver() {
41
43
  function buildCard(node) {
42
44
  const { type, label } = node.data();
43
45
  const html = `
44
- <div class="card" style="width: 18rem;">
46
+ <div class="card" style="width: 20rem;">
45
47
  <div class="card-header">
46
48
  <h5 class="card-title">${type}</h5>
47
49
  <h6 class="card-subtitle text-muted">${label}</h6>
@@ -49,7 +51,9 @@ function buildCard(node) {
49
51
  <div class="card-body">
50
52
  ${buildTagBadges(node)}
51
53
  ${buildCardBody(node)}
52
- ${type === "page" || type === "view" ? buildPreviewDiv(node) : ""}
54
+ <div>
55
+ ${type === "page" || type === "view" ? buildPreviewDiv(node) : ""}
56
+ </div>
53
57
  ${type === "page" || type === "view" ? buildMinRoleSelect(node) : ""}
54
58
  </div>
55
59
  </div>
@@ -70,26 +74,35 @@ function buildPreview(node) {
70
74
  "CSRF-Token": _sc_globalCsrf,
71
75
  },
72
76
  success: (res) => {
73
- $(`#${previewId}`).html(`
77
+ $(`[id='${previewId}']`).html(`
74
78
  <div
75
79
  id="preview_wrapper"
76
- style="min-height: 70px;"
80
+ style="min-height: 70px; pointer-events: none;"
77
81
  >
78
82
  ${res}
79
83
  </div></div></div>`);
80
- const previewDiv = $(`#${previewId}`);
84
+ const previewDiv = $(`[id='${previewId}']`);
81
85
  const pos = previewDiv.position();
82
86
  const cssBase = `
87
+ max-height: ${maxPreviewHeight};
88
+ pointer-events: none;
83
89
  position: absolute; top: ${pos.top}px; left: ${pos.left}px;
84
90
  width: ${previewDiv.width()}px; height: ${previewDiv.height() + 12}px;`;
85
- $(`#${previewId}`).after(`
86
- <div
91
+ const previewLabelId = `${previewId}_label`;
92
+ const previewOverlayId = `${previewId}_overlay`;
93
+ $(`[id='${previewLabelId}']`).remove();
94
+ $(`[id='${previewOverlayId}']`).remove();
95
+ $(`[id='${previewId}']`).after(`
96
+ <div
97
+ id="${previewOverlayId}"
87
98
  style="${cssBase}
88
99
  background-color: black; opacity: 0.1;
89
100
  z-index: 10;"
90
101
  >
91
102
  </div>
92
- <div style="${cssBase} opacity: 0.5;">
103
+ <div
104
+ id="${previewLabelId}"
105
+ style="${cssBase} opacity: 0.5;">
93
106
  <h2 class="preview-text fw-bold text-danger">
94
107
  Preview
95
108
  </h2>
@@ -105,7 +118,7 @@ function buildPreview(node) {
105
118
  function buildPreviewDiv(node) {
106
119
  const previewId = `preview_${node.id()}`;
107
120
  return `
108
- <div class="my-2" id="${previewId}" style="min-height: 70px;">
121
+ <div class="my-2" id="${previewId}" style="min-height: 70px; max-height: ${maxPreviewHeight}; overflow: scroll;">
109
122
  <div style="opacity: 0.5;">
110
123
  <h2>
111
124
  <span class="fw-bold text-danger">Preview</span>
@@ -36,11 +36,11 @@ function add_repeater(nm) {
36
36
  newe.appendTo($("div.repeats-" + nm));
37
37
  }
38
38
 
39
- const _apply_showif_plugins = []
39
+ const _apply_showif_plugins = [];
40
40
 
41
- const add_apply_showif_plugin = p => {
42
- _apply_showif_plugins.push(p)
43
- }
41
+ const add_apply_showif_plugin = (p) => {
42
+ _apply_showif_plugins.push(p);
43
+ };
44
44
  function apply_showif() {
45
45
  $("[data-show-if]").each(function (ix, element) {
46
46
  var e = $(element);
@@ -92,7 +92,8 @@ function apply_showif() {
92
92
  } else {
93
93
  e.append(
94
94
  $(
95
- `<option ${`${current}` === `${o.value}` ? "selected" : ""
95
+ `<option ${
96
+ `${current}` === `${o.value}` ? "selected" : ""
96
97
  } value="${o.value}">${o.label}</option>`
97
98
  )
98
99
  );
@@ -117,76 +118,127 @@ function apply_showif() {
117
118
  e.attr("data-selected", ec.target.value);
118
119
  });
119
120
 
120
- const currentOptionsSet = e.prop('data-fetch-options-current-set')
121
+ const currentOptionsSet = e.prop("data-fetch-options-current-set");
121
122
  if (currentOptionsSet === qs) return;
122
123
 
123
124
  const activate = (success, qs) => {
124
125
  e.empty();
125
- e.prop('data-fetch-options-current-set', qs)
126
+ e.prop("data-fetch-options-current-set", qs);
126
127
  if (!dynwhere.required) e.append($(`<option></option>`));
127
128
  let currentDataOption = undefined;
128
- const dataOptions = []
129
+ const dataOptions = [];
129
130
  success.forEach((r) => {
130
131
  const label = dynwhere.label_formula
131
132
  ? new Function(
132
- `{${Object.keys(r).join(",")}}`,
133
- "return " + dynwhere.label_formula
134
- )(r)
135
- : r[dynwhere.summary_field]
136
- const value = r[dynwhere.refname]
137
- const selected = `${current}` === `${r[dynwhere.refname]}`
133
+ `{${Object.keys(r).join(",")}}`,
134
+ "return " + dynwhere.label_formula
135
+ )(r)
136
+ : r[dynwhere.summary_field];
137
+ const value = r[dynwhere.refname];
138
+ const selected = `${current}` === `${r[dynwhere.refname]}`;
138
139
  dataOptions.push({ text: label, value });
139
140
  if (selected) currentDataOption = value;
140
- const html = `<option ${selected ? "selected" : ""
141
- } value="${value}">${label}</option>`
142
- e.append(
143
- $(html)
144
- );
141
+ const html = `<option ${
142
+ selected ? "selected" : ""
143
+ } value="${value}">${label}</option>`;
144
+ e.append($(html));
145
145
  });
146
- element.dispatchEvent(new Event('RefreshSelectOptions'))
146
+ element.dispatchEvent(new Event("RefreshSelectOptions"));
147
147
  if (e.hasClass("selectized") && $().selectize) {
148
148
  e.selectize()[0].selectize.clearOptions();
149
149
  e.selectize()[0].selectize.addOption(dataOptions);
150
150
  if (typeof currentDataOption !== "undefined")
151
151
  e.selectize()[0].selectize.setValue(currentDataOption);
152
-
153
152
  }
154
- }
153
+ };
155
154
 
156
- const cache = e.prop('data-fetch-options-cache') || {}
155
+ const cache = e.prop("data-fetch-options-cache") || {};
157
156
  if (cache[qs]) {
158
- activate(cache[qs], qs)
157
+ activate(cache[qs], qs);
159
158
  } else
160
159
  $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
161
160
  if (resp.success) {
162
- activate(resp.success, qs)
163
- const cacheNow = e.prop('data-fetch-options-cache') || {}
164
- e.prop('data-fetch-options-cache', { ...cacheNow, [qs]: resp.success })
161
+ activate(resp.success, qs);
162
+ const cacheNow = e.prop("data-fetch-options-cache") || {};
163
+ e.prop("data-fetch-options-cache", {
164
+ ...cacheNow,
165
+ [qs]: resp.success,
166
+ });
165
167
  }
166
168
  });
167
169
  });
168
170
 
169
171
  $("[data-source-url]").each(function (ix, element) {
170
172
  const e = $(element);
171
- const rec = get_form_record(e);
173
+ const rec0 = get_form_record(e);
174
+
175
+ const relevantFieldsStr = e.attr("data-relevant-fields");
176
+ let rec;
177
+ if (relevantFieldsStr) {
178
+ rec = {};
179
+ relevantFieldsStr.split(",").forEach((k) => {
180
+ rec[k] = rec0[k];
181
+ });
182
+ } else rec = rec0;
183
+ const recS = JSON.stringify(rec);
184
+
185
+ const shown = e.prop("data-source-url-current");
186
+ if (shown === recS) return;
187
+
188
+ const cache = e.prop("data-source-url-cache") || {};
189
+
190
+ const activate_onchange_coldef = () => {
191
+ e.closest(".form-namespace")
192
+ .find("input,select, textarea")
193
+ .on("change", (ec) => {
194
+ const $ec = $(ec.target);
195
+ const k = $ec.attr("name");
196
+ if (!k || k === "_columndef") return;
197
+ const v = ec.target.value;
198
+ const $def = e
199
+ .closest(".form-namespace")
200
+ .find("input[name=_columndef]");
201
+ const def = JSON.parse($def.val());
202
+ def[k] = v;
203
+ $def.val(JSON.stringify(def));
204
+ });
205
+ };
206
+
207
+ if (typeof cache[recS] !== "undefined") {
208
+ e.html(cache[recS]);
209
+ activate_onchange_coldef();
210
+ return;
211
+ }
172
212
  ajax_post_json(e.attr("data-source-url"), rec, {
173
213
  success: (data) => {
174
214
  e.html(data);
215
+ const cacheNow = e.prop("data-source-url-cache") || {};
216
+ e.prop("data-source-url-cache", {
217
+ ...cacheNow,
218
+ [recS]: data,
219
+ });
220
+ e.prop("data-source-url-current", recS);
221
+ activate_onchange_coldef();
175
222
  },
176
223
  error: (err) => {
177
224
  console.error(err);
225
+ const cacheNow = e.prop("data-source-url-cache") || {};
226
+ e.prop("data-source-url-cache", {
227
+ ...cacheNow,
228
+ [recS]: "",
229
+ });
178
230
  e.html("");
179
231
  },
180
232
  });
181
233
  });
182
- _apply_showif_plugins.forEach(p => p())
234
+ _apply_showif_plugins.forEach((p) => p());
183
235
  }
184
236
 
185
237
  function splitTargetMatch(elemValue, target, keySpec) {
186
238
  if (!elemValue) return false;
187
- const [fld, keySpec1] = keySpec.split("|_")
188
- const [sep, pos] = keySpec1.split("_")
189
- const elemValueShort = elemValue.split(sep)[pos]
239
+ const [fld, keySpec1] = keySpec.split("|_");
240
+ const [sep, pos] = keySpec1.split("_");
241
+ const elemValueShort = elemValue.split(sep)[pos];
190
242
  return elemValueShort === target;
191
243
  }
192
244
 
@@ -195,7 +247,7 @@ function get_form_record(e, select_labels) {
195
247
  e.closest(".form-namespace")
196
248
  .find("input[name],select[name]")
197
249
  .each(function () {
198
- const name = $(this).attr("data-fieldname") || $(this).attr("name")
250
+ const name = $(this).attr("data-fieldname") || $(this).attr("name");
199
251
  if (select_labels && $(this).prop("tagName").toLowerCase() === "select")
200
252
  rec[name] = $(this).find("option:selected").text();
201
253
  else if ($(this).prop("type") === "checkbox")
@@ -508,14 +560,16 @@ function notifyAlert(note, spin) {
508
560
  }
509
561
 
510
562
  $("#alerts-area")
511
- .append(`<div class="alert alert-${type} alert-dismissible fade show ${spin ? "d-flex align-items-center" : ""
512
- }" role="alert">
563
+ .append(`<div class="alert alert-${type} alert-dismissible fade show ${
564
+ spin ? "d-flex align-items-center" : ""
565
+ }" role="alert">
513
566
  ${txt}
514
- ${spin
515
- ? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
516
- : `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
567
+ ${
568
+ spin
569
+ ? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
570
+ : `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
517
571
  </button>`
518
- }
572
+ }
519
573
  </div>`);
520
574
  }
521
575
 
@@ -532,8 +586,9 @@ function common_done(res, isWeb = true) {
532
586
  (isWeb ? location : parent.location).reload(); //TODO notify to cookie if reload or goto
533
587
  }
534
588
  if (res.download) {
535
- const dataurl = `data:${res.download.mimetype || "application/octet-stream"
536
- };base64,${res.download.blob}`;
589
+ const dataurl = `data:${
590
+ res.download.mimetype || "application/octet-stream"
591
+ };base64,${res.download.blob}`;
537
592
  fetch(dataurl)
538
593
  .then((res) => res.blob())
539
594
  .then((blob) => {
@@ -553,15 +608,19 @@ function common_done(res, isWeb = true) {
553
608
  else if (res.goto) {
554
609
  if (res.target === "_blank") window.open(res.goto, "_blank").focus();
555
610
  else {
556
- const prev = new URL(window.location.href)
557
- const next = new URL(res.goto, prev.origin)
611
+ const prev = new URL(window.location.href);
612
+ const next = new URL(res.goto, prev.origin);
558
613
  window.location.href = res.goto;
559
- if (prev.origin === next.origin && prev.pathname === next.pathname && next.hash !== prev.hash)
560
- location.reload()
614
+ if (
615
+ prev.origin === next.origin &&
616
+ prev.pathname === next.pathname &&
617
+ next.hash !== prev.hash
618
+ )
619
+ location.reload();
561
620
  }
562
621
  }
563
622
  if (res.popup) {
564
- ajax_modal(res.popup)
623
+ ajax_modal(res.popup);
565
624
  }
566
625
  }
567
626
 
@@ -572,7 +631,9 @@ const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
572
631
  const $e = form.find(`input[name="${k}_${ix}"]`);
573
632
  if ($e.length) $e.val(v);
574
633
  else {
575
- const $ne = $(`<input type="hidden" data-repeater-ix="${ix}" name="${k}_${ix}"></input>`);
634
+ const $ne = $(
635
+ `<input type="hidden" data-repeater-ix="${ix}" name="${k}_${ix}"></input>`
636
+ );
576
637
  $ne.val(v);
577
638
  form.append($ne);
578
639
  }
@@ -700,9 +761,9 @@ function room_older(viewname, room_id, btn) {
700
761
  function init_room(viewname, room_id) {
701
762
  const socket = parent?.config?.server_path
702
763
  ? io(parent.config.server_path, {
703
- query: `jwt=${localStorage.getItem("auth_jwt")}`,
704
- transports: ["websocket"],
705
- })
764
+ query: `jwt=${localStorage.getItem("auth_jwt")}`,
765
+ transports: ["websocket"],
766
+ })
706
767
  : io({ transports: ["websocket"] });
707
768
 
708
769
  socket.emit("join_room", [viewname, room_id]);
@@ -735,32 +796,31 @@ function cancel_form(form) {
735
796
  }
736
797
 
737
798
  function split_paste_handler(e) {
738
- let clipboardData = e.clipboardData || window.clipboardData || e.originalEvent.clipboardData;
799
+ let clipboardData =
800
+ e.clipboardData || window.clipboardData || e.originalEvent.clipboardData;
739
801
 
740
- const lines = clipboardData.getData('text').split(/\r\n/g)
802
+ const lines = clipboardData.getData("text").split(/\r\n/g);
741
803
 
742
804
  // do normal thing if not multiline - do not interfere with ordinary copy paste
743
805
  if (lines.length < 2) return;
744
806
  e.preventDefault();
745
- const form = $(e.target).closest('form')
807
+ const form = $(e.target).closest("form");
746
808
 
747
809
  let matched = false;
748
810
 
749
- form.find('input:not(:disabled):not([readonly]):not(:hidden)').each(function (ix, element) {
750
- if (!matched && element === e.target) matched = true;
751
- if (matched && lines.length > 0) {
752
- const $elem = $(element)
753
- if (ix === 0 && $elem.attr("type") !== "number") {
754
- //const existing = $elem.val()
755
- //const pasted =
756
- $elem.val(lines.shift())
757
-
758
- } else
759
- $elem.val(lines.shift())
760
- }
761
- })
762
-
763
-
811
+ form
812
+ .find("input:not(:disabled):not([readonly]):not(:hidden)")
813
+ .each(function (ix, element) {
814
+ if (!matched && element === e.target) matched = true;
815
+ if (matched && lines.length > 0) {
816
+ const $elem = $(element);
817
+ if (ix === 0 && $elem.attr("type") !== "number") {
818
+ //const existing = $elem.val()
819
+ //const pasted =
820
+ $elem.val(lines.shift());
821
+ } else $elem.val(lines.shift());
822
+ }
823
+ });
764
824
  }
765
825
 
766
826
  function is_paging_param(key) {
@@ -364,3 +364,9 @@ table.table-inner-grid td {
364
364
  margin-left: 30px;
365
365
  margin-top: -5px;
366
366
  }
367
+
368
+ .accordion-button:after {
369
+ order: -1;
370
+ margin-left: 0;
371
+ margin-right: 0.5em;
372
+ }
@@ -202,6 +202,19 @@ function view_post(viewname, route, data, onDone) {
202
202
  });
203
203
  }
204
204
  let logged_errors = [];
205
+ let error_catcher_enabled = false;
206
+ function enable_error_catcher() {
207
+ if (error_catcher_enabled) return;
208
+ document.addEventListener(
209
+ "DOMContentLoaded",
210
+ function () {
211
+ window.onerror = globalErrorCatcher;
212
+ },
213
+ false
214
+ );
215
+ error_catcher_enabled = true;
216
+ }
217
+
205
218
  function globalErrorCatcher(message, source, lineno, colno, error) {
206
219
  if (error && error.preventDefault) error.preventDefault();
207
220
  if (logged_errors.includes(message)) return;
@@ -260,16 +273,22 @@ function ajax_modal(url, opts = {}) {
260
273
  if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
261
274
  else $("#scmodal").removeClass("no-submit-reload");
262
275
  $.ajax(url, {
276
+ headers: {
277
+ SaltcornModalRequest: "true",
278
+ },
263
279
  success: function (res, textStatus, request) {
264
280
  var title = request.getResponseHeader("Page-Title");
281
+ var width = request.getResponseHeader("SaltcornModalWidth");
282
+ if (width) $(".modal-dialog").css("max-width", width);
283
+ else $(".modal-dialog").css("max-width", "");
265
284
  if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
266
285
  $("#scmodal .modal-body").html(res);
267
286
  $("#scmodal").prop("data-modal-state", url);
268
287
  new bootstrap.Modal($("#scmodal")).show();
269
288
  initialize_page();
270
- (opts.onOpen || function () { })(res);
289
+ (opts.onOpen || function () {})(res);
271
290
  $("#scmodal").on("hidden.bs.modal", function (e) {
272
- (opts.onClose || function () { })(res);
291
+ (opts.onClose || function () {})(res);
273
292
  $("body").css("overflow", "");
274
293
  });
275
294
  },
@@ -278,7 +297,7 @@ function ajax_modal(url, opts = {}) {
278
297
 
279
298
  function saveAndContinue(e, k) {
280
299
  var form = $(e).closest("form");
281
- const valres = form[0].reportValidity()
300
+ const valres = form[0].reportValidity();
282
301
  if (!valres) return;
283
302
  submitWithEmptyAction(form[0]);
284
303
  var url = form.attr("action");
@@ -323,7 +342,7 @@ function applyViewConfig(e, url, k) {
323
342
  "CSRF-Token": _sc_globalCsrf,
324
343
  },
325
344
  data: JSON.stringify(cfg),
326
- error: function (request) { },
345
+ error: function (request) {},
327
346
  success: function (res) {
328
347
  k && k(res);
329
348
  !k && updateViewPreview();
@@ -344,10 +363,14 @@ function updateViewPreview() {
344
363
  "CSRF-Token": _sc_globalCsrf,
345
364
  },
346
365
 
347
- error: function (request) { },
366
+ error: function (request) {},
348
367
  success: function (res) {
349
368
  $preview.css({ opacity: 1.0 });
350
369
 
370
+ //disable functions preview migght try to call
371
+ set_state_field = () => {};
372
+ set_state_fields = () => {};
373
+
351
374
  //disable elements in preview
352
375
  $preview.html(res);
353
376
  $preview.find("a").attr("href", "#");
@@ -357,11 +380,6 @@ function updateViewPreview() {
357
380
 
358
381
  $preview.find("textarea").attr("disabled", true);
359
382
  $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
383
  },
366
384
  });
367
385
  }
@@ -596,15 +614,13 @@ function build_mobile_app(button) {
596
614
  localStorage.setItem("sidebarClosed", `${closed}`);
597
615
  });
598
616
  }
599
- })()
600
-
601
-
617
+ })() +
602
618
  /*
603
619
  https://github.com/jeffdavidgreen/bootstrap-html5-history-tabs/blob/master/bootstrap-history-tabs.js
604
620
  Copyright (c) 2015 Jeff Green
605
621
  */
606
622
 
607
- + (function ($) {
623
+ (function ($) {
608
624
  "use strict";
609
625
  $.fn.historyTabs = function () {
610
626
  var that = this;
@@ -619,21 +635,24 @@ function build_mobile_app(button) {
619
635
  $(element).on("show.bs.tab", function () {
620
636
  var stateObject = { url: $(this).attr("href") };
621
637
 
622
- if (window.location.hash && stateObject.url !== window.location.hash) {
638
+ if (
639
+ window.location.hash &&
640
+ stateObject.url !== window.location.hash
641
+ ) {
623
642
  window.history.pushState(
624
643
  stateObject,
625
644
  document.title,
626
645
  window.location.pathname +
627
- window.location.search +
628
- $(this).attr("href")
646
+ window.location.search +
647
+ $(this).attr("href")
629
648
  );
630
649
  } else {
631
650
  window.history.replaceState(
632
651
  stateObject,
633
652
  document.title,
634
653
  window.location.pathname +
635
- window.location.search +
636
- $(this).attr("href")
654
+ window.location.search +
655
+ $(this).attr("href")
637
656
  );
638
657
  }
639
658
  });
@@ -649,4 +668,33 @@ function build_mobile_app(button) {
649
668
 
650
669
  // Copyright (c) 2011 Marcus Ekwall, http://writeless.se/
651
670
  // https://github.com/mekwall/jquery-throttle
652
- (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);
671
+ (function (a) {
672
+ var b = a.jQuery || a.me || (a.me = {}),
673
+ i = function (e, f, g, h, c, a) {
674
+ f || (f = 100);
675
+ var d = !1,
676
+ j = !1,
677
+ i = typeof g === "function",
678
+ l = function (a, b) {
679
+ d = setTimeout(function () {
680
+ d = !1;
681
+ if (h || c) e.apply(a, b), c && (j = +new Date());
682
+ i && g.apply(a, b);
683
+ }, f);
684
+ },
685
+ k = function () {
686
+ if (!d || a) {
687
+ if (!d && !h && (!c || +new Date() - j > f))
688
+ e.apply(this, arguments), c && (j = +new Date());
689
+ (a || !c) && clearTimeout(d);
690
+ l(this, arguments);
691
+ }
692
+ };
693
+ if (b.guid) k.guid = e.guid = e.guid || b.guid++;
694
+ return k;
695
+ };
696
+ b.throttle = i;
697
+ b.debounce = function (a, b, g, h, c) {
698
+ return i(a, b, g, h, c, !0);
699
+ };
700
+ })(this);