@saltcorn/mobile-app 1.1.0-beta.9 → 1.1.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 (40) hide show
  1. package/.babelrc +3 -0
  2. package/build_scripts/modify_android_manifest.js +47 -0
  3. package/build_scripts/modify_gradle_cfg.js +21 -0
  4. package/package.json +21 -12
  5. package/src/.eslintrc +21 -0
  6. package/src/helpers/api.js +43 -0
  7. package/src/helpers/auth.js +191 -0
  8. package/src/helpers/common.js +189 -0
  9. package/{www/js/utils/table_utils.js → src/helpers/db_schema.js} +18 -40
  10. package/src/helpers/file_system.js +102 -0
  11. package/{www/js/utils/global_utils.js → src/helpers/navigation.js} +189 -332
  12. package/src/helpers/offline_mode.js +645 -0
  13. package/src/index.js +20 -0
  14. package/src/init.js +424 -0
  15. package/src/routing/index.js +98 -0
  16. package/{www/js → src/routing}/mocks/request.js +5 -5
  17. package/{www/js → src/routing}/mocks/response.js +1 -1
  18. package/{www/js → src/routing}/routes/api.js +10 -15
  19. package/{www/js → src/routing}/routes/auth.js +12 -6
  20. package/{www/js → src/routing}/routes/delete.js +9 -6
  21. package/{www/js → src/routing}/routes/edit.js +9 -6
  22. package/src/routing/routes/error.js +6 -0
  23. package/{www/js → src/routing}/routes/fields.js +7 -2
  24. package/{www/js → src/routing}/routes/page.js +15 -10
  25. package/{www/js → src/routing}/routes/sync.js +17 -8
  26. package/{www/js → src/routing}/routes/view.js +20 -15
  27. package/{www/js/routes/common.js → src/routing/utils.js} +18 -13
  28. package/unsecure-default-key.jks +0 -0
  29. package/webpack.config.js +31 -0
  30. package/www/data/encoded_site_logo.js +1 -0
  31. package/www/index.html +23 -493
  32. package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +193 -274
  33. package/config.xml +0 -27
  34. package/res/icon/android/icon.png +0 -0
  35. package/res/screen/android/splash-icon.png +0 -0
  36. package/res/screen/ios/Default@2x~universal~anyany.png +0 -0
  37. package/www/js/routes/error.js +0 -5
  38. package/www/js/routes/init.js +0 -76
  39. package/www/js/utils/file_helpers.js +0 -108
  40. package/www/js/utils/offline_mode_helper.js +0 -625
@@ -1,8 +1,13 @@
1
- /*global window, $, offlineHelper, axios, write, cordova, router, getDirEntry, saltcorn, document, FileReader, navigator, splashConfig, i18next*/
1
+ /*global saltcorn*/
2
+ import i18next from "i18next";
2
3
 
3
- let routingHistory = [];
4
+ import { router } from "../routing/index";
5
+ import { startOfflineMode } from "./offline_mode";
6
+ import { showAlerts } from "./common";
4
7
 
