lupine.web 1.1.3 → 1.1.5
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/README.md +66 -3
- package/jsx-runtime/index.js +14 -14
- package/jsx-runtime/package.json +16 -16
- package/jsx-runtime/src/index.d.ts +2 -2
- package/package.json +53 -52
- package/src/core/bind-attributes.ts +61 -61
- package/src/core/bind-lang.ts +52 -52
- package/src/core/bind-links.ts +26 -16
- package/src/core/bind-meta.tsx +52 -52
- package/src/core/bind-ref.ts +51 -51
- package/src/core/bind-styles.ts +239 -239
- package/src/core/bind-theme.ts +53 -53
- package/src/core/camel-to-hyphens.ts +3 -3
- package/src/core/export-lupine.ts +80 -80
- package/src/core/index.ts +17 -17
- package/src/core/initialize.ts +116 -116
- package/src/core/mount-component.ts +72 -68
- package/src/core/page-loaded-events.ts +16 -16
- package/src/core/page-router.ts +180 -180
- package/src/core/render-component.ts +230 -233
- package/src/core/replace-innerhtml.ts +23 -23
- package/src/core/server-cookie.ts +24 -24
- package/src/global.d.ts +66 -66
- package/src/index.ts +14 -14
- package/src/jsx.ts +1044 -1043
- package/src/lib/cookie.ts +44 -44
- package/src/lib/debug-watch.ts +32 -32
- package/src/lib/index.ts +7 -7
- package/src/lib/is-frontend.ts +3 -3
- package/src/lib/logger.ts +55 -55
- package/src/lib/unique-id.ts +40 -40
- package/src/lib/web-config.ts +79 -77
- package/src/lib/web-env.ts +99 -99
- package/src/models/index.ts +4 -4
- package/src/models/json-props.ts +8 -8
- package/src/models/simple-storage-props.ts +9 -9
- package/src/models/theme-props.ts +7 -7
- package/src/models/to-client-delivery-props.ts +8 -8
- package/src/styles/css-styles.ts +814 -814
- package/src/styles/index.ts +4 -4
- package/tsconfig.json +113 -113
|
@@ -1,68 +1,72 @@
|
|
|
1
|
-
import { bindAttributes } from './bind-attributes';
|
|
2
|
-
import { bindLinks } from './bind-links';
|
|
3
|
-
import { VNode } from '../jsx';
|
|
4
|
-
// import { Logger } from '../lib/logger';
|
|
5
|
-
// import { bindPageResetEvent } from './page-reset-events';
|
|
6
|
-
import { callUnload, replaceInnerhtml } from './replace-innerhtml';
|
|
7
|
-
import { renderComponent } from './render-component';
|
|
8
|
-
|
|
9
|
-
// const logger = new Logger('mount-components');
|
|
10
|
-
export const mountInnerComponent = async (selector: string | null | Element, jsxNodes: VNode<any>) => {
|
|
11
|
-
renderComponent(jsxNodes.type, jsxNodes.props);
|
|
12
|
-
const el = selector && (typeof selector === 'string' ? document.querySelector(selector) : selector);
|
|
13
|
-
if (el) {
|
|
14
|
-
// call unload before releace innerHTML
|
|
15
|
-
await replaceInnerhtml(el, jsxNodes.props._html.join(''));
|
|
16
|
-
|
|
17
|
-
bindAttributes(el, jsxNodes.type, jsxNodes.props);
|
|
18
|
-
bindLinks(el);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// suggest to use HtmlVar.
|
|
23
|
-
export const mountOuterComponent = async (selector: string | Element, jsxNodes: VNode<any>) => {
|
|
24
|
-
renderComponent(jsxNodes.type, jsxNodes.props);
|
|
25
|
-
let el = selector && (typeof selector === 'string' ? document.querySelector(selector) : selector);
|
|
26
|
-
if (el) {
|
|
27
|
-
// Can't do outerHTML directly because it will lose attributes
|
|
28
|
-
const template = document.createElement('template');
|
|
29
|
-
// template.innerHTML = jsxNodes.props._html.join("");
|
|
30
|
-
// call unload before releace innerHTML
|
|
31
|
-
await replaceInnerhtml(template, jsxNodes.props._html.join(''));
|
|
32
|
-
// renderComponent should only have one element
|
|
33
|
-
template.content.children.length > 1 &&
|
|
34
|
-
console.error('renderComponent should only have one element: ', template.content.children.length);
|
|
35
|
-
const newEl = template.content.firstChild as Element;
|
|
36
|
-
// el.replaceWith(newEl);
|
|
37
|
-
await callUnload(el);
|
|
38
|
-
el.parentNode?.replaceChild(newEl, el);
|
|
39
|
-
bindAttributes(newEl, jsxNodes.type, jsxNodes.props);
|
|
40
|
-
bindLinks(newEl);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// suggest to use HtmlVar.
|
|
45
|
-
export const mountSiblingComponent = async (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
template.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
1
|
+
import { bindAttributes } from './bind-attributes';
|
|
2
|
+
import { bindLinks } from './bind-links';
|
|
3
|
+
import { VNode } from '../jsx';
|
|
4
|
+
// import { Logger } from '../lib/logger';
|
|
5
|
+
// import { bindPageResetEvent } from './page-reset-events';
|
|
6
|
+
import { callUnload, replaceInnerhtml } from './replace-innerhtml';
|
|
7
|
+
import { renderComponent } from './render-component';
|
|
8
|
+
|
|
9
|
+
// const logger = new Logger('mount-components');
|
|
10
|
+
export const mountInnerComponent = async (selector: string | null | Element, jsxNodes: VNode<any>) => {
|
|
11
|
+
renderComponent(jsxNodes.type, jsxNodes.props);
|
|
12
|
+
const el = selector && (typeof selector === 'string' ? document.querySelector(selector) : selector);
|
|
13
|
+
if (el) {
|
|
14
|
+
// call unload before releace innerHTML
|
|
15
|
+
await replaceInnerhtml(el, jsxNodes.props._html.join(''));
|
|
16
|
+
|
|
17
|
+
bindAttributes(el, jsxNodes.type, jsxNodes.props);
|
|
18
|
+
bindLinks(el);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// suggest to use HtmlVar.
|
|
23
|
+
export const mountOuterComponent = async (selector: string | Element, jsxNodes: VNode<any>) => {
|
|
24
|
+
renderComponent(jsxNodes.type, jsxNodes.props);
|
|
25
|
+
let el = selector && (typeof selector === 'string' ? document.querySelector(selector) : selector);
|
|
26
|
+
if (el) {
|
|
27
|
+
// Can't do outerHTML directly because it will lose attributes
|
|
28
|
+
const template = document.createElement('template');
|
|
29
|
+
// template.innerHTML = jsxNodes.props._html.join("");
|
|
30
|
+
// call unload before releace innerHTML
|
|
31
|
+
await replaceInnerhtml(template, jsxNodes.props._html.join(''));
|
|
32
|
+
// renderComponent should only have one element
|
|
33
|
+
template.content.children.length > 1 &&
|
|
34
|
+
console.error('renderComponent should only have one element: ', template.content.children.length);
|
|
35
|
+
const newEl = template.content.firstChild as Element;
|
|
36
|
+
// el.replaceWith(newEl);
|
|
37
|
+
await callUnload(el);
|
|
38
|
+
el.parentNode?.replaceChild(newEl, el);
|
|
39
|
+
bindAttributes(newEl, jsxNodes.type, jsxNodes.props);
|
|
40
|
+
bindLinks(newEl);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// suggest to use HtmlVar.
|
|
45
|
+
export const mountSiblingComponent = async (
|
|
46
|
+
selector: string | Element,
|
|
47
|
+
jsxNodes: VNode<any>,
|
|
48
|
+
position: 'before' | 'after' = 'after'
|
|
49
|
+
) => {
|
|
50
|
+
renderComponent(jsxNodes.type, jsxNodes.props);
|
|
51
|
+
let el = selector && (typeof selector === 'string' ? document.querySelector(selector) : selector);
|
|
52
|
+
if (el) {
|
|
53
|
+
// Can't do outerHTML directly because it will lose attributes
|
|
54
|
+
const template = document.createElement('template');
|
|
55
|
+
// template.innerHTML = jsxNodes.props._html.join("");
|
|
56
|
+
// call unload before releace innerHTML
|
|
57
|
+
await replaceInnerhtml(template, jsxNodes.props._html.join(''));
|
|
58
|
+
// renderComponent should only have one element
|
|
59
|
+
template.content.children.length > 1 &&
|
|
60
|
+
console.error('renderComponent should only have one element: ', template.content.children.length);
|
|
61
|
+
const newEl = template.content.firstChild as Element;
|
|
62
|
+
// el.replaceWith(newEl);
|
|
63
|
+
await callUnload(el);
|
|
64
|
+
if (el.nextSibling || position === 'before') {
|
|
65
|
+
el.parentNode?.insertBefore(newEl, position === 'after' ? el.nextSibling : el);
|
|
66
|
+
} else {
|
|
67
|
+
el.parentNode?.appendChild(newEl);
|
|
68
|
+
}
|
|
69
|
+
bindAttributes(newEl, jsxNodes.type, jsxNodes.props);
|
|
70
|
+
bindLinks(newEl);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
// some events need to be done before the page is rendered
|
|
2
|
-
const _pageLoadedEvents: Function[] = [];
|
|
3
|
-
export const callPageLoadedEvent = () => {
|
|
4
|
-
_pageLoadedEvents.forEach((i) => {
|
|
5
|
-
try {
|
|
6
|
-
i();
|
|
7
|
-
} catch (e) {
|
|
8
|
-
console.error(e);
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// The fn is called when the page is first time loaded
|
|
14
|
-
export const bindPageLoadedEvent = (fn: Function) => {
|
|
15
|
-
_pageLoadedEvents.push(fn);
|
|
16
|
-
};
|
|
1
|
+
// some events need to be done before the page is rendered
|
|
2
|
+
const _pageLoadedEvents: Function[] = [];
|
|
3
|
+
export const callPageLoadedEvent = () => {
|
|
4
|
+
_pageLoadedEvents.forEach((i) => {
|
|
5
|
+
try {
|
|
6
|
+
i();
|
|
7
|
+
} catch (e) {
|
|
8
|
+
console.error(e);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// The fn is called when the page is first time loaded
|
|
14
|
+
export const bindPageLoadedEvent = (fn: Function) => {
|
|
15
|
+
_pageLoadedEvents.push(fn);
|
|
16
|
+
};
|
package/src/core/page-router.ts
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
import { VNode } from '../jsx';
|
|
2
|
-
import { isFrontEnd } from '../lib/is-frontend';
|
|
3
|
-
import { PageProps } from './export-lupine';
|
|
4
|
-
import { mountInnerComponent } from './mount-component';
|
|
5
|
-
import { Logger } from '../lib/logger';
|
|
6
|
-
|
|
7
|
-
export type PageRouterCallback = (props: PageProps) => Promise<VNode<any> | null>;
|
|
8
|
-
|
|
9
|
-
export type PageRouterData = {
|
|
10
|
-
path: string;
|
|
11
|
-
handler: (PageRouterCallback | PageRouter)[];
|
|
12
|
-
parameterVariables: string[];
|
|
13
|
-
parameterLength: number;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type FramePageProps = {
|
|
17
|
-
component: (placeholderClassname: string, vnode: VNode<any>) => Promise<VNode<any>>;
|
|
18
|
-
placeholderClassname: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export class PageRouter {
|
|
22
|
-
logger = new Logger('page-router');
|
|
23
|
-
private routerData: PageRouterData[] = [];
|
|
24
|
-
private filter: PageRouterCallback | undefined;
|
|
25
|
-
private framePage: FramePageProps | undefined;
|
|
26
|
-
private subDir: string = '';
|
|
27
|
-
|
|
28
|
-
// if the filter returns null (passed filter), the router will continue.
|
|
29
|
-
// it works in the same way as in use method
|
|
30
|
-
setFilter(filter: PageRouterCallback) {
|
|
31
|
-
this.filter = filter;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// if the script is under a sub-dir (without last /), then findRoute needs to remove it from the url
|
|
35
|
-
setSubDir(subDir: string) {
|
|
36
|
-
this.subDir = subDir;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
setFramePage(framePage: FramePageProps) {
|
|
40
|
-
this.framePage = framePage;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// the path should start with / and end without /, and it can be
|
|
44
|
-
// /aaa/:bbb/ccc/:ddd (ccc is a fixed section)
|
|
45
|
-
// /aaa/:bbb/ccc/?ddd/?eee (from ddd, all sections are optional)
|
|
46
|
-
// /aaa/:?bbb/ccc/ (from bbb, all sections are optional)
|
|
47
|
-
private storeRouter(path: string, handler: (PageRouterCallback | PageRouter)[]) {
|
|
48
|
-
let fixedPath;
|
|
49
|
-
if (path === '*' || path === '' || path === '/*') {
|
|
50
|
-
// removed path === '/' ||
|
|
51
|
-
fixedPath = '*';
|
|
52
|
-
} else {
|
|
53
|
-
fixedPath = path;
|
|
54
|
-
if (!fixedPath.startsWith('/')) {
|
|
55
|
-
fixedPath = '/' + fixedPath;
|
|
56
|
-
}
|
|
57
|
-
if (fixedPath.endsWith('/') && fixedPath.length > 1) {
|
|
58
|
-
fixedPath = fixedPath.substring(0, fixedPath.length - 1);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let parameterLength = 0;
|
|
63
|
-
let parameterVariables: string[] = [];
|
|
64
|
-
const ind = fixedPath.indexOf('/:');
|
|
65
|
-
if (ind >= 0) {
|
|
66
|
-
parameterVariables = fixedPath.substring(ind + 1).split('/');
|
|
67
|
-
fixedPath = fixedPath.substring(0, ind);
|
|
68
|
-
// from optionInd, all will be optional
|
|
69
|
-
const optionInd = parameterVariables.findIndex((item) => item.startsWith('?'));
|
|
70
|
-
parameterLength = optionInd >= 0 ? optionInd : parameterVariables.length;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this.routerData.push({
|
|
74
|
-
path: fixedPath,
|
|
75
|
-
handler,
|
|
76
|
-
parameterVariables,
|
|
77
|
-
parameterLength,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
use(path: string, ...handler: (PageRouterCallback | PageRouter)[]) {
|
|
82
|
-
this.storeRouter(path, handler);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private async callHandle(handle: PageRouterCallback, path: string, props: PageProps): Promise<VNode<any> | null> {
|
|
86
|
-
try {
|
|
87
|
-
const vNode = await handle(props);
|
|
88
|
-
// logger.debug(`Processed path: ${path}`);
|
|
89
|
-
return vNode;
|
|
90
|
-
} catch (e: any) {
|
|
91
|
-
this.logger.error(`Processed path: ${path}, error: ${e.message}`);
|
|
92
|
-
// res.write(JSON.stringify({ status: 'error', message: `Processed path: ${path}, error: ${e.message}` }));
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async findRoute(url: string, props: PageProps, renderPartPage: boolean): Promise<VNode<any> | null> {
|
|
98
|
-
for (let i = 0, routerList; (routerList = this.routerData[i]); i++) {
|
|
99
|
-
if (routerList.path === '*' || url === routerList.path || url.startsWith(routerList.path + '/')) {
|
|
100
|
-
const parameters: { [key: string]: string } = {};
|
|
101
|
-
let meet = true;
|
|
102
|
-
if (routerList.parameterVariables.length > 0) {
|
|
103
|
-
meet = false;
|
|
104
|
-
let newUrl = url.substring(routerList.path.length + 1);
|
|
105
|
-
if (newUrl.endsWith('/')) {
|
|
106
|
-
newUrl = newUrl.substring(0, newUrl.length - 1);
|
|
107
|
-
}
|
|
108
|
-
const restPath = newUrl.split('/');
|
|
109
|
-
// the path must have mandatory parameters but some parameters can be optional
|
|
110
|
-
if (
|
|
111
|
-
restPath.length >= routerList.parameterLength &&
|
|
112
|
-
restPath.length <= routerList.parameterVariables.length
|
|
113
|
-
) {
|
|
114
|
-
meet = true;
|
|
115
|
-
for (const [index, item] of routerList.parameterVariables.entries()) {
|
|
116
|
-
if (!item.startsWith(':') && !item.startsWith('?') && item !== restPath[index]) {
|
|
117
|
-
meet = false;
|
|
118
|
-
break;
|
|
119
|
-
} else if ((item.startsWith(':') || item.startsWith('?')) && index < restPath.length) {
|
|
120
|
-
parameters[item.replace(/[:?]/g, '')] = restPath[index];
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
props.urlParameters = parameters;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (meet) {
|
|
128
|
-
for (let j = 0, router; (router = routerList.handler[j]); j++) {
|
|
129
|
-
if (router instanceof PageRouter) {
|
|
130
|
-
// it's a sub-level router
|
|
131
|
-
const nextPath =
|
|
132
|
-
routerList.path === '*' || (url === '/' && routerList.path === '/')
|
|
133
|
-
? url
|
|
134
|
-
: url.substring(routerList.path.length);
|
|
135
|
-
// TODO: sub-level?
|
|
136
|
-
const vNode = await router.handleRoute(nextPath, props, renderPartPage);
|
|
137
|
-
if (vNode) {
|
|
138
|
-
return vNode;
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
// it should be a function
|
|
142
|
-
// the query's url should match the api's path
|
|
143
|
-
const dom = await this.callHandle(router, url, props);
|
|
144
|
-
if (dom) {
|
|
145
|
-
return dom;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// stop process for this path if no page is found
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async handleRoute(url: string, props: PageProps, renderPartPage: boolean): Promise<VNode<any> | null> {
|
|
158
|
-
if (url.startsWith(this.subDir)) {
|
|
159
|
-
url = url.substring(this.subDir.length);
|
|
160
|
-
}
|
|
161
|
-
let vNode = null;
|
|
162
|
-
if (this.filter) {
|
|
163
|
-
vNode = await this.callHandle(this.filter, url, props);
|
|
164
|
-
}
|
|
165
|
-
if (!vNode) {
|
|
166
|
-
vNode = await this.findRoute(url, props, renderPartPage);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (vNode && this.framePage) {
|
|
170
|
-
const selector = '.' + this.framePage.placeholderClassname;
|
|
171
|
-
if (renderPartPage && isFrontEnd() && document.querySelector(selector)) {
|
|
172
|
-
await mountInnerComponent(selector, vNode);
|
|
173
|
-
return null;
|
|
174
|
-
} else {
|
|
175
|
-
return this.framePage.component(this.framePage.placeholderClassname, vNode);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return vNode;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
1
|
+
import { VNode } from '../jsx';
|
|
2
|
+
import { isFrontEnd } from '../lib/is-frontend';
|
|
3
|
+
import { PageProps } from './export-lupine';
|
|
4
|
+
import { mountInnerComponent } from './mount-component';
|
|
5
|
+
import { Logger } from '../lib/logger';
|
|
6
|
+
|
|
7
|
+
export type PageRouterCallback = (props: PageProps) => Promise<VNode<any> | null>;
|
|
8
|
+
|
|
9
|
+
export type PageRouterData = {
|
|
10
|
+
path: string;
|
|
11
|
+
handler: (PageRouterCallback | PageRouter)[];
|
|
12
|
+
parameterVariables: string[];
|
|
13
|
+
parameterLength: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type FramePageProps = {
|
|
17
|
+
component: (placeholderClassname: string, vnode: VNode<any>) => Promise<VNode<any>>;
|
|
18
|
+
placeholderClassname: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class PageRouter {
|
|
22
|
+
logger = new Logger('page-router');
|
|
23
|
+
private routerData: PageRouterData[] = [];
|
|
24
|
+
private filter: PageRouterCallback | undefined;
|
|
25
|
+
private framePage: FramePageProps | undefined;
|
|
26
|
+
private subDir: string = '';
|
|
27
|
+
|
|
28
|
+
// if the filter returns null (passed filter), the router will continue.
|
|
29
|
+
// it works in the same way as in use method
|
|
30
|
+
setFilter(filter: PageRouterCallback) {
|
|
31
|
+
this.filter = filter;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// if the script is under a sub-dir (without last /), then findRoute needs to remove it from the url
|
|
35
|
+
setSubDir(subDir: string) {
|
|
36
|
+
this.subDir = subDir;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setFramePage(framePage: FramePageProps) {
|
|
40
|
+
this.framePage = framePage;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// the path should start with / and end without /, and it can be
|
|
44
|
+
// /aaa/:bbb/ccc/:ddd (ccc is a fixed section)
|
|
45
|
+
// /aaa/:bbb/ccc/?ddd/?eee (from ddd, all sections are optional)
|
|
46
|
+
// /aaa/:?bbb/ccc/ (from bbb, all sections are optional)
|
|
47
|
+
private storeRouter(path: string, handler: (PageRouterCallback | PageRouter)[]) {
|
|
48
|
+
let fixedPath;
|
|
49
|
+
if (path === '*' || path === '' || path === '/*') {
|
|
50
|
+
// removed path === '/' ||
|
|
51
|
+
fixedPath = '*';
|
|
52
|
+
} else {
|
|
53
|
+
fixedPath = path;
|
|
54
|
+
if (!fixedPath.startsWith('/')) {
|
|
55
|
+
fixedPath = '/' + fixedPath;
|
|
56
|
+
}
|
|
57
|
+
if (fixedPath.endsWith('/') && fixedPath.length > 1) {
|
|
58
|
+
fixedPath = fixedPath.substring(0, fixedPath.length - 1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let parameterLength = 0;
|
|
63
|
+
let parameterVariables: string[] = [];
|
|
64
|
+
const ind = fixedPath.indexOf('/:');
|
|
65
|
+
if (ind >= 0) {
|
|
66
|
+
parameterVariables = fixedPath.substring(ind + 1).split('/');
|
|
67
|
+
fixedPath = fixedPath.substring(0, ind);
|
|
68
|
+
// from optionInd, all will be optional
|
|
69
|
+
const optionInd = parameterVariables.findIndex((item) => item.startsWith('?'));
|
|
70
|
+
parameterLength = optionInd >= 0 ? optionInd : parameterVariables.length;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.routerData.push({
|
|
74
|
+
path: fixedPath,
|
|
75
|
+
handler,
|
|
76
|
+
parameterVariables,
|
|
77
|
+
parameterLength,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
use(path: string, ...handler: (PageRouterCallback | PageRouter)[]) {
|
|
82
|
+
this.storeRouter(path, handler);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async callHandle(handle: PageRouterCallback, path: string, props: PageProps): Promise<VNode<any> | null> {
|
|
86
|
+
try {
|
|
87
|
+
const vNode = await handle(props);
|
|
88
|
+
// logger.debug(`Processed path: ${path}`);
|
|
89
|
+
return vNode;
|
|
90
|
+
} catch (e: any) {
|
|
91
|
+
this.logger.error(`Processed path: ${path}, error: ${e.message}`);
|
|
92
|
+
// res.write(JSON.stringify({ status: 'error', message: `Processed path: ${path}, error: ${e.message}` }));
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async findRoute(url: string, props: PageProps, renderPartPage: boolean): Promise<VNode<any> | null> {
|
|
98
|
+
for (let i = 0, routerList; (routerList = this.routerData[i]); i++) {
|
|
99
|
+
if (routerList.path === '*' || url === routerList.path || url.startsWith(routerList.path + '/')) {
|
|
100
|
+
const parameters: { [key: string]: string } = {};
|
|
101
|
+
let meet = true;
|
|
102
|
+
if (routerList.parameterVariables.length > 0) {
|
|
103
|
+
meet = false;
|
|
104
|
+
let newUrl = url.substring(routerList.path.length + 1);
|
|
105
|
+
if (newUrl.endsWith('/')) {
|
|
106
|
+
newUrl = newUrl.substring(0, newUrl.length - 1);
|
|
107
|
+
}
|
|
108
|
+
const restPath = newUrl.split('/');
|
|
109
|
+
// the path must have mandatory parameters but some parameters can be optional
|
|
110
|
+
if (
|
|
111
|
+
restPath.length >= routerList.parameterLength &&
|
|
112
|
+
restPath.length <= routerList.parameterVariables.length
|
|
113
|
+
) {
|
|
114
|
+
meet = true;
|
|
115
|
+
for (const [index, item] of routerList.parameterVariables.entries()) {
|
|
116
|
+
if (!item.startsWith(':') && !item.startsWith('?') && item !== restPath[index]) {
|
|
117
|
+
meet = false;
|
|
118
|
+
break;
|
|
119
|
+
} else if ((item.startsWith(':') || item.startsWith('?')) && index < restPath.length) {
|
|
120
|
+
parameters[item.replace(/[:?]/g, '')] = restPath[index];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
props.urlParameters = parameters;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (meet) {
|
|
128
|
+
for (let j = 0, router; (router = routerList.handler[j]); j++) {
|
|
129
|
+
if (router instanceof PageRouter) {
|
|
130
|
+
// it's a sub-level router
|
|
131
|
+
const nextPath =
|
|
132
|
+
routerList.path === '*' || (url === '/' && routerList.path === '/')
|
|
133
|
+
? url
|
|
134
|
+
: url.substring(routerList.path.length);
|
|
135
|
+
// TODO: sub-level?
|
|
136
|
+
const vNode = await router.handleRoute(nextPath, props, renderPartPage);
|
|
137
|
+
if (vNode) {
|
|
138
|
+
return vNode;
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
// it should be a function
|
|
142
|
+
// the query's url should match the api's path
|
|
143
|
+
const dom = await this.callHandle(router, url, props);
|
|
144
|
+
if (dom) {
|
|
145
|
+
return dom;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// stop process for this path if no page is found
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async handleRoute(url: string, props: PageProps, renderPartPage: boolean): Promise<VNode<any> | null> {
|
|
158
|
+
if (url.startsWith(this.subDir)) {
|
|
159
|
+
url = url.substring(this.subDir.length);
|
|
160
|
+
}
|
|
161
|
+
let vNode = null;
|
|
162
|
+
if (this.filter) {
|
|
163
|
+
vNode = await this.callHandle(this.filter, url, props);
|
|
164
|
+
}
|
|
165
|
+
if (!vNode) {
|
|
166
|
+
vNode = await this.findRoute(url, props, renderPartPage);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (vNode && this.framePage) {
|
|
170
|
+
const selector = '.' + this.framePage.placeholderClassname;
|
|
171
|
+
if (renderPartPage && isFrontEnd() && document.querySelector(selector)) {
|
|
172
|
+
await mountInnerComponent(selector, vNode);
|
|
173
|
+
return null;
|
|
174
|
+
} else {
|
|
175
|
+
return this.framePage.component(this.framePage.placeholderClassname, vNode);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return vNode;
|
|
179
|
+
}
|
|
180
|
+
}
|