@olenbetong/appframe-core 1.0.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.
- package/dist/esm/af.common.js +433 -0
- package/dist/esm/af.common.min.js +48 -0
- package/dist/esm/af.common.min.js.map +7 -0
- package/dist/iife/af.common.js +458 -0
- package/dist/iife/af.common.min.js +48 -0
- package/dist/iife/af.common.min.js.map +7 -0
- package/es/Namespace.d.ts +5 -0
- package/es/Namespace.js +3 -0
- package/es/SessionHandler.d.ts +15 -0
- package/es/SessionHandler.js +150 -0
- package/es/UserSession.d.ts +10 -0
- package/es/UserSession.js +13 -0
- package/es/expose.d.ts +6 -0
- package/es/expose.js +54 -0
- package/es/getLocalizedString.d.ts +8 -0
- package/es/getLocalizedString.js +114 -0
- package/es/has.d.ts +1 -0
- package/es/has.js +3 -0
- package/es/index.d.ts +7 -0
- package/es/index.js +7 -0
- package/es/localStorage.d.ts +10 -0
- package/es/localStorage.js +38 -0
- package/es/parseFunc.d.ts +5 -0
- package/es/parseFunc.js +8 -0
- package/es/requireNamespace.d.ts +1 -0
- package/es/requireNamespace.js +23 -0
- package/es/settings.d.ts +2 -0
- package/es/settings.js +27 -0
- package/es/warn.d.ts +1 -0
- package/es/warn.js +12 -0
- package/package.json +38 -0
- package/scripts/import-json.js +5 -0
- package/scripts/replace-version.js +37 -0
- package/src/Namespace.ts +4 -0
- package/src/SessionHandler.ts +175 -0
- package/src/UserSession.ts +16 -0
- package/src/expose.ts +68 -0
- package/src/getLocalizedString.ts +153 -0
- package/src/global.d.ts +56 -0
- package/src/has.ts +6 -0
- package/src/index.ts +7 -0
- package/src/localStorage.ts +49 -0
- package/src/parseFunc.ts +9 -0
- package/src/requireNamespace.ts +26 -0
- package/src/settings.ts +36 -0
- package/src/warn.ts +10 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import mitt, { Emitter, Handler } from "mitt";
|
|
2
|
+
import { getLocalizedString } from "./getLocalizedString.js";
|
|
3
|
+
|
|
4
|
+
type SessionHandlerEvent = "onExpired" | "onAuthenticated";
|
|
5
|
+
|
|
6
|
+
export default class SessionHandler {
|
|
7
|
+
static messages = {
|
|
8
|
+
login: "Log in",
|
|
9
|
+
relog: "Session expired - log in"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
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
|
+
|
|
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
|
+
});
|
|
162
|
+
|
|
163
|
+
passwordElem.focus();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
attachEvent(eventName: SessionHandlerEvent, handler: Handler) {
|
|
169
|
+
return this.eventHandler.on(eventName, handler);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
detachEvent(eventName: SessionHandlerEvent, handler: Handler) {
|
|
173
|
+
return this.eventHandler.off(eventName, handler);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import SessionHandler from "./SessionHandler.js";
|
|
2
|
+
|
|
3
|
+
export default class UserSession extends SessionHandler {
|
|
4
|
+
decimalSeparator: string = ".";
|
|
5
|
+
formats: any;
|
|
6
|
+
groupSeparator: string | null = null;
|
|
7
|
+
shortDateFormat: string = "dd.MM.yyyy";
|
|
8
|
+
login: string | null = null;
|
|
9
|
+
isDeveloper: boolean = false;
|
|
10
|
+
|
|
11
|
+
constructor(userData: any) {
|
|
12
|
+
super();
|
|
13
|
+
this.formats = {};
|
|
14
|
+
Object.assign(this, userData);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/expose.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import has from "./has.js";
|
|
2
|
+
import parseFunc from "./parseFunc.js";
|
|
3
|
+
import requireNamespace from "./requireNamespace.js";
|
|
4
|
+
import warn from "./warn.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Expose a value to a potentially deeply nested global variable
|
|
8
|
+
* @param name {string | object} Either a dot-separated path to the global variable, or an object to expose the value on
|
|
9
|
+
* @param value {object | Function | Function[]} The value(s) to expose
|
|
10
|
+
*/
|
|
11
|
+
export default function expose(
|
|
12
|
+
name: object | string,
|
|
13
|
+
value: object | Function | Function[]
|
|
14
|
+
): void {
|
|
15
|
+
if (arguments.length !== 2) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"Incorrect number of arguments passed to the expose function!"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
for (let func of value) {
|
|
23
|
+
const { name: funcName } = parseFunc(func);
|
|
24
|
+
|
|
25
|
+
if (funcName) {
|
|
26
|
+
expose(name, func); // Expose func on namespace found in pName
|
|
27
|
+
} else {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Unable to detect name of function! Name it, or only supply one function and include name in first parameter."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
let keyName: string | undefined;
|
|
35
|
+
let namespace;
|
|
36
|
+
|
|
37
|
+
if (typeof value === "function") {
|
|
38
|
+
keyName = parseFunc(value).name;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof name === "string") {
|
|
42
|
+
let pathParts = name.split(".");
|
|
43
|
+
|
|
44
|
+
if (!keyName) {
|
|
45
|
+
keyName = pathParts[pathParts.length - 1];
|
|
46
|
+
pathParts = pathParts.slice(0, pathParts.length - 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
namespace = requireNamespace(window, pathParts);
|
|
50
|
+
} else if (
|
|
51
|
+
keyName &&
|
|
52
|
+
(typeof name === "object" || typeof name === "function")
|
|
53
|
+
) {
|
|
54
|
+
namespace = name;
|
|
55
|
+
} else {
|
|
56
|
+
throw new TypeError(
|
|
57
|
+
"Invalid typeof parameter 'name', or unable to parse pValue!"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (has(namespace, keyName)) {
|
|
62
|
+
// If class is already defined, warn or show an alert
|
|
63
|
+
warn([name, keyName].join(".") + " is already defined!");
|
|
64
|
+
} else {
|
|
65
|
+
namespace[keyName] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
async function getLocalizedStringAsync(string: string) {
|
|
2
|
+
try {
|
|
3
|
+
let response = await fetch("/api/user/localize", {
|
|
4
|
+
body: JSON.stringify({ string }),
|
|
5
|
+
headers: {
|
|
6
|
+
Accept: "application/json; */*",
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
9
|
+
},
|
|
10
|
+
method: "POST",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (response.ok) {
|
|
14
|
+
return (await response.json()) as string;
|
|
15
|
+
}
|
|
16
|
+
} catch (error) {}
|
|
17
|
+
|
|
18
|
+
return string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function afGetLocalizedString(string: string): string;
|
|
22
|
+
function afGetLocalizedString(string: string, wait: false): string;
|
|
23
|
+
function afGetLocalizedString(string: string, wait: true): Promise<string>;
|
|
24
|
+
function afGetLocalizedString(
|
|
25
|
+
string: string,
|
|
26
|
+
wait?: boolean
|
|
27
|
+
): string | Promise<string> {
|
|
28
|
+
if (af?.article?.i18n !== undefined) {
|
|
29
|
+
if (!Object.prototype.hasOwnProperty.call(af.article.i18n, string)) {
|
|
30
|
+
af.article.i18n[string] = string;
|
|
31
|
+
|
|
32
|
+
let promise = getLocalizedStringAsync(string).then((text) => {
|
|
33
|
+
af.article.i18n[string] = text;
|
|
34
|
+
return text;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (wait) {
|
|
38
|
+
return promise;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return af.article.i18n[string];
|
|
43
|
+
} else {
|
|
44
|
+
return string;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare global {
|
|
49
|
+
interface Window {
|
|
50
|
+
__UNLOCALIZED_STRINGS__: Set<string>;
|
|
51
|
+
__doNothingWithStrings?: () => Record<string, string>;
|
|
52
|
+
__getUnlocalizedStrings?: (forCustomRoute?: boolean) => string;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const key = "__UNLOCALIZED_STRINGS__";
|
|
57
|
+
const localizeSet = window[key] || new Set();
|
|
58
|
+
|
|
59
|
+
if (!window[key]) {
|
|
60
|
+
window[key] = localizeSet;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleReadyStateChange() {
|
|
64
|
+
if (document.readyState !== "loading") {
|
|
65
|
+
addDeveloperUtils();
|
|
66
|
+
document.removeEventListener("readystatechange", handleReadyStateChange);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (document.readyState !== "loading") {
|
|
71
|
+
addDeveloperUtils();
|
|
72
|
+
} else {
|
|
73
|
+
document.addEventListener("readystatechange", handleReadyStateChange);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isDeveloper() {
|
|
77
|
+
const { af } = window;
|
|
78
|
+
|
|
79
|
+
return af && af.userSession && af.userSession.isDeveloper;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function addDeveloperUtils() {
|
|
83
|
+
if (isDeveloper()) {
|
|
84
|
+
if (typeof window.__doNothingWithStrings === "function") {
|
|
85
|
+
const addedStrings = window.__doNothingWithStrings();
|
|
86
|
+
|
|
87
|
+
for (let string of Object.keys(addedStrings)) {
|
|
88
|
+
localizeSet.add(string);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!window[key]) {
|
|
93
|
+
window[key] = localizeSet;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!window.__getUnlocalizedStrings) {
|
|
97
|
+
window.__getUnlocalizedStrings = function (forCustomRoute) {
|
|
98
|
+
const strings = [];
|
|
99
|
+
|
|
100
|
+
for (let string of localizeSet) {
|
|
101
|
+
if (string) {
|
|
102
|
+
const escaped = string.replace(/'/g, "\\'");
|
|
103
|
+
if (forCustomRoute) {
|
|
104
|
+
const localized = afGetLocalizedString(string).replaceAll(
|
|
105
|
+
/'/g,
|
|
106
|
+
"\\'"
|
|
107
|
+
);
|
|
108
|
+
strings.push(`'${escaped}': '${localized}',`);
|
|
109
|
+
} else {
|
|
110
|
+
strings.push(
|
|
111
|
+
`s['${escaped}'] = @Html(@context.AddLocalizableString("${string.replaceAll(
|
|
112
|
+
'"',
|
|
113
|
+
'\\"'
|
|
114
|
+
)}"));`
|
|
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/global.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AfDataObject,
|
|
3
|
+
DataHandler,
|
|
4
|
+
DataObject,
|
|
5
|
+
Procedure,
|
|
6
|
+
ProcedureAPI,
|
|
7
|
+
} from "@olenbetong/data-object";
|
|
8
|
+
import UserSession from "./UserSession.js";
|
|
9
|
+
|
|
10
|
+
export type Appframe = {
|
|
11
|
+
article: {
|
|
12
|
+
dataObjects?: Record<string, DataObject<any>>;
|
|
13
|
+
i18n: Record<string, string>;
|
|
14
|
+
id: string;
|
|
15
|
+
hostName: string;
|
|
16
|
+
};
|
|
17
|
+
common?: {
|
|
18
|
+
getLocalizedString?: (string: string) => string;
|
|
19
|
+
};
|
|
20
|
+
controls?: {
|
|
21
|
+
alert?: (message: string, callback?: () => void) => void;
|
|
22
|
+
confirm?: (options: {
|
|
23
|
+
title: string;
|
|
24
|
+
message: string;
|
|
25
|
+
buttons: string[];
|
|
26
|
+
callback: (button: number) => void;
|
|
27
|
+
}) => void;
|
|
28
|
+
};
|
|
29
|
+
data?: {
|
|
30
|
+
[index: string]: { new (options?: any): DataHandler<any> };
|
|
31
|
+
} & { version?: string };
|
|
32
|
+
DataObject?: {
|
|
33
|
+
new <T>(options: any): DataObject<T>;
|
|
34
|
+
getByID?: (name: string) => AfDataObject<unknown> | null;
|
|
35
|
+
};
|
|
36
|
+
Procedure?: { new <T, V>(options: any): Procedure<T, V> };
|
|
37
|
+
ProcedureAPI?: { new <T, V>(options: any): ProcedureAPI<T, V> };
|
|
38
|
+
userSession: UserSession;
|
|
39
|
+
template: {
|
|
40
|
+
loop: (template: string, data: any, custom?: any) => string;
|
|
41
|
+
run: (template: string, row: any, custom?: any) => string;
|
|
42
|
+
run2: (
|
|
43
|
+
template: string,
|
|
44
|
+
currentRow: any,
|
|
45
|
+
previousRow?: any,
|
|
46
|
+
state?: any,
|
|
47
|
+
custom?: any
|
|
48
|
+
) => string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
declare global {
|
|
53
|
+
var __VERSION__: string;
|
|
54
|
+
var __DEV__: number;
|
|
55
|
+
var af: Appframe;
|
|
56
|
+
}
|
package/src/has.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as mitt } from "mitt";
|
|
2
|
+
export { default as expose } from "./expose.js";
|
|
3
|
+
export { getLocalizedString } from "./getLocalizedString.js";
|
|
4
|
+
export { default as localStorage } from "./localStorage.js";
|
|
5
|
+
export { default as SessionHandler } from "./SessionHandler.js";
|
|
6
|
+
export { default as UserSession } from "./UserSession.js";
|
|
7
|
+
export const version = __VERSION__;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function getLocalKey(key: string): string {
|
|
2
|
+
return [af?.article?.id || "", "common", key].join(".");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
class LocalStorage {
|
|
6
|
+
isPersisted() {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getLocalStorageKey(key: string, global?: boolean) {
|
|
11
|
+
return global ? ["common", key].join(".") : getLocalKey(key);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get(key: string, defaultValue: unknown, global?: boolean) {
|
|
15
|
+
const lsKey = this.getLocalStorageKey(key, global);
|
|
16
|
+
const value = localStorage.getItem(lsKey);
|
|
17
|
+
|
|
18
|
+
return value !== null ? JSON.parse(value) : defaultValue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set(key: string, value: unknown, global?: boolean) {
|
|
22
|
+
localStorage.setItem(
|
|
23
|
+
this.getLocalStorageKey(key, global),
|
|
24
|
+
JSON.stringify(value)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
remove(key: string, global?: boolean) {
|
|
29
|
+
const lsKey = this.getLocalStorageKey(key, global);
|
|
30
|
+
if (localStorage.getItem(lsKey) !== null) {
|
|
31
|
+
localStorage.removeItem(lsKey);
|
|
32
|
+
return true;
|
|
33
|
+
} else {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
clear() {
|
|
39
|
+
const baseKey = getLocalKey("");
|
|
40
|
+
|
|
41
|
+
for (let key in localStorage) {
|
|
42
|
+
if (key.slice(0, baseKey.length) === baseKey) {
|
|
43
|
+
localStorage.removeItem(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default new LocalStorage();
|
package/src/parseFunc.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
|
|
3
|
+
export default function parseFunc(func: Function) {
|
|
4
|
+
var match = func.toString().match(funcRegex);
|
|
5
|
+
if (match === null) {
|
|
6
|
+
throw new TypeError("Unable to parse function!");
|
|
7
|
+
}
|
|
8
|
+
return { name: match[1], args: match[2].split(","), body: match[3] };
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Namespace from "./Namespace.js";
|
|
2
|
+
|
|
3
|
+
export default function requireNamespace(
|
|
4
|
+
root: any,
|
|
5
|
+
namespace: string | Array<string>
|
|
6
|
+
): any {
|
|
7
|
+
if (typeof root === "object") {
|
|
8
|
+
if (typeof namespace === "string") {
|
|
9
|
+
let path = namespace.split(".");
|
|
10
|
+
|
|
11
|
+
return requireNamespace(root, path);
|
|
12
|
+
} else if (Array.isArray(namespace)) {
|
|
13
|
+
let current: any = root;
|
|
14
|
+
for (let i = 0; i < namespace.length; i++) {
|
|
15
|
+
current = current[namespace[i]] =
|
|
16
|
+
current[namespace[i]] || new (Namespace as any)();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return current;
|
|
20
|
+
} else {
|
|
21
|
+
throw new TypeError("Invalid type of parameter 'namespace'");
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
throw new TypeError("Invalid type of parameter 'root'");
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/settings.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function getKey(article?: string) {
|
|
2
|
+
const articleKey = article || af.article.id;
|
|
3
|
+
return `${articleKey}.${af.userSession.login}`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function getSettings<T>(setting: string, article?: string): T | null {
|
|
7
|
+
if (!af?.userSession) return null;
|
|
8
|
+
|
|
9
|
+
const key = getKey(article);
|
|
10
|
+
const settings = JSON.parse(localStorage.getItem(key) || "{}");
|
|
11
|
+
|
|
12
|
+
if (setting) {
|
|
13
|
+
return settings[setting] || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return settings;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function saveSettings<T>(
|
|
20
|
+
setting: string,
|
|
21
|
+
value: T | null,
|
|
22
|
+
article?: string
|
|
23
|
+
): void {
|
|
24
|
+
if (!af?.userSession) return;
|
|
25
|
+
|
|
26
|
+
const key = getKey(article);
|
|
27
|
+
let userSettings = JSON.parse(localStorage.getItem(key) || "{}");
|
|
28
|
+
|
|
29
|
+
if (typeof value !== "undefined") {
|
|
30
|
+
userSettings[setting] = value;
|
|
31
|
+
} else {
|
|
32
|
+
userSettings = setting;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
localStorage.setItem(key, JSON.stringify(userSettings));
|
|
36
|
+
}
|
package/src/warn.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
export default function warn(message: string, fallbackToAlert?: boolean) {
|
|
3
|
+
if (console && typeof console.warn === "function") {
|
|
4
|
+
console.warn(message);
|
|
5
|
+
} else if (console && typeof console.log === "function") {
|
|
6
|
+
console.log(message);
|
|
7
|
+
} else if (fallbackToAlert) {
|
|
8
|
+
alert(message);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2021",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"strictNullChecks": true,
|
|
8
|
+
"baseUrl": "./",
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"moduleResolution": "node",
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"outDir": "./es",
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"exclude": [
|
|
17
|
+
"test",
|
|
18
|
+
"dist",
|
|
19
|
+
"es",
|
|
20
|
+
"node_modules",
|
|
21
|
+
"**/*.test.ts",
|
|
22
|
+
"__tests__/*",
|
|
23
|
+
"scripts/*"
|
|
24
|
+
],
|
|
25
|
+
"include": ["./src/*"]
|
|
26
|
+
}
|