5
- function currentLocation() {
8
+ export let routingHistory = [];
9
+
10
+ export function currentLocation() {
6
11
  if (routingHistory.length == 0) return undefined;
7
12
  let index = routingHistory.length - 1;
8
13
  while (index > 0 && routingHistory[index].route.startsWith("post/")) {
@@ -11,7 +16,13 @@ function currentLocation() {
11
16
  return routingHistory[index].route;
12
17
  }
13
18
 
14
- function currentQuery(skipPosts = false) {
19
+ export function currentUrl() {
20
+ const query = currentQuery();
21
+ const safeQuery = query ? (query.startsWith("?") ? query : `?${query}`) : "";
22
+ return `${currentLocation()}${safeQuery}`;
23
+ }
24
+
25
+ export function currentQuery(skipPosts = false) {
15
26
  if (routingHistory.length == 0) return undefined;
16
27
  let index = routingHistory.length - 1;
17
28
  if (skipPosts)
@@ -21,7 +32,7 @@ function currentQuery(skipPosts = false) {
21
32
  return routingHistory[index].query;
22
33
  }
23
34
 
24
- function addQueryParam(key, value) {
35
+ export function addQueryParam(key, value) {
25
36
  let query = currentQuery();
26
37
  if (!query) {
27
38
  routingHistory[routingHistory.length - 1].query = `${key}=${value}`;
@@ -32,251 +43,71 @@ function addQueryParam(key, value) {
32
43
  }
33
44
  }
34
45
 
35
- function addRoute(routeEntry) {
36
- routingHistory.push(routeEntry);
37
- }
38
-
39
- function clearHistory() {
40
- routingHistory = [];
41
- }
42
-
43
- function popRoute() {
44
- routingHistory.pop();
45
- }
46
-
47
- async function apiCall({ method, path, params, body, responseType, timeout }) {
48
- const config =
49
- typeof saltcorn !== "undefined"
50
- ? saltcorn.data.state.getState().mobileConfig
51
- : splashConfig;
52
- const serverPath = config.server_path;
53
- const url = `${serverPath}${path}`;
54
- const headers = {
55
- "X-Requested-With": "XMLHttpRequest",
56
- "X-Saltcorn-Client": "mobile-app",
57
- };
58
- if (config.tenantAppName) headers["X-Saltcorn-App"] = config.tenantAppName;
59
- const token = config.jwt;
60
- if (token) headers.Authorization = `jwt ${token}`;
61
- try {
62
- const result = await axios({
63
- url: url,
64
- method,
65
- params,
66
- headers,
67
- responseType: responseType ? responseType : "json",
68
- data: body,
69
- timeout: timeout ? timeout : 0,
70
- });
71
- return result;
72
- } catch (error) {
73
- error.message = `Unable to call ${method} ${url}:\n${error.message}`;
74
- throw error;
46
+ export function setAnchor(anchor) {
47
+ if (routingHistory.length === 0)
48
+ throw new Error("No current route to set anchor");
49
+ else {
50
+ const current = routingHistory[routingHistory.length - 1];
51
+ current.anchor = anchor;
75
52
  }
76
53
  }
77
54
 
78
- function clearAlerts() {
79
- const iframe = document.getElementById("content-iframe");
80
- const alertsArea =
81
- iframe.contentWindow.document.getElementById("toasts-area");
82
- alertsArea.innerHTML = "";
83
- }
84
-
85
- function showAlerts(alerts, toast = true) {
86
- if (typeof saltcorn === "undefined") {
87
- console.log("Not yet initalized.");
88
- console.log(alerts);
89
- } else {
90
- const iframe = document.getElementById("content-iframe");
91
- let area = iframe.contentWindow.document.getElementById(
92
- toast ? "toasts-area" : "top-alert"
93
- );
94
- if (!area) {
95
- const areaHtml = `<div class="container">
96
- <div
97
- id="toasts-area"
98
- class="toast-container position-fixed bottom-0 start-50 p-0"
99
- style="z-index: 9999;"
100
- aria-live="polite"
101
- aria-atomic="true">
102
- </div>
103
- </div>`;
104
- iframe.contentWindow.document
105
- .getElementById("page-inner-content")
106
- .insertAdjacentHTML("beforeend", areaHtml);
107
- area = iframe.contentWindow.document.getElementById(
108
- toast ? "toasts-area" : "top-alert"
109
- );
110
- }
111
-
112
- const successIds = [];
113
- area.innerHTML = "";
114
- for (const { type, msg } of alerts) {
115
- if (toast) {
116
- const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
117
- area.innerHTML += saltcorn.markup.toast(type, msg, rndid);
118
- if (type === "success") successIds.push(rndid);
119
- } else area.innerHTML += saltcorn.markup.alert(type, msg);
120
- }
121
- if (successIds.length > 0) {
122
- setTimeout(() => {
123
- for (const id of successIds) {
124
- const toastEl = iframe.contentWindow.document.getElementById(id);
125
- if (toastEl) $(toastEl).removeClass("show");
126
- }
127
- }, 5000);
128
- }
55
+ export function getAnchor() {
56
+ if (routingHistory.length === 0) return undefined;
57
+ else {
58
+ const current = routingHistory[routingHistory.length - 1];
59
+ return current.anchor;
129
60
  }
130
- return true;
131
- }
132
-
133
- function clearTopAlerts() {
134
- const iframe = document.getElementById("content-iframe");
135
- const area = iframe.contentWindow.document.getElementById("alerts-area");
136
- if (area) area.innerHTML = "";
137
- const topAlert = iframe.contentWindow.document.getElementById("top-alert");
138
- if (topAlert) topAlert.innerHTML = "";
139
61
  }
140
62
 
141
- // TODO combine with loadEncodedFile
142
- async function loadFileAsText(fileId) {
143
- try {
144
- const response = await apiCall({
145
- method: "GET",
146
- path: `/files/download/${fileId}`,
147
- responseType: "blob",
148
- });
149
- return new Promise((resolve, reject) => {
150
- const reader = new FileReader();
151
- reader.onloadend = () => {
152
- return resolve(reader.result);
153
- };
154
- reader.onerror = (error) => {
155
- return reject(error);
156
- };
157
- reader.readAsText(response.data);
158
- });
159
- } catch (error) {
160
- if (
161
- !showAlerts([
162
- {
163
- type: "error",
164
- msg: error.message ? error.message : "An error occured.",
165
- },
166
- ])
167
- );
168
- throw error;
169
- }
63
+ export function addRoute(routeEntry) {
64
+ routingHistory.push(routeEntry);
170
65
  }
171
66
 
172
- async function loadEncodedFile(fileId) {
173
- try {
174
- const response = await apiCall({
175
- method: "GET",
176
- path: `/files/download/${fileId}`,
177
- responseType: "blob",
178
- });
179
- return new Promise((resolve, reject) => {
180
- const reader = new FileReader();
181
- reader.onloadend = () => {
182
- return resolve(reader.result);
183
- };
184
- reader.onerror = (error) => {
185
- return reject(error);
186
- };
187
- reader.readAsDataURL(response.data);
188
- });
189
- } catch (error) {
190
- showAlerts([
191
- {
192
- type: "error",
193
- msg: error.message ? error.message : "An error occured.",
194
- },
195
- ]);
196
- }
67
+ export function clearHistory() {
68
+ routingHistory = [];
197
69
  }
198
70
 
199
- function splitPathQuery(url) {
200
- let path = url;
201
- let query = undefined;
202
- const queryStart = url.indexOf("?");
203
- if (queryStart > 0) {
204
- path = url.substring(0, queryStart);
205
- query = url.substring(queryStart);
206
- }
207
- return { path, query };
71
+ export function popRoute() {
72
+ routingHistory.pop();
208
73
  }
209
74
 
210
- async function replaceIframe(content, isFile = false) {
75
+ export async function goBack(steps = 1, exitOnFirstPage = false) {
76
+ const { inLoadState } = saltcorn.data.state.getState().mobileConfig;
77
+ if (inLoadState) return;
211
78
  const iframe = document.getElementById("content-iframe");
212
- iframe.srcdoc = content;
213
- if (isFile) {
214
- iframe.setAttribute("is-html-file", true);
215
- await new Promise((resolve, reject) => {
216
- iframe.onload = () => {
217
- try {
218
- const _iframe = document.getElementById("content-iframe");
219
- const iframeDoc = _iframe.contentWindow.document;
220
- const baseEl = iframeDoc.createElement("base");
221
- iframeDoc.head.appendChild(baseEl);
222
- baseEl.href = "http://localhost";
223
- const scriptEl = iframeDoc.createElement("script");
224
- iframeDoc.body.appendChild(scriptEl);
225
- scriptEl.onload = () => {
226
- resolve();
227
- };
228
- scriptEl.src = "js/utils/iframe_view_utils.js";
229
- } catch (e) {
230
- reject(e);
231
- }
232
- };
233
- iframe.onerror = () => {
234
- reject();
235
- };
236
- });
237
- } else iframe.setAttribute("is-html-file", false);
238
- }
239
-
240
- function addScriptToIframeHead(iframeDoc, script) {
241
- return new Promise((resolve, reject) => {
242
- const srcAttr = script.attributes.getNamedItem("src").value;
243
- const existingScripts = iframeDoc.head.getElementsByTagName("script");
244
- for (const existing of existingScripts) {
245
- const existingSrc = existing.attributes.getNamedItem("src");
246
- if (existingSrc && existingSrc.value === srcAttr) return resolve(); // already there
79
+ if (
80
+ routingHistory.length === 0 ||
81
+ (exitOnFirstPage && routingHistory.length === 1)
82
+ ) {
83
+ navigator.app.exitApp();
84
+ } else if (routingHistory.length <= steps) {
85
+ try {
86
+ if (iframe?.contentWindow?.showLoadSpinner)
87
+ iframe.contentWindow.showLoadSpinner();
88
+ routingHistory = [];
89
+ await handleRoute("/");
90
+ } finally {
91
+ if (iframe?.contentWindow?.removeLoadSpinner)
92
+ iframe.contentWindow.removeLoadSpinner();
247
93
  }
248
- const scriptEl = iframeDoc.createElement("script");
249
- iframeDoc.head.appendChild(scriptEl);
250
- scriptEl.onload = () => {
251
- resolve();
252
- };
253
- scriptEl.src = srcAttr;
254
- });
255
- }
256
-
257
- async function replaceIframeInnerContent(content) {
258
- const iframe = document.getElementById("content-iframe");
259
- const iframeDocument = iframe.contentWindow.document;
260
- const modal = iframeDocument.getElementById("scmodal");
261
- if (modal) modal.remove();
262
- const innerContentDiv = iframeDocument.getElementById("page-inner-content");
263
- innerContentDiv.innerHTML = content;
264
- const scripts = innerContentDiv.getElementsByTagName("script");
265
- for (const script of scripts) {
266
- if (script.attributes.getNamedItem("src")) {
267
- await addScriptToIframeHead(iframe.contentWindow.document, script);
268
- } else {
269
- iframe.contentWindow.eval(script.innerHTML);
94
+ } else {
95
+ try {
96
+ if (iframe?.contentWindow?.showLoadSpinner)
97
+ iframe.contentWindow.showLoadSpinner();
98
+ routingHistory = routingHistory.slice(0, routingHistory.length - steps);
99
+ // don't repeat a post
100
+ if (routingHistory[routingHistory.length - 1].route.startsWith("post/")) {
101
+ routingHistory.pop();
102
+ }
103
+ const newCurrent = routingHistory.pop();
104
+ await handleRoute(newCurrent.route, newCurrent.query);
105
+ } finally {
106
+ if (iframe?.contentWindow?.removeLoadSpinner)
107
+ iframe.contentWindow.removeLoadSpinner();
270
108
  }
271
109
  }
272
- const scmodal = iframe.contentWindow.$("#scmodal");
273
- if (scmodal) {
274
- scmodal.modal("hide");
275
- }
276
- iframe.contentWindow.scrollTo(0, 0);
277
- iframe.contentWindow.initialize_page();
278
110
  }
279
-
280
111
  function clearContentDiv() {
281
112
  const iframe = document.getElementById("content-iframe");
282
113
  if (iframe) {
@@ -286,53 +117,12 @@ function clearContentDiv() {
286
117
  }
287
118
  }
288
119
 
289
- async function gotoEntryView() {
290
- const mobileConfig = saltcorn.data.state.getState().mobileConfig;
291
- try {
292
- if (mobileConfig.inErrorState) window.location.reload(true);
293
- else if (
294
- mobileConfig.networkState === "none" &&
295
- mobileConfig.allowOfflineMode &&
296
- !mobileConfig.isOfflineMode
297
- ) {
298
- await offlineHelper.startOfflineMode();
299
- clearHistory();
300
- }
301
- const page = await router.resolve({
302
- pathname: mobileConfig.entry_point,
303
- alerts: [],
304
- });
305
- addRoute({ route: mobileConfig.entry_point, query: undefined });
306
- await replaceIframeInnerContent(page.content);
307
- } catch (error) {
308
- showAlerts([
309
- {
310
- type: "error",
311
- msg: error.message ? error.message : "An error occured.",
312
- },
313
- ]);
314
- }
315
- }
316
-
317
- function handleOpenModal() {
318
- const result = { moddalWasOpen: false, noSubmitReload: false };
319
- const iframe = document.getElementById("content-iframe");
320
- if (!iframe) return result;
321
- const openModal = iframe.contentWindow.$("#scmodal.modal.show");
322
- if (openModal.length === 0) return result;
323
- result.moddalWasOpen = true;
324
- iframe.contentWindow.bootstrap.Modal.getInstance(openModal[0]).hide();
325
- result.noSubmitReload = openModal[0].classList.contains("no-submit-reload");
326
- return result;
327
- }
328
-
329
120
  function isModalOpen() {
330
121
  const iframe = document.getElementById("content-iframe");
331
122
  if (!iframe) return false;
332
123
  const openModal = iframe.contentWindow.$("#scmodal.modal.show");
333
124
  return openModal.length > 0;
334
125
  }
335
-
336
126
  function replaceModalContent(content) {
337
127
  const iframe = document.getElementById("content-iframe");
338
128
  if (!iframe) throw new Error("No iframe found");
@@ -343,12 +133,7 @@ function replaceModalContent(content) {
343
133
  modalBody.html(content);
344
134
  }
345
135
 
346
- function isHtmlFile() {
347
- const iframe = document.getElementById("content-iframe");
348
- return iframe.getAttribute("is-html-file") === "true";
349
- }
350
-
351
- async function handleRoute(route, query, files, data) {
136
+ export async function handleRoute(route, query, files, data) {
352
137
  const mobileConfig = saltcorn.data.state.getState().mobileConfig;
353
138
  let routeAdded = false;
354
139
  const isModal = isModalOpen();
@@ -359,7 +144,7 @@ async function handleRoute(route, query, files, data) {
359
144
  mobileConfig.allowOfflineMode &&
360
145
  !mobileConfig.isOfflineMode
361
146
  ) {
362
- await offlineHelper.startOfflineMode();
147
+ await startOfflineMode();
363
148
  clearHistory();
364
149
  await gotoEntryView();
365
150
  } else {
@@ -395,7 +180,8 @@ async function handleRoute(route, query, files, data) {
395
180
  );
396
181
  }
397
182
  } else if (page.content) {
398
- if (isModal) replaceModalContent(page.content);
183
+ if (isModal && route?.startsWith("post"))
184
+ replaceModalContent(page.content);
399
185
  else if (!page.replaceIframe)
400
186
  await replaceIframeInnerContent(page.content);
401
187
  else await replaceIframe(page.content, page.isFile);
@@ -425,66 +211,137 @@ async function handleRoute(route, query, files, data) {
425
211
  }
426
212
  }
427
213
 
214
+ function handleOpenModal() {
215
+ const result = { moddalWasOpen: false, noSubmitReload: false };
216
+ const iframe = document.getElementById("content-iframe");
217
+ if (!iframe) return result;
218
+ const openModal = iframe.contentWindow.$("#scmodal.modal.show");
219
+ if (openModal.length === 0) return result;
220
+ result.moddalWasOpen = true;
221
+ iframe.contentWindow.bootstrap.Modal.getInstance(openModal[0]).hide();
222
+ result.noSubmitReload = openModal[0].classList.contains("no-submit-reload");
223
+ return result;
224
+ }
225
+
226
+ export function isHtmlFile() {
227
+ const iframe = document.getElementById("content-iframe");
228
+ return iframe.getAttribute("is-html-file") === "true";
229
+ }
230
+
428
231
  async function reload() {
429
232
  const currentRoute = currentLocation();
430
233
  if (!currentRoute) await gotoEntryView();
431
234
  await handleRoute(currentRoute, currentQuery(true));
432
235
  }
433
236
 
434
- async function goBack(steps = 1, exitOnFirstPage = false) {
435
- const { inLoadState } = saltcorn.data.state.getState().mobileConfig;
436
- if (inLoadState) return;
437
- const iframe = document.getElementById("content-iframe");
438
- if (
439
- routingHistory.length === 0 ||
440
- (exitOnFirstPage && routingHistory.length === 1)
441
- ) {
442
- navigator.app.exitApp();
443
- } else if (routingHistory.length <= steps) {
444
- try {
445
- if (iframe?.contentWindow?.showLoadSpinner)
446
- iframe.contentWindow.showLoadSpinner();
447
- routingHistory = [];
448
- await handleRoute("/");
449
- } finally {
450
- if (iframe?.contentWindow?.removeLoadSpinner)
451
- iframe.contentWindow.removeLoadSpinner();
452
- }
453
- } else {
454
- try {
455
- if (iframe?.contentWindow?.showLoadSpinner)
456
- iframe.contentWindow.showLoadSpinner();
457
- routingHistory = routingHistory.slice(0, routingHistory.length - steps);
458
- // don't repeat a post
459
- if (routingHistory[routingHistory.length - 1].route.startsWith("post/")) {
460
- routingHistory.pop();
461
- }
462
- const newCurrent = routingHistory.pop();
463
- await handleRoute(newCurrent.route, newCurrent.query);
464
- } finally {
465
- if (iframe?.contentWindow?.removeLoadSpinner)
466
- iframe.contentWindow.removeLoadSpinner();
237
+ export async function gotoEntryView() {
238
+ const mobileConfig = saltcorn.data.state.getState().mobileConfig;
239
+ try {
240
+ if (mobileConfig.inErrorState) window.location.reload(true);
241
+ else if (
242
+ mobileConfig.networkState === "none" &&
243
+ mobileConfig.allowOfflineMode &&
244
+ !mobileConfig.isOfflineMode
245
+ ) {
246
+ await startOfflineMode();
247
+ clearHistory();
467
248
  }
249
+ const page = await router.resolve({
250
+ pathname: mobileConfig.entry_point,
251
+ alerts: [],
252
+ });
253
+ addRoute({ route: mobileConfig.entry_point, query: undefined });
254
+ await replaceIframeInnerContent(page.content);
255
+ } catch (error) {
256
+ showAlerts([
257
+ {
258
+ type: "error",
259
+ msg: error.message ? error.message : "An error occured.",
260
+ },
261
+ ]);
468
262
  }
469
263
  }
470
264
 
471
- function errorAlert(error) {
472
- showAlerts([
473
- {
474
- type: "error",
475
- msg: error.message ? error.message : "An error occured.",
476
- },
477
- ]);
478
- console.error(error);
265
+ export async function replaceIframe(content, isFile = false) {
266
+ const iframe = document.getElementById("content-iframe");
267
+ iframe.srcdoc = content;
268
+ if (isFile) {
269
+ iframe.setAttribute("is-html-file", true);
270
+ await new Promise((resolve, reject) => {
271
+ iframe.onload = () => {
272
+ try {
273
+ const _iframe = document.getElementById("content-iframe");
274
+ const iframeDoc = _iframe.contentWindow.document;
275
+ const baseEl = iframeDoc.createElement("base");
276
+ iframeDoc.head.appendChild(baseEl);
277
+ baseEl.href = "http://localhost";
278
+ const scriptEl = iframeDoc.createElement("script");
279
+ iframeDoc.body.appendChild(scriptEl);
280
+ scriptEl.onload = () => {
281
+ resolve();
282
+ };
283
+ scriptEl.src = "js/iframe_view_utils.js";
284
+ } catch (e) {
285
+ reject(e);
286
+ }
287
+ };
288
+ iframe.onerror = () => {
289
+ reject();
290
+ };
291
+ });
292
+ } else iframe.setAttribute("is-html-file", false);
479
293
  }
480
294
 
481
- async function checkJWT(jwt) {
482
- if (jwt && jwt !== "undefined") {
483
- const response = await apiCall({
484
- method: "GET",
485
- path: "/auth/authenticated",
486
- timeout: 10000,
487
- });
488
- return response.data.authenticated;
489
- } else return false;
295
+ export function addScriptToIframeHead(iframeDoc, script) {
296
+ return new Promise((resolve /*reject*/) => {
297
+ const srcAttr = script.attributes.getNamedItem("src").value;
298
+ const existingScripts = iframeDoc.head.getElementsByTagName("script");
299
+ for (const existing of existingScripts) {
300
+ const existingSrc = existing.attributes.getNamedItem("src");
301
+ if (existingSrc && existingSrc.value === srcAttr) return resolve(); // already there
302
+ }
303
+ const scriptEl = iframeDoc.createElement("script");
304
+ iframeDoc.head.appendChild(scriptEl);
305
+ scriptEl.onload = () => {
306
+ resolve();
307
+ };
308
+ scriptEl.src = srcAttr;
309
+ });
310
+ }
311
+
312
+ export async function replaceIframeInnerContent(content) {
313
+ const iframe = document.getElementById("content-iframe");
314
+ const iframeDocument = iframe.contentWindow.document;
315
+ if (iframe.contentWindow.$) {
316
+ const scmodal = iframe.contentWindow.$("#scmodal");
317
+ if (scmodal.length > 0) {
318
+ scmodal.modal("hide");
319
+ scmodal.remove();
320
+ }
321
+ }
322
+ const modal = iframeDocument.getElementById("scmodal");
323
+ if (modal) modal.remove();
324
+ const innerContentDiv = iframeDocument.getElementById("page-inner-content");
325
+ innerContentDiv.innerHTML = content;
326
+ const scripts = innerContentDiv.getElementsByTagName("script");
327
+ for (const script of scripts) {
328
+ if (script.attributes.getNamedItem("src")) {
329
+ await addScriptToIframeHead(iframe.contentWindow.document, script);
330
+ } else {
331
+ iframe.contentWindow.eval(script.innerHTML);
332
+ }
333
+ }
334
+ iframe.contentWindow.scrollTo(0, 0);
335
+ iframe.contentWindow.initialize_page();
336
+ }
337
+
338
+ export function splitPathQuery(url) {
339
+ let path = url;
340
+ let query = undefined;
341
+ const queryStart = url.indexOf("?");
342
+ if (queryStart > 0) {
343
+ path = url.substring(0, queryStart);
344
+ query = url.substring(queryStart);
345
+ }
346
+ return { path, query };
490
347
  }