@olenbetong/appframe-core 2.0.3 → 2.1.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 (42) hide show
  1. package/dist/esm/af.common.js +115 -293
  2. package/dist/esm/af.common.legacy.js +119 -290
  3. package/dist/esm/af.common.legacy.min.js +1 -47
  4. package/dist/esm/af.common.legacy.min.js.map +4 -4
  5. package/dist/esm/af.common.min.js +1 -47
  6. package/dist/esm/af.common.min.js.map +4 -4
  7. package/dist/iife/af.common.js +118 -294
  8. package/dist/iife/af.common.legacy.js +122 -293
  9. package/dist/iife/af.common.legacy.min.js +1 -47
  10. package/dist/iife/af.common.legacy.min.js.map +4 -4
  11. package/dist/iife/af.common.min.js +1 -47
  12. package/dist/iife/af.common.min.js.map +4 -4
  13. package/es/SessionHandler.d.ts +2 -7
  14. package/es/SessionHandler.js +34 -135
  15. package/es/UserSession.d.ts +1 -2
  16. package/es/UserSession.js +1 -2
  17. package/es/downloadRequest.js +1 -4
  18. package/es/expose.d.ts +1 -1
  19. package/es/expose.js +4 -5
  20. package/es/getLocalizedString.d.ts +6 -7
  21. package/es/getLocalizedString.js +9 -82
  22. package/es/index.d.ts +3 -2
  23. package/es/index.js +4 -3
  24. package/es/localize.d.ts +14 -0
  25. package/es/localize.js +25 -0
  26. package/es/parseFunc.d.ts +1 -1
  27. package/es/parseFunc.js +1 -1
  28. package/es/requireNamespace.d.ts +1 -1
  29. package/es/requireNamespace.js +1 -7
  30. package/package.json +2 -2
  31. package/src/SessionHandler.ts +38 -156
  32. package/src/UserSession.ts +1 -2
  33. package/src/downloadRequest.ts +1 -4
  34. package/src/expose.ts +4 -5
  35. package/src/getLocalizedString.ts +22 -110
  36. package/src/index.ts +3 -2
  37. package/src/localize.ts +29 -0
  38. package/src/parseFunc.ts +1 -1
  39. package/src/requireNamespace.ts +1 -5
  40. package/es/warn.d.ts +0 -1
  41. package/es/warn.js +0 -4
  42. package/src/warn.ts +0 -4
@@ -1,168 +1,50 @@
1
1
  import mitt, { Emitter, Handler } from "mitt";
2
- import { getLocalizedString } from "./getLocalizedString.js";
3
2
 
4
3
  type SessionHandlerEvent = "onExpired" | "onAuthenticated";
5
4
 
