@saltcorn/mobile-app 1.1.0-beta.11 → 1.1.0-beta.13

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 (39) 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 +34 -0
  4. package/package.json +20 -11
  5. package/src/.eslintrc +21 -0
  6. package/src/helpers/api.js +41 -0
  7. package/src/helpers/auth.js +191 -0
  8. package/src/helpers/common.js +175 -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} +169 -335
  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 +14 -9
  25. package/{www/js → src/routing}/routes/sync.js +9 -5
  26. package/{www/js → src/routing}/routes/view.js +16 -11
  27. package/{www/js/routes/common.js → src/routing/utils.js} +15 -13
  28. package/webpack.config.js +31 -0
  29. package/www/data/encoded_site_logo.js +1 -0
  30. package/www/index.html +23 -491
  31. package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +137 -269
  32. package/config.xml +0 -27
  33. package/res/icon/android/icon.png +0 -0
  34. package/res/screen/android/splash-icon.png +0 -0
  35. package/res/screen/ios/Default@2x~universal~anyany.png +0 -0
  36. package/www/js/routes/error.js +0 -5
  37. package/www/js/routes/init.js +0 -76
  38. package/www/js/utils/file_helpers.js +0 -108
  39. package/www/js/utils/offline_mode_helper.js +0 -625
@@ -0,0 +1,102 @@
1
+ import { Filesystem, Encoding, Directory } from "@capacitor/filesystem";
2
+
3
+ export async function writeFile(name, directory, content) {
4
+ try {
5
+ await Filesystem.writeFile({
6
+ path: name,
7
+ data: content,
8
+ directory: directory,
9
+ encoding: Encoding.UTF8,
10
+ });
11
+ } catch (error) {
12
+ console.log("Unable to write file", error);
13
+ throw error;
14
+ }
15
+ }
16
+
17
+ export async function readFile(name, directory) {
18
+ try {
19
+ const contents = await Filesystem.readFile({
20
+ path: name,
21
+ directory: directory,
22
+ encoding: Encoding.UTF8,
23
+ });
24
+ return contents.data;
25
+ } catch (error) {
26
+ console.log("Unable to read file", error);
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ export async function fileExists(name, directory) {
32
+ try {
33
+ await Filesystem.stat({ path: name, directory: directory });
34
+ return true;
35
+ } catch (error) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ export async function writeJSON(name, directory, json) {
41
+ const contents = JSON.stringify(json);
42
+ await writeFile(name, directory, contents);
43
+ }
44
+
45
+ function getDirEntryCordova(directory) {
46
+ return new Promise((resolve, reject) => {
47
+ window.resolveLocalFileSystemURL(
48
+ directory,
49
+ function (fs) {
50
+ resolve(fs);
51
+ },
52
+ function (error) {
53
+ reject(error);
54
+ }
55
+ );
56
+ });
57
+ }
58
+
59
+ export async function readBinaryCordova(fileName, dirName) {
60
+ const dirEntry = await getDirEntryCordova(dirName);
61
+ return new Promise((resolve, reject) => {
62
+ dirEntry.getFile(
63
+ fileName,
64
+ { create: false, exclusive: false },
65
+ function (fileEntry) {
66
+ fileEntry.file(function (file) {
67
+ let reader = new FileReader();
68
+ reader.onloadend = function (e) {
69
+ resolve({ buffer: this.result, file: file });
70
+ };
71
+ reader.readAsArrayBuffer(file);
72
+ });
73
+ },
74
+ function (err) {
75
+ console.log(`unable to read ${fileName}`);
76
+ console.log(err);
77
+ reject(err);
78
+ }
79
+ );
80
+ });
81
+ }
82
+
83
+ // TODO get this working
84
+ // export function normalizeFilePath(filePath) {
85
+ // if (filePath.startsWith("file:///")) {
86
+ // return filePath.replace("file:///storage/emulated/0/", "");
87
+ // }
88
+ // return filePath;
89
+ // }
90
+
91
+ // export async function readBinary(name, directory) {
92
+ // try {
93
+ // const file = await Filesystem.readFile({
94
+ // path: normalizeFilePath(`${directory}/${name}`),
95
+ // directory: Directory.ExternalStorage,
96
+ // });
97
+ // return Uint8Array.from(file.data, (char) => char.charCodeAt(0));
98
+ // } catch (error) {
99
+ // console.log("Unable to read file", error);
100
+ // throw error;
101
+ // }
102
+ // }
@@ -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,7 @@ function currentLocation() {
11
16
  return routingHistory[index].route;
12
17
  }
13
18
 
14
- function currentQuery(skipPosts = false) {
19
+ export function currentQuery(skipPosts = false) {
15
20
  if (routingHistory.length == 0) return undefined;
16
21
  let index = routingHistory.length - 1;
17
22
  if (skipPosts)
@@ -21,7 +26,7 @@ function currentQuery(skipPosts = false) {
21
26
  return routingHistory[index].query;
22
27
  }
23
28
 
24
- function addQueryParam(key, value) {
29
+ export function addQueryParam(key, value) {
25
30
  let query = currentQuery();
26
31
  if (!query) {
27
32
  routingHistory[routingHistory.length - 1].query = `${key}=${value}`;
@@ -32,251 +37,54 @@ function addQueryParam(key, value) {
32
37
  }
33
38
  }
34
39
 
35
- function addRoute(routeEntry) {
40
+ export function addRoute(routeEntry) {
36
41
  routingHistory.push(routeEntry);
37
42
  }
38
43
 
39
- function clearHistory() {
44
+ export function clearHistory() {
40
45
  routingHistory = [];
41
46
  }
42
47
 
43
- function popRoute() {
48
+ export function popRoute() {
44
49
  routingHistory.pop();
45
50
  }
46
51
 
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;
75
- }
76
- }
77
-
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
- }
129
- }
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
- }
140
-
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
- }
170
- }
171
-
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
- }
197
- }
198
-
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 };
208
- }
209
-
210
- async function replaceIframe(content, isFile = false) {
52
+ export async function goBack(steps = 1, exitOnFirstPage = false) {
53
+ const { inLoadState } = saltcorn.data.state.getState().mobileConfig;
54
+ if (inLoadState) return;
211
55
  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
56
+ if (
57
+ routingHistory.length === 0 ||
58
+ (exitOnFirstPage && routingHistory.length === 1)
59
+ ) {
60
+ navigator.app.exitApp();
61
+ } else if (routingHistory.length <= steps) {
62
+ try {
63
+ if (iframe?.contentWindow?.showLoadSpinner)
64
+ iframe.contentWindow.showLoadSpinner();
65
+ routingHistory = [];
66
+ await handleRoute("/");
67
+ } finally {
68
+ if (iframe?.contentWindow?.removeLoadSpinner)
69
+ iframe.contentWindow.removeLoadSpinner();
247
70
  }
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);
71
+ } else {
72
+ try {
73
+ if (iframe?.contentWindow?.showLoadSpinner)
74
+ iframe.contentWindow.showLoadSpinner();
75
+ routingHistory = routingHistory.slice(0, routingHistory.length - steps);
76
+ // don't repeat a post
77
+ if (routingHistory[routingHistory.length - 1].route.startsWith("post/")) {
78
+ routingHistory.pop();
79
+ }
80
+ const newCurrent = routingHistory.pop();
81
+ await handleRoute(newCurrent.route, newCurrent.query);
82
+ } finally {
83
+ if (iframe?.contentWindow?.removeLoadSpinner)
84
+ iframe.contentWindow.removeLoadSpinner();
270
85
  }
271
86
  }
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
87
  }