6
5
  export default class SessionHandler {
7
- static messages = {
8
- login: "Log in",
9
- relog: "Session expired - log in"
10
- };
11
-
12
6
  private eventHandler: Emitter<{ onAuthenticated: void; onExpired: void }> = mitt();
13
- private showingDialog = false;
14
- private dialog = null;
15
-
16
- expired(callback: Function) {
17
- af.userSession = af.userSession || {
18
- login: null
19
- };
20
-
21
- if (typeof callback !== "function") {
22
- throw new TypeError("Parameter pCallback passed to the expired function must be a function!");
23
- }
24
-
25
- const once = () => {
26
- this.detachEvent("onAuthenticated", once);
27
- callback();
28
- };
29
-
30
- this.attachEvent("onAuthenticated", once);
31
-
32
- if (this.showingDialog !== true) {
33
- this.showingDialog = true;
34
- const contentContainer = document.createElement("div");
35
- contentContainer.className = "session-expired-container";
36
- contentContainer.innerHTML = `
37
- <div class="session-expired-log-in" style="margin-top: 20px;">
38
- <div class="row">
39
- <div class="sessionExpired">
40
- <h4 class="message">Frakoblet - tidsavbrudd</h4>
41
- <div class="form-group">
42
- <label for="username">${getLocalizedString("Username")}</label>
43
- <input type="text" class="form-control" id="username" autofocus>
44
- </div>
45
- <div class="form-group">
46
- <label for="password">${getLocalizedString("Password")}</label>
47
- <input type="password" autocomplete="password" class="form-control" id="password" >
48
- </div>
49
- <div class="form-group">
50
- <div>
51
- <button type="submit" class="btn btn-default sign-in">
52
- ${getLocalizedString("Log in")}
53
- </button>
54
- </div>
55
- </div>
56
- </div>
57
- </div>
58
- <div class="row">
59
- <div class="" style="display:none; margin: 0px 30px;">
60
- <div class="alert alert-warning" role="alert" style="margin-top:10px;"></div>
61
- </div>
62
- </div>
63
- </div>
64
- `;
65
- const message = af.userSession.login ? SessionHandler.messages.relog : SessionHandler.messages.login;
66
-
67
- const messageElem = contentContainer.querySelector(".message") as HTMLHeadingElement;
68
- const statusElem = contentContainer.querySelector(".alert-warning") as HTMLDivElement;
69
- const userElem = contentContainer.querySelector("input[type=text]") as HTMLInputElement;
70
- const signInElem = contentContainer.querySelector(".sign-in") as HTMLButtonElement;
71
- const passwordElem = contentContainer.querySelector("input[type=password]") as HTMLInputElement;
72
-
73
- if (
74
- messageElem !== null &&
75
- userElem !== null &&
76
- statusElem !== null &&
77
- userElem !== null &&
78
- signInElem !== null &&
79
- passwordElem !== null
80
- ) {
81
- messageElem.innerText = getLocalizedString(message);
82
-
83
- userElem.value = af.userSession.login || "";
84
- userElem.disabled = !!af.userSession.login;
85
- userElem.readOnly = !!af.userSession.login;
86
-
87
- document.body.appendChild(contentContainer);
88
-
89
- const doLogin = async () => {
90
- passwordElem.disabled = true;
91
7
 
92
- const request = new Request("/login", {
93
- body: JSON.stringify({
94
- username: userElem.value,
95
- password: passwordElem.value,
96
- remember: false
97
- }),
98
- headers: {
99
- Accept: "application/json",
100
- "Content-Type": "application/json; charset=utf-8",
101
- "X-Requested-With": "XMLHttpRequest"
102
- },
103
- method: "POST"
104
- });
105
-
106
- try {
107
- const response = await fetch(request);
108
- const json = await response.json();
109
-
110
- if (json.success) {
111
- document.body.removeChild(contentContainer);
112
- this.showingDialog = false;
113
- this.eventHandler.emit("onAuthenticated");
114
-
115
- if (statusElem.parentNode) {
116
- (statusElem.parentNode as HTMLElement).style.display = "none";
117
- }
118
- } else {
119
- throw new Error(json.error);
120
- }
121
- } catch (err) {
122
- if (err && typeof err === "object") {
123
- let error = err as Error;
124
- passwordElem.disabled = false;
125
- passwordElem.classList.toggle("invalid", true);
126
- passwordElem.value = "";
127
- passwordElem.focus();
128
- statusElem.innerText = error.message ? error.message : String(error);
129
-
130
- if (statusElem.parentNode) {
131
- (statusElem.parentNode as HTMLElement).style.display = "initial";
132
- }
133
- }
134
- }
135
- };
136
-
137
- signInElem.addEventListener("click", doLogin);
138
-
139
- passwordElem.addEventListener("keyup", evt => {
140
- if (evt.key === "Enter") {
141
- doLogin();
142
- }
143
- });
144
-
145
- passwordElem.addEventListener("change", evt => {
146
- if ((evt as KeyboardEvent).key === "Enter") {
147
- doLogin();
148
- }
149
- });
150
-
151
- userElem.addEventListener("keyup", evt => {
152
- if (evt.key === "Enter") {
153
- doLogin();
154
- }
155
- });
156
-
157
- userElem.addEventListener("change", evt => {
158
- if ((evt as KeyboardEvent).key === "Enter") {
159
- doLogin();
160
- }
161
- });
8
+ authenticated() {
9
+ this.eventHandler.emit("onAuthenticated");
10
+ }
162
11
 
163
- passwordElem.focus();
12
+ async expired(callback?: (() => void), type?: "RequireTwoFactor"): Promise<void> {
13
+ let authPromise = new Promise<void>((resolve) => {
14
+ let eventHandler = () => {
15
+ this.eventHandler.off("onAuthenticated", eventHandler);
16
+ callback?.();
17
+ resolve();
18
+ }
19
+
20
+ this.eventHandler.on("onAuthenticated", eventHandler);
21
+ });
22
+
23
+ let dialog: HTMLDialogElement | null = document.querySelector("dialog.session-expired-dialog");
24
+ if (!dialog) {
25
+ dialog = document.createElement("dialog");
26
+ dialog.className = "session-expired-dialog";
27
+ dialog.style.display = "flex";
28
+ dialog.style.padding = "0";
29
+ dialog.style.left = "50%";
30
+ dialog.style.transform = "translateX(-50%)";
31
+
32
+ let link = "/login?ReturnUrl=%2fapi/sessionexpired/remove";
33
+ if (type !== "RequireTwoFactor") {
34
+ await fetch("/logout", { method: "POST" });
35
+ } else {
36
+ link = "/login?RequireTwoFactor=1&ReturnUrl=%2fapi/sessionexpired/remove";
37
+ }
38
+
39
+ dialog.innerHTML = `<iframe sandbox="allow-same-origin allow-scripts allow-modals allow-top-navigation-by-user-activation allow-forms" src="${link}" style="border:0;width:min(500px,100vw);height:min(700px, 100vh);"></iframe>`;
40
+ document.body.append(dialog);
41
+ dialog.showModal();
42
+ await authPromise;
43
+ dialog.remove();
44
+ } else {
45
+ await authPromise;
164
46
  }
165
- }
47
+
166
48
  }
167
49
 
168
50
  attachEvent(eventName: SessionHandlerEvent, handler: Handler) {
@@ -1,6 +1,6 @@
1
1
  import SessionHandler from "./SessionHandler.js";
2
2
 
3
- export default class UserSession extends SessionHandler {
3
+ export class UserSession extends SessionHandler {
4
4
  amDesignator: string = "a.m.";
5
5
  dayNames: string[] = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
6
6
  dayNamesShort: string[] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
@@ -20,7 +20,6 @@ export default class UserSession extends SessionHandler {
20
20
  shortDateFormat: string = "dd.MM.yyyy";
21
21
  culture: string = "en-GB";
22
22
  uiculture: string = "en-GB";
23
- expired: (callback: () => void) => void = () => null;
24
23
 
25
24
  constructor(userData: any) {
26
25
  super();
@@ -60,10 +60,7 @@ export async function downloadRequest(request: Request, { fileName, signal, onPr
60
60
  });
61
61
 
62
62
  response = new Response(stream);
63
- } catch (error) {
64
- // eslint-disable-next-line no-console
65
- console.warn("Failed to replace response stream to log download progress.");
66
- }
63
+ } catch (error) {}
67
64
 
68
65
  const blob = await response.blob();
69
66
 
package/src/expose.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { has } from "./has.js";
2
- import parseFunc from "./parseFunc.js";
3
- import requireNamespace from "./requireNamespace.js";
4
- import warn from "./warn.js";
2
+ import { parseFunc } from "./parseFunc.js";
3
+ import { requireNamespace } from "./requireNamespace.js";
5
4
 
6
5
  /**
7
6
  * Expose a value to a potentially deeply nested global variable
8
7
  * @param name {string | object} Either a dot-separated path to the global variable, or an object to expose the value on
9
8
  * @param value {object | Function | Function[]} The value(s) to expose
10
9
  */
11
- export default function expose(name: object | string, value: object | Function | Function[]): void {
10
+ export function expose(name: object | string, value: object | Function | Function[]): void {
12
11
  if (arguments.length !== 2) {
13
12
  throw new Error("Incorrect number of arguments passed to the expose function!");
14
13
  }
@@ -50,7 +49,7 @@ export default function expose(name: object | string, value: object | Function |
50
49
 
51
50
  if (has(namespace, keyName)) {
52
51
  // If class is already defined, warn or show an alert
53
- warn([name, keyName].join(".") + " is already defined!");
52
+ console.warn([name, keyName].join(".") + " is already defined!");
54
53
  } else {
55
54
  namespace[keyName] = value;
56
55
  }
@@ -1,6 +1,6 @@
1
1
  import { has } from "./has";
2
2
 
3
- async function getLocalizedStringAsync(text: string) {
3
+ async function fetchLocalizedString(text: string) {
4
4
  try {
5
5
  let response = await fetch(`/api/user/localize/new/${af.article.id}`, {
6
6
  body: JSON.stringify({ text }),
@@ -19,23 +19,36 @@ async function getLocalizedStringAsync(text: string) {
19
19
 
20
20
  return text;
21
21
  }
22
+ export function getLocalizedStringAsync(string: undefined | null): undefined | null;
23
+ export function getLocalizedStringAsync(string: string): Promise<string>;
24
+ export function getLocalizedStringAsync(string: string | undefined | null) {
25
+ if (!string) return string;
22
26
 
23
- function afGetLocalizedString(string: undefined | null): undefined | null;
24
- function afGetLocalizedString(string: string): string;
25
- function afGetLocalizedString(string: string, wait: false): string;
26
- function afGetLocalizedString(string: string, wait: true): Promise<string>;
27
- function afGetLocalizedString(string: string | undefined | null, wait?: boolean): string | undefined | null | Promise<string> {
28
- if(typeof string === "string" && string.trim().length === 0) {
27
+ return getLocalizedString(string, true);
28
+ }
29
+
30
+ export function getLocalizedString(string: undefined | null): undefined | null;
31
+ export function getLocalizedString(string: string): string;
32
+ export function getLocalizedString(string: string, wait: false): string;
33
+ export function getLocalizedString(string: string, wait: true): Promise<string>;
34
+ export function getLocalizedString(string: undefined | null, wait: true): undefined | null;
35
+ export function getLocalizedString(
36
+ string: string | undefined | null,
37
+ wait?: boolean
38
+ ): string | undefined | null | Promise<string> {
39
+ if (typeof string === "string" && string.trim().length === 0) {
29
40
  return string;
30
41
  }
31
42
 
32
- if (string === undefined || string === '' || string === null) { return; }
43
+ if (string === undefined || string === "" || string === null) {
44
+ return;
45
+ }
33
46
 
34
47
  if (af?.article?.i18n !== undefined) {
35
48
  if (!has(af.article.i18n, string)) {
36
49
  af.article.i18n[string] = string;
37
50
 
38
- let promise = getLocalizedStringAsync(string).then((text) => {
51
+ let promise = fetchLocalizedString(string).then((text) => {
39
52
  af.article.i18n[string] = text;
40
53
  return text;
41
54
  });
@@ -50,104 +63,3 @@ function afGetLocalizedString(string: string | undefined | null, wait?: boolean)
50
63
  return string;
51
64
  }
52
65
  }
53
-
54
- declare global {
55
- interface Window {
56
- __UNLOCALIZED_STRINGS__: Set<string>;
57
- __doNothingWithStrings?: () => Record<string, string>;
58
- __getUnlocalizedStrings?: (forCustomRoute?: boolean) => string;
59
- }
60
- }
61
-
62
- const key = "__UNLOCALIZED_STRINGS__";
63
- const localizeSet = window[key] || new Set();
64
-
65
- if (!window[key]) {
66
- window[key] = localizeSet;
67
- }
68
-
69
- function handleReadyStateChange() {
70
- if (document.readyState !== "loading") {
71
- addDeveloperUtils();
72
- document.removeEventListener("readystatechange", handleReadyStateChange);
73
- }
74
- }
75
-
76
- if (document.readyState !== "loading") {
77
- addDeveloperUtils();
78
- } else {
79
- document.addEventListener("readystatechange", handleReadyStateChange);
80
- }
81
-
82
- function isDeveloper() {
83
- const { af } = window;
84
-
85
- return af && af.userSession && af.userSession.isDeveloper;
86
- }
87
-
88
- function addDeveloperUtils() {
89
- if (isDeveloper()) {
90
- if (typeof window.__doNothingWithStrings === "function") {
91
- const addedStrings = window.__doNothingWithStrings();
92
-
93
- for (let string of Object.keys(addedStrings)) {
94
- localizeSet.add(string);
95
- }
96
- }
97
-
98
- if (!window[key]) {
99
- window[key] = localizeSet;
100
- }
101
-
102
- if (!window.__getUnlocalizedStrings) {
103
- window.__getUnlocalizedStrings = function (forCustomRoute) {
104
- const strings = [];
105
-
106
- for (let string of localizeSet) {
107
- if (string) {
108
- const escaped = string.replace(/'/g, "\\'");
109
- if (forCustomRoute) {
110
- const localized = afGetLocalizedString(string).replaceAll(/'/g, "\\'");
111
- strings.push(`'${escaped}': '${localized}',`);
112
- } else {
113
- strings.push(
114
- `s['${escaped}'] = @Html(@context.AddLocalizableString("${string.replaceAll('"', '\\"')}"));`
115
- );
116
- }
117
- }
118
- }
119
-
120
- if (forCustomRoute) {
121
- return `// Localization hack, since it AddLocalizableString doesn't work properly when custom routes is used
122
- if (af.userSession.uiculture === 'nb-NO') {
123
- const strings = {
124
- ${strings.join("\n\t\t")}
125
- };
126
- af.article.i18n = af.article.i18n || {};
127
- Object.assign(af.article.i18n, strings);
128
- }`;
129
- }
130
-
131
- return `<script type="text/javascript">
132
- function __doNothingWithStrings() {
133
- @{
134
- var context = @Appframe.Web.Context.RequestContext.Current;
135
- }
136
- let s = {};
137
- ${strings.join("\n")}
138
- return s;
139
- }
140
- </script>`;
141
- };
142
- }
143
- }
144
- }
145
-
146
- export function getLocalizedString(string: string) {
147
- const localized = afGetLocalizedString(string);
148
- if (localized === string && !localizeSet.has(string)) {
149
- localizeSet.add(string);
150
- }
151
-
152
- return localized;
153
- }
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import "./errorHandler.js";
2
2
 
3
- import UserSession from "./UserSession.js";
3
+ import { UserSession } from "./UserSession.js";
4
4
 
5
5
  export { default as mitt } from "mitt";
6
6
  export { downloadBlob } from "./downloadBlob.js";
7
7
  export * from "./downloadRequest.js";
8
- export { default as expose } from "./expose.js";
8
+ export { expose } from "./expose.js";
9
9
  export { getLocalizedString } from "./getLocalizedString.js";
10
+ export { localize } from "./localize.js";
10
11
  export * from "./has.js";
11
12
  export { default as localStorage } from "./localStorage.js";
12
13
  export { default as SessionHandler } from "./SessionHandler.js";
@@ -0,0 +1,29 @@
1
+ import { getLocalizedString } from "./getLocalizedString.js";
2
+
3
+ /**
4
+ * Localization function for tagged templates. Replaces the arguments
5
+ * with $n, where n is the index, before asking Appframe to translate
6
+ * the string.
7
+ *
8
+ * For example, if you call localize`Page ${2} or ${5}`, the string
9
+ * "Page $0 of $1" will be sent to Appframe, and for norwegian,
10
+ * "Side 2 av 5" will be returned.
11
+ *
12
+ * @param stringParts
13
+ * @param args
14
+ * @returns Translated string
15
+ */
16
+ export function localize(stringParts: TemplateStringsArray, ...args: any[]): string {
17
+ if (!Array.isArray(stringParts) || stringParts?.length || stringParts.length !== args.length + 1) {
18
+ throw TypeError("Method 'localize' must be used as a tag function");
19
+ }
20
+
21
+ let stringToLocalize = stringParts.map((s, i) => (i === stringParts.length - 1 ? s : `${s}\$${i}`)).join("");
22
+ let localized = getLocalizedString(stringToLocalize);
23
+
24
+ args.forEach((arg, index) => {
25
+ localized = localized.replace("$" + index, arg);
26
+ })
27
+
28
+ return localized;
29
+ }
package/src/parseFunc.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  const funcRegex = /^function(?:\s+([a-zA-Z$_][a-zA-Z0-9$_]*))?\s*\(((?:(?:\s*[a-zA-Z$_][a-zA-Z0-9$_]*)(?:\s*,\s*[a-zA-Z$_][a-zA-Z0-9$_]*)*)?)\s*\)\s*\{((?:.|\n|\r)*)\}$/;
2
2
 
3
- export default function parseFunc(func: Function) {
3
+ export function parseFunc(func: Function) {
4
4
  var match = func.toString().match(funcRegex);
5
5
  if (match === null) {
6
6
  throw new TypeError("Unable to parse function!");
@@ -1,6 +1,6 @@
1
1
  import Namespace from "./Namespace.js";
2
2
 
3
- export default function requireNamespace(
3
+ export function requireNamespace(
4
4
  root: any,
5
5
  namespace: string | Array<string>
6
6
  ): any {
@@ -17,10 +17,6 @@ export default function requireNamespace(
17
17
  }
18
18
 
19
19
  return current;
20
- } else {
21
- throw new TypeError("Invalid type of parameter 'namespace'");
22
20
  }
23
- } else {
24
- throw new TypeError("Invalid type of parameter 'root'");
25
21
  }
26
22
  }
package/es/warn.d.ts DELETED
@@ -1 +0,0 @@
1
- export default function warn(message: string, fallbackToAlert?: boolean): void;
package/es/warn.js DELETED
@@ -1,4 +0,0 @@
1
- /* eslint-disable no-console */
2
- export default function warn(message, fallbackToAlert) {
3
- console.warn(message);
4
- }
package/src/warn.ts DELETED
@@ -1,4 +0,0 @@
1
- /* eslint-disable no-console */
2
- export default function warn(message: string, fallbackToAlert?: boolean) {
3
- console.warn(message);
4
- }