279
-
280
88
  function clearContentDiv() {
281
89
  const iframe = document.getElementById("content-iframe");
282
90
  if (iframe) {
@@ -286,53 +94,12 @@ function clearContentDiv() {
286
94
  }
287
95
  }
288
96
 
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
97
  function isModalOpen() {
330
98
  const iframe = document.getElementById("content-iframe");
331
99
  if (!iframe) return false;
332
100
  const openModal = iframe.contentWindow.$("#scmodal.modal.show");
333
101
  return openModal.length > 0;
334
102
  }
335
-
336
103
  function replaceModalContent(content) {
337
104
  const iframe = document.getElementById("content-iframe");
338
105
  if (!iframe) throw new Error("No iframe found");
@@ -343,12 +110,7 @@ function replaceModalContent(content) {
343
110
  modalBody.html(content);
344
111
  }
345
112
 
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) {
113
+ export async function handleRoute(route, query, files, data) {
352
114
  const mobileConfig = saltcorn.data.state.getState().mobileConfig;
353
115
  let routeAdded = false;
354
116
  const isModal = isModalOpen();
@@ -359,7 +121,7 @@ async function handleRoute(route, query, files, data) {
359
121
  mobileConfig.allowOfflineMode &&
360
122
  !mobileConfig.isOfflineMode
361
123
  ) {
362
- await offlineHelper.startOfflineMode();
124
+ await startOfflineMode();
363
125
  clearHistory();
364
126
  await gotoEntryView();
365
127
  } else {
@@ -395,7 +157,8 @@ async function handleRoute(route, query, files, data) {
395
157
  );
396
158
  }
397
159
  } else if (page.content) {
398
- if (isModal) replaceModalContent(page.content);
160
+ if (isModal && route?.startsWith("post"))
161
+ replaceModalContent(page.content);
399
162
  else if (!page.replaceIframe)
400
163
  await replaceIframeInnerContent(page.content);
401
164
  else await replaceIframe(page.content, page.isFile);
@@ -425,66 +188,137 @@ async function handleRoute(route, query, files, data) {
425
188
  }
426
189
  }
427
190
 
191
+ function handleOpenModal() {
192
+ const result = { moddalWasOpen: false, noSubmitReload: false };
193
+ const iframe = document.getElementById("content-iframe");
194
+ if (!iframe) return result;
195
+ const openModal = iframe.contentWindow.$("#scmodal.modal.show");
196
+ if (openModal.length === 0) return result;
197
+ result.moddalWasOpen = true;
198
+ iframe.contentWindow.bootstrap.Modal.getInstance(openModal[0]).hide();
199
+ result.noSubmitReload = openModal[0].classList.contains("no-submit-reload");
200
+ return result;
201
+ }
202
+
203
+ export function isHtmlFile() {
204
+ const iframe = document.getElementById("content-iframe");
205
+ return iframe.getAttribute("is-html-file") === "true";
206
+ }
207
+
428
208
  async function reload() {
429
209
  const currentRoute = currentLocation();
430
210
  if (!currentRoute) await gotoEntryView();
431
211
  await handleRoute(currentRoute, currentQuery(true));
432
212
  }
433
213
 
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();
214
+ export async function gotoEntryView() {
215
+ const mobileConfig = saltcorn.data.state.getState().mobileConfig;
216
+ try {
217
+ if (mobileConfig.inErrorState) window.location.reload(true);
218
+ else if (
219
+ mobileConfig.networkState === "none" &&
220
+ mobileConfig.allowOfflineMode &&
221
+ !mobileConfig.isOfflineMode
222
+ ) {
223
+ await startOfflineMode();
224
+ clearHistory();
467
225
  }
226
+ const page = await router.resolve({
227
+ pathname: mobileConfig.entry_point,
228
+ alerts: [],
229
+ });
230
+ addRoute({ route: mobileConfig.entry_point, query: undefined });
231
+ await replaceIframeInnerContent(page.content);
232
+ } catch (error) {
233
+ showAlerts([
234
+ {
235
+ type: "error",
236
+ msg: error.message ? error.message : "An error occured.",
237
+ },
238
+ ]);
468
239
  }
469
240
  }
470
241
 
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);
242
+ export async function replaceIframe(content, isFile = false) {
243
+ const iframe = document.getElementById("content-iframe");
244
+ iframe.srcdoc = content;
245
+ if (isFile) {
246
+ iframe.setAttribute("is-html-file", true);
247
+ await new Promise((resolve, reject) => {
248
+ iframe.onload = () => {
249
+ try {
250
+ const _iframe = document.getElementById("content-iframe");
251
+ const iframeDoc = _iframe.contentWindow.document;
252
+ const baseEl = iframeDoc.createElement("base");
253
+ iframeDoc.head.appendChild(baseEl);
254
+ baseEl.href = "http://localhost";
255
+ const scriptEl = iframeDoc.createElement("script");
256
+ iframeDoc.body.appendChild(scriptEl);
257
+ scriptEl.onload = () => {
258
+ resolve();
259
+ };
260
+ scriptEl.src = "js/iframe_view_utils.js";
261
+ } catch (e) {
262
+ reject(e);
263
+ }
264
+ };
265
+ iframe.onerror = () => {
266
+ reject();
267
+ };
268
+ });
269
+ } else iframe.setAttribute("is-html-file", false);
479
270
  }
480
271
 
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;
272
+ export function addScriptToIframeHead(iframeDoc, script) {
273
+ return new Promise((resolve /*reject*/) => {
274
+ const srcAttr = script.attributes.getNamedItem("src").value;
275
+ const existingScripts = iframeDoc.head.getElementsByTagName("script");
276
+ for (const existing of existingScripts) {
277
+ const existingSrc = existing.attributes.getNamedItem("src");
278
+ if (existingSrc && existingSrc.value === srcAttr) return resolve(); // already there
279
+ }
280
+ const scriptEl = iframeDoc.createElement("script");
281
+ iframeDoc.head.appendChild(scriptEl);
282
+ scriptEl.onload = () => {
283
+ resolve();
284
+ };
285
+ scriptEl.src = srcAttr;
286
+ });
287
+ }
288
+
289
+ export async function replaceIframeInnerContent(content) {
290
+ const iframe = document.getElementById("content-iframe");
291
+ const iframeDocument = iframe.contentWindow.document;
292
+ if (iframe.contentWindow.$) {
293
+ const scmodal = iframe.contentWindow.$("#scmodal");
294
+ if (scmodal.length > 0) {
295
+ scmodal.modal("hide");
296
+ scmodal.remove();
297
+ }
298
+ }
299
+ const modal = iframeDocument.getElementById("scmodal");
300
+ if (modal) modal.remove();
301
+ const innerContentDiv = iframeDocument.getElementById("page-inner-content");
302
+ innerContentDiv.innerHTML = content;
303
+ const scripts = innerContentDiv.getElementsByTagName("script");
304
+ for (const script of scripts) {
305
+ if (script.attributes.getNamedItem("src")) {
306
+ await addScriptToIframeHead(iframe.contentWindow.document, script);
307
+ } else {
308
+ iframe.contentWindow.eval(script.innerHTML);
309
+ }
310
+ }
311
+ iframe.contentWindow.scrollTo(0, 0);
312
+ iframe.contentWindow.initialize_page();
313
+ }
314
+
315
+ export function splitPathQuery(url) {
316
+ let path = url;
317
+ let query = undefined;
318
+ const queryStart = url.indexOf("?");
319
+ if (queryStart > 0) {
320
+ path = url.substring(0, queryStart);
321
+ query = url.substring(queryStart);
322
+ }
323
+ return { path, query };
490
324
  }