amateras 0.5.0 → 0.6.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 (81) hide show
  1. package/README.md +23 -26
  2. package/ext/html/node/$Anchor.ts +2 -2
  3. package/ext/html/node/$Canvas.ts +2 -2
  4. package/ext/html/node/$Dialog.ts +2 -2
  5. package/ext/html/node/$Form.ts +2 -2
  6. package/ext/html/node/$Image.ts +2 -2
  7. package/ext/html/node/$Input.ts +2 -2
  8. package/ext/html/node/$Label.ts +2 -2
  9. package/ext/html/node/$Media.ts +2 -2
  10. package/ext/html/node/$OptGroup.ts +2 -2
  11. package/ext/html/node/$Option.ts +2 -2
  12. package/ext/html/node/$Select.ts +2 -2
  13. package/ext/html/node/$TextArea.ts +2 -2
  14. package/ext/i18n/README.md +20 -0
  15. package/ext/i18n/src/index.ts +106 -12
  16. package/ext/i18n/src/structure/I18n.ts +12 -8
  17. package/ext/i18n/src/structure/I18nTranslation.ts +35 -0
  18. package/ext/idb/src/structure/builder/$IDBBuilder.ts +8 -8
  19. package/ext/markdown/README.md +53 -0
  20. package/ext/markdown/package.json +7 -0
  21. package/ext/markdown/src/index.ts +3 -0
  22. package/ext/markdown/src/lib/type.ts +26 -0
  23. package/ext/markdown/src/lib/util.ts +21 -0
  24. package/ext/markdown/src/structure/Markdown.ts +54 -0
  25. package/ext/markdown/src/structure/MarkdownLexer.ts +111 -0
  26. package/ext/markdown/src/structure/MarkdownParser.ts +33 -0
  27. package/ext/markdown/src/syntax/alert.ts +46 -0
  28. package/ext/markdown/src/syntax/blockquote.ts +35 -0
  29. package/ext/markdown/src/syntax/bold.ts +11 -0
  30. package/ext/markdown/src/syntax/code.ts +11 -0
  31. package/ext/markdown/src/syntax/codeblock.ts +44 -0
  32. package/ext/markdown/src/syntax/heading.ts +14 -0
  33. package/ext/markdown/src/syntax/horizontalRule.ts +11 -0
  34. package/ext/markdown/src/syntax/image.ts +23 -0
  35. package/ext/markdown/src/syntax/italic.ts +11 -0
  36. package/ext/markdown/src/syntax/link.ts +46 -0
  37. package/ext/markdown/src/syntax/list.ts +121 -0
  38. package/ext/markdown/src/syntax/table.ts +67 -0
  39. package/ext/markdown/src/syntax/text.ts +19 -0
  40. package/ext/router/README.md +111 -17
  41. package/ext/router/package.json +10 -0
  42. package/ext/router/src/index.ts +69 -0
  43. package/ext/router/src/node/Page.ts +34 -0
  44. package/ext/router/src/node/Router.ts +191 -0
  45. package/ext/router/{node → src/node}/RouterAnchor.ts +13 -2
  46. package/ext/router/src/structure/PageBuilder.ts +24 -0
  47. package/ext/router/src/structure/Route.ts +105 -0
  48. package/ext/signal/README.md +93 -0
  49. package/ext/signal/package.json +9 -0
  50. package/ext/signal/src/index.ts +128 -0
  51. package/{src → ext/signal/src}/structure/Signal.ts +6 -10
  52. package/ext/ssr/index.ts +4 -4
  53. package/ext/ui/lib/VirtualScroll.ts +25 -0
  54. package/ext/ui/node/Accordian.ts +97 -0
  55. package/ext/ui/node/Form.ts +53 -0
  56. package/ext/ui/node/Grid.ts +0 -0
  57. package/ext/ui/node/Table.ts +43 -0
  58. package/ext/ui/node/Tabs.ts +114 -0
  59. package/ext/ui/node/Toast.ts +16 -0
  60. package/ext/ui/node/Waterfall.ts +72 -0
  61. package/ext/ui/package.json +11 -0
  62. package/package.json +6 -3
  63. package/src/core.ts +30 -60
  64. package/src/global.ts +9 -2
  65. package/src/index.ts +1 -2
  66. package/src/lib/assignProperties.ts +57 -0
  67. package/src/lib/native.ts +25 -8
  68. package/src/lib/uppercase.ts +3 -0
  69. package/src/node/$Element.ts +7 -41
  70. package/src/node/$EventTarget.ts +45 -0
  71. package/src/node/$Node.ts +60 -65
  72. package/src/node/$Virtual.ts +65 -0
  73. package/src/node.ts +7 -6
  74. package/ext/i18n/src/node/I18nText.ts +0 -35
  75. package/ext/markdown/index.ts +0 -121
  76. package/ext/router/index.ts +0 -73
  77. package/ext/router/node/Page.ts +0 -27
  78. package/ext/router/node/Route.ts +0 -54
  79. package/ext/router/node/Router.ts +0 -149
  80. package/src/lib/assign.ts +0 -38
  81. package/src/lib/assignHelper.ts +0 -18
@@ -0,0 +1,45 @@
1
+ import { isBoolean } from "#lib/native";
2
+
3
+ export class $EventTarget<EvMap = {}> {
4
+ node: EventTarget;
5
+ constructor(node: EventTarget) {
6
+ this.node = node;
7
+ if (node !== window) (node as Mutable<EventTarget>).$ = this;
8
+ }
9
+
10
+ on(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | AddEventListenerOptions): this {
11
+ return this.addEventListener(type, listener, options);
12
+ }
13
+
14
+ off(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | EventListenerOptions): this {
15
+ return this.removeEventListener(type, listener, options);
16
+ }
17
+
18
+ once(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | AddEventListenerOptions): this {
19
+ return this.on(type, listener, { once: true, ...(isBoolean(options) ? {capture: options} : options ?? {}) })
20
+ }
21
+ }
22
+
23
+ export type $Event<E extends $EventTarget, Ev = any> = Ev & {currentTarget: {$: E}};
24
+ export type $EventListener<E extends $EventTarget, Ev> = (event: $Event<E, Ev>) => void;
25
+ export type $EventListenerObject<E extends $EventTarget, Ev> = { handleEvent(object: $Event<E, Ev>): void; }
26
+
27
+ export interface $EventTarget<EvMap = {}> {
28
+ /** {@link EventTarget.addEventListener} */
29
+ addEventListener<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | AddEventListenerOptions): this;
30
+ addEventListener(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | AddEventListenerOptions): this;
31
+ /** {@link EventTarget.removeEventListener} */
32
+ removeEventListener<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | EventListenerOptions): this;
33
+ removeEventListener(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | EventListenerOptions): this;
34
+ /** {@link EventTarget.dispatchEvent} */
35
+ dispatchEvent(event: Event): boolean;
36
+
37
+ on<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | AddEventListenerOptions): this;
38
+ on(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | AddEventListenerOptions): this;
39
+
40
+ off<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | EventListenerOptions): this;
41
+ off(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | EventListenerOptions): this;
42
+
43
+ once<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | AddEventListenerOptions): this;
44
+ once(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | AddEventListenerOptions): this;
45
+ }
package/src/node/$Node.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import { chain } from "#lib/chain";
2
2
  import { _document } from "#lib/env";
3
- import { _Array_from, _instanceof, _JSON_stringify, _null, _Promise, forEach, isBoolean, isFunction, isNull, isObject, isUndefined } from "#lib/native";
3
+ import { _Array_from, _instanceof, _JSON_stringify, _null, _Promise, forEach, isFunction, isNull, isUndefined } from "#lib/native";
4
4
  import { toArray } from "#lib/toArray";
5
- import { Signal } from "#structure/Signal";
5
+ import { $EventTarget } from "./$EventTarget";
6
6
 
7
- export class $Node {
8
- node: Node & ChildNode;
7
+ export class $Node<EvMap = {}> extends $EventTarget<EvMap> {
8
+ declare node: Node & ChildNode & ParentNode;
9
+ static processors = new Set<$NodeContentProcessor>();
10
+ static setters = new Set<$NodeSetterHandler>();
9
11
  constructor(node: Node & ChildNode) {
10
- this.node = node;
11
- //@ts-expect-error
12
- this.node.$ = this;
12
+ super(node);
13
13
  }
14
14
 
15
15
  content(children: $NodeContentResolver<this>) {
@@ -21,7 +21,10 @@ export class $Node {
21
21
 
22
22
  insert(resolver: $NodeContentResolver<this>, position = -1) {
23
23
  // process nodes
24
- forEach(toArray(resolver), resolve_child => forEach($Node.process(this, resolve_child), $node => $Node.append(this, $node, position)));
24
+ forEach(toArray(resolver), resolve_child => forEach($Node.process(this, resolve_child), $node => {
25
+ $Node.append(this, $node, position);
26
+ if (position >= 0) position++;
27
+ }));
25
28
  return this;
26
29
  }
27
30
 
@@ -58,50 +61,19 @@ export class $Node {
58
61
  return _instanceof(this, instance) ? this : null;
59
62
  }
60
63
 
61
- on(type: string, listener: any, options?: boolean | AddEventListenerOptions) {
62
- return this.addEventListener(type, listener, options);
63
- }
64
-
65
- off(type: string, listener: any, options?: boolean | EventListenerOptions) {
66
- return this.removeEventListener(type, listener, options);
67
- }
68
-
69
- once(type: string, listener: any, options?: boolean | AddEventListenerOptions) {
70
- return this.on(type, listener, { once: true, ...(isBoolean(options) ? {capture: options} : options ?? {}) })
71
- }
72
-
73
64
  static process<T extends $Node>($node: T, content: $NodeContentResolver<any>): Array<$Node | undefined | null> {
65
+ for (const processor of this.processors) {
66
+ const result = processor($node, content);
67
+ if (result) return result;
68
+ }
74
69
  if (isUndefined(content) || isNull(content) || _instanceof(content, $Node)) return [content];
75
70
  // is Promise
76
71
  if (_instanceof(content, _Promise)) return [$('async').await(content, ($async, $child) => $async.replace($child as any))];
77
72
  // is SignalFunction or ContentHandler
78
73
  if (isFunction(content)) {
79
- const signal = (content as any).signal;
80
- if (_instanceof(signal, Signal)) {
81
- const resolver = (content as $.SignalFunction<any>)();
82
- if (_instanceof(resolver, $Node)) {
83
- // handler signal $Node result
84
- let node = resolver;
85
- const set = (value: any) => {
86
- node.replace(value);
87
- node = value;
88
- }
89
- signal.subscribe(set);
90
- return [resolver];
91
- } else {
92
- // handler signal other type result
93
- const $text = _document ? new $Text() : $('signal').attr({ type: typeof signal.value() });
94
- const set = (value: any) => $text.textContent(isObject(value) ? _JSON_stringify(value) : value);
95
- if (_instanceof($text, $Text)) $text.signals.add(signal);
96
- signal.subscribe(set);
97
- set(resolver);
98
- return [$text];
99
- }
100
- } else {
101
- const _content = content($node) as $NodeContentResolver<$Node>;
102
- if (_instanceof(_content, _Promise)) return this.process($node, _content as any);
103
- else return toArray(_content).map(content => this.process($node, content)).flat();
104
- }
74
+ const _content = content($node) as $NodeContentResolver<$Node>;
75
+ if (_instanceof(_content, _Promise)) return this.process($node, _content as any);
76
+ else return toArray(_content).map(content => this.process($node, content)).flat();
105
77
  }
106
78
  // is nested array
107
79
  if (_instanceof(content, Array)) return content.map(c => this.process($node, c)).flat();
@@ -122,17 +94,18 @@ export class $Node {
122
94
  }
123
95
 
124
96
  export class $Text extends $Node {
125
- signals = new Set<Signal<any>>();
126
97
  constructor(textContent?: string) {
127
98
  super(new Text(textContent));
128
99
  }
129
100
  }
130
101
 
102
+ export type $NodeSetterHandler = (value: any, set: (value: any) => void) => any;
103
+ export type $NodeContentProcessor = <N extends $Node>($node: N, content: $NodeContentResolver<N>) => Array<$Node | undefined | null> | void | undefined;
131
104
  export type $NodeContentHandler<T extends $Node> = ($node: T) => OrPromise<$NodeContentResolver<T>>;
132
- export type $NodeContentTypes = $Node | string | number | boolean | $.SignalFunction<any> | null | undefined;
105
+ export type $NodeContentTypes = $Node | string | number | boolean | $.$NodeContentTypeExtends | null | undefined;
133
106
  export type $NodeContentResolver<T extends $Node> = OrPromise<$NodeContentTypes | $NodeContentHandler<T> | $NodeContentResolver<T>[]>;
134
107
 
135
- export interface $Node {
108
+ export interface $Node<EvMap = {}> extends $EventTarget<EvMap> {
136
109
  /** {@link Node.baseURI} */
137
110
  readonly baseURI: string;
138
111
  /** {@link Node.childNodes} */
@@ -157,13 +130,21 @@ export interface $Node {
157
130
  readonly parentNode?: ParentNode | null;
158
131
  /** {@link Node.previousSibling} */
159
132
  readonly previousSibling?: ChildNode | null;
133
+ /** {@link ParentNode.childElementCount} */
134
+ readonly childElementCount: number;
135
+ /** {@link ParentNode.children} */
136
+ readonly children: HTMLCollection;
137
+ /** {@link ParentNode.firstElementChild} */
138
+ readonly firstElementChild: Element | null;
139
+ /** {@link ParentNode.lastElementChild} */
140
+ readonly lastElementChild: Element | null;
160
141
 
161
142
  /** {@link Node.appendChild} */
162
143
  appendChild<T extends Node>(node: T): T;
163
144
  /** {@link Node.cloneNode} */
164
145
  cloneNode(subtree?: boolean): Node;
165
146
  /** {@link Node.compareDocumentPosition} */
166
- compareDocumentPosition(other: Node): number;
147
+ compareDocumentPosition(other: $EventTarget | Node): number;
167
148
  /** {@link Node.getRootNode} */
168
149
  getRootNode(options?: GetRootNodeOptions): Node;
169
150
  /** {@link Node.hasChildNodes} */
@@ -173,9 +154,9 @@ export interface $Node {
173
154
  /** {@link Node.isDefaultNamespace} */
174
155
  isDefaultNamespace(namespace: string | null): boolean;
175
156
  /** {@link Node.isEqualNode} */
176
- isEqualNode(otherNode: Node | null): boolean;
157
+ isEqualNode(otherNode: $EventTarget | Node | null): boolean;
177
158
  /** {@link Node.isSameNode} */
178
- isSameNode(otherNode: Node | null): boolean;
159
+ isSameNode(otherNode: $EventTarget | Node | null): boolean;
179
160
  /** {@link Node.lookupNamespaceURI} */
180
161
  lookupNamespaceURI(prefix: string | null): string | null;
181
162
  /** {@link Node.lookupPrefix} */
@@ -186,20 +167,34 @@ export interface $Node {
186
167
  removeChild<T extends Node>(child: T): T;
187
168
  /** {@link Node.replaceChild} */
188
169
  replaceChild<T extends Node>(node: Node, child: T): T;
189
- /** {@link Node.replaceChild} */
190
- after(...nodes: (Node | string)[]): this;
191
- /** {@link Node.replaceChild} */
192
- before(...nodes: (Node | string)[]): this;
193
- /** {@link Node.replaceChild} */
170
+ /** {@link Node.contains} */
171
+ contains(other: $EventTarget | Node | null | undefined): boolean;
172
+
173
+ /** {@link ChildNode.after} */
174
+ after(...nodes: ($EventTarget | Node | string)[]): this;
175
+ /** {@link ChildNode.before} */
176
+ before(...nodes: ($EventTarget | Node | string)[]): this;
177
+ /** {@link ChildNode.remove} */
194
178
  remove(): this;
195
- /** {@link Node.replaceChild} */
196
- replaceWith(...nodes: (Node | string)[]): this;
197
- /** {@link EventTarget.addEventListener} */
198
- addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
199
- /** {@link EventTarget.removeEventListener} */
200
- removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
201
- /** {@link EventTarget.dispatchEvent} */
202
- dispatchEvent(event: Event): boolean;
179
+ /** {@link ChildNode.replaceWith} */
180
+ replaceWith(...nodes: ($EventTarget | Node | string)[]): this;
181
+ /** {@link ParentNode.append} */
182
+ append(...nodes: ($EventTarget | Node | string)[]): this;
183
+ /** {@link ParentNode.prepend} */
184
+ prepend(...nodes: ($EventTarget | Node | string)[]): this;
185
+ /** {@link ParentNode.querySelector} */
186
+ querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
187
+ querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
188
+ querySelector<K extends keyof MathMLElementTagNameMap>(selectors: K): MathMLElementTagNameMap[K] | null;
189
+ querySelector<E extends Element = Element>(selectors: string): E | null;
190
+ /** {@link ParentNode.querySelectorAll} */
191
+ querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
192
+ querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
193
+ querySelectorAll<K extends keyof MathMLElementTagNameMap>(selectors: K): NodeListOf<MathMLElementTagNameMap[K]>;
194
+ querySelectorAll<K extends keyof HTMLElementDeprecatedTagNameMap>(selectors: K): NodeListOf<HTMLElementDeprecatedTagNameMap[K]>;
195
+ querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
196
+ /** {@link ParentNode.replaceChildren} */
197
+ replaceChildren(...nodes: ($EventTarget | Node | string)[]): this;
203
198
 
204
199
  /** {@link Node.nodeValue} */
205
200
  nodeValue(nodeValue: $Parameter<string | null>): this;
@@ -0,0 +1,65 @@
1
+ import { chain } from "#lib/chain";
2
+ import { forEach, _Array_from, _instanceof, _null } from "#lib/native";
3
+ import { toArray } from "#lib/toArray";
4
+ import { $HTMLElement } from "#node/$HTMLElement";
5
+ import { $Node, type $NodeContentResolver } from "#node/$Node";
6
+
7
+ export class $Virtual<Ele extends HTMLElement = HTMLElement, EvMap = HTMLElementEventMap> extends $HTMLElement<Ele, EvMap> {
8
+ nodes = new Set<$Node>();
9
+ hiddenNodes = new Set<$Node>();
10
+ constructor(resolver: string | Ele) {
11
+ super(resolver);
12
+ }
13
+
14
+ content(children: $NodeContentResolver<this>) {
15
+ return chain(this, _null, _null, children, children => {
16
+ this.nodes.clear();
17
+ forEach(this.childNodes, node => node.remove());
18
+ this.insert(children);
19
+ })
20
+ }
21
+
22
+ insert(resolver: $NodeContentResolver<this>, position = -1) {
23
+ // process nodes
24
+ forEach(toArray(resolver), resolve_child => {
25
+ forEach($Node.process(this, resolve_child), $node => {
26
+ $Virtual.append(this, $node, position)
27
+ if (position >= 0) position++;
28
+ })
29
+ });
30
+ return this;
31
+ }
32
+
33
+ hide($node?: $Node | null) {
34
+ if ($node && this.nodes.has($node)) this.hiddenNodes.add($node);
35
+ return this;
36
+ }
37
+
38
+ show($node?: $Node | null) {
39
+ if ($node) this.hiddenNodes.delete($node);
40
+ return this;
41
+ }
42
+
43
+ render() {
44
+ // remove hidden node
45
+ forEach(this.childNodes, node => this.hiddenNodes.has($(node)) && node.remove());
46
+ // add visible node with position
47
+ forEach(this.nodes, ($node, i) => {
48
+ if (
49
+ !this.hiddenNodes.has($node) // node is not hidden
50
+ && !$node.inDOM() // node is not in dom tree
51
+ && $(this.childNodes).at(i) !== $node // node not matched at position
52
+ ) $Node.append(this, $node, i);
53
+ })
54
+ return this;
55
+ }
56
+
57
+ static append($node: $Virtual, child: $Node | undefined | null, position: number) {
58
+ if (child) {
59
+ const childList = _Array_from($node.nodes);
60
+ if (!childList.at(position)) childList.push(child);
61
+ else childList.splice(position >= 0 ? position : childList.length + 1 + position, 0, child);
62
+ $node.nodes = new Set(childList);
63
+ }
64
+ }
65
+ }
package/src/node.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { $HTMLElement } from '#node/$HTMLElement';
2
- import { assignHelper } from '#lib/assignHelper';
2
+ import { assignProperties } from '#lib/assignProperties';
3
3
  import { $Element } from '#node/$Element';
4
4
  import { $Node, $Text } from '#node/$Node';
5
+ import { $EventTarget } from '#node/$EventTarget';
5
6
 
6
- assignHelper(EventTarget, $Node);
7
- assignHelper(Node, $Node);
8
- assignHelper(Text, $Text);
9
- assignHelper(Element, $Element);
10
- assignHelper(HTMLElement, $HTMLElement);
7
+ assignProperties(EventTarget, $EventTarget);
8
+ assignProperties(Node, $Node);
9
+ assignProperties(Text, $Text);
10
+ assignProperties(Element, $Element);
11
+ assignProperties(HTMLElement, $HTMLElement);
@@ -1,35 +0,0 @@
1
- import { _Array_from, isUndefined } from "amateras/lib/native";
2
- import { $HTMLElement } from "amateras/node/$HTMLElement";
3
- import type { I18n } from "#structure/I18n";
4
-
5
- export class I18nText extends $HTMLElement<HTMLElement, { i18nupdate: Event }> {
6
- i18n: I18n;
7
- key: string;
8
- options: I18nTextOptions | undefined;
9
- constructor(i18n: I18n, key: string, options?: I18nTextOptions) {
10
- super('text');
11
- this.i18n = i18n;
12
- this.key = key;
13
- this.options = options;
14
- i18n.locale$.signal.subscribe(() => this.update())
15
- this.update();
16
- }
17
-
18
- async update() {
19
- update: {
20
- const {key, i18n} = this;
21
- const dictionary = i18n.dictionary();
22
- if (!dictionary) {this.content(key); break update}
23
- const target = await dictionary.find(key);
24
- if (isUndefined(target)) break update;
25
- const snippets = target.split(/\$[a-zA-Z0-9_]+\$/);
26
- if (snippets.length === 1 || !this.options) {this.content(target); break update}
27
- const matches = target.matchAll(/(\$([a-zA-Z0-9_]+)\$)/g);
28
- this.content(snippets.map(text => [text, this.options?.[matches.next().value?.at(2)!] ?? null]));
29
- }
30
- this.dispatchEvent(new Event('i18nupdate'));
31
- return this;
32
- }
33
- }
34
-
35
- export type I18nTextOptions = {[key: string]: any}
@@ -1,121 +0,0 @@
1
- import { _Array_from, forEach } from "amateras/lib/native";
2
-
3
- const blockProcesses = new Set<MarkdownBlockProcessOptions>();
4
- const inlineProcesses = new Set<MarkdownProcessFunction>();
5
-
6
- export class Markdown {
7
- blockProcessSet = new Set(blockProcesses);
8
- inlineProcessSet = new Set(inlineProcesses);
9
- constructor() {}
10
-
11
- blockProcess(options: MarkdownBlockProcessOptions) {
12
- this.blockProcessSet.add(options);
13
- return this;
14
- }
15
-
16
- inlineProcess(handle: MarkdownProcessFunction) {
17
- this.inlineProcessSet.add(handle);
18
- return this;
19
- }
20
-
21
- toHTML(text: string) {
22
- const blocks = _Array_from(text.matchAll(/(?:.+?\n?)+/gm));
23
- return blocks.map(block => {
24
- let matched, blockText = block[0]
25
- for (const blockProcess of blockProcesses) {
26
- matched = blockText.match(blockProcess.regexp);
27
- if (!matched) continue;
28
- blockText = blockProcess.handle(blockText);
29
- const removeHTML = blockText.replaceAll(/<.+>[^<]+?<\/.+>/gm, '');
30
- if (!removeHTML) break;
31
- }
32
- if (!matched) blockText = paragraph(blockText);
33
- inlineProcesses.forEach(fn => blockText = fn(blockText))
34
- return blockText;
35
- }).join('')
36
- }
37
-
38
- toDOM(text: string) {
39
- return $('article').innerHTML(this.toHTML(text))
40
- }
41
- }
42
-
43
- export type MarkdownProcessFunction = (text: string) => string;
44
- export interface MarkdownBlockProcessOptions {
45
- regexp: RegExp,
46
- handle: (text: string) => string;
47
- }
48
-
49
- const blockProcess = (options: MarkdownBlockProcessOptions) => blockProcesses.add(options);
50
- const inlineProcess = (handle: MarkdownProcessFunction) => inlineProcesses.add(handle);
51
- const replaceAll = (str: string, searchValue: string | RegExp, replacer: ((substring: string, ...args: any[]) => string) | string): string => str.replaceAll(searchValue, replacer as any);
52
- const trim = (str: string) => str.trim();
53
- const paragraph = (str: string) => replaceAll(str, /(?:.+?\n?)+/gm, $0 => `<p>${trim($0)}</p>`);
54
- // Headings
55
- blockProcess({
56
- regexp: /^(#+) (.+)/gm,
57
- handle: text => replaceAll(text, /^(#+) (.+)/gm, (_, $1: string, $2) => `<h${$1.length}>${$2}</h${$1.length}>`)
58
- });
59
- blockProcess({
60
- regexp: /^(.+)\n==+$/gm,
61
- handle: text => replaceAll(text, /^(.+)\n==+$/gm, (_, $1) => `<h1>${$1}</h1>`)
62
- });
63
- blockProcess({
64
- regexp: /^(.+)\n--+$/gm,
65
- handle: text => replaceAll(text, /^(.+)\n--+$/gm, (_, $1) => `<h2>${$1}</h2>`)
66
- });
67
- // Blockquote
68
- blockProcess({
69
- regexp: /(?:^> ?.*(?:\n|$))+/gm,
70
- handle: text => {
71
- const fn = (str: string) => {
72
- const blocks = _Array_from(str.matchAll(/(?:^> ?.*(?:\n|$))+/gm));
73
- forEach(blocks, block => {
74
- const blocked = fn(replaceAll(block[0], /^> ?/gm, ''));
75
- str = str.replace(block[0], `<blockquote>\n${paragraph(blocked)}\n</blockquote>`);
76
- })
77
- return str;
78
- }
79
- return fn(text);
80
- }
81
- });
82
- // List
83
- blockProcess({
84
- regexp: /(?:^(?:\t|(?: )+)?(?:-|[0-9]+\.) (?:.+\n?))+/gm,
85
- handle: text => {
86
- const fn = (str: string) => {
87
- const blocks = _Array_from(str.matchAll(/(?:^(?:\t|(?: )+)?(?:-|[0-9]+\.) (?:.+\n?))+/gm));
88
- forEach(blocks, block => {
89
- let haveList = false // check this loop have list
90
- const type = block[0].match(/^(-|[0-9]+\.) /)?.[1] === '-' ? 'ul' : 'ol';
91
- const listed = replaceAll(block[0], /^(?:-|[0-9]+\.) (.+)/gm, (_, $1: string) => (haveList = true, `<li>\n${trim($1)}\n</li>`));
92
- const clearTabbed = replaceAll(listed, /^(?:\t|(?: ))/gm, '');
93
- const convertedList = fn(clearTabbed);
94
- str = str.replace(block[0], haveList ? `<${type}>\n${trim(convertedList)}\n</${type}>` : convertedList);
95
- })
96
- return str;
97
- }
98
- return fn(text);
99
- }
100
- })
101
- // Codeblock
102
- blockProcess({
103
- regexp: /^```([^`\n]+)\n([^`]+)?```/gm,
104
- handle: text => replaceAll(text, /^```([^`\n]+)\n([^`]+)?```/gm, (_, $1, $2: string) => `<pre><code>\n${trim($2)}\n</code></pre>`)
105
- })
106
- // Horizontal Rule
107
- blockProcess({
108
- regexp: /^(?:---|\*\*\*|___)(\s+)?$/gm,
109
- handle: text => replaceAll(text, /^(?:---|\*\*\*|___)(\s+)?$/gm, _ => `<hr>`)
110
- })
111
- // Bold
112
- inlineProcess(text => replaceAll(text, /\*\*([^*]+?)\*\*/g, (_, $1) => `<b>${$1}</b>`));
113
- // Italic
114
- inlineProcess(text => replaceAll(text, /\*([^*]+?)\*/g, (_, $1) => `<i>${$1}</i>`));
115
- // Image
116
- inlineProcess(text => replaceAll(text, /!\[(.+?)\]\((.+?)(?: "(.+?)?")?\)/g, (_, alt, src, title) => `<img src="${src}" alt="${alt}"${title ? ` title="${title}"` : ''}>`));
117
- // Link
118
- inlineProcess(text => replaceAll(text, /\[(.+?)\]\((?:(\w\w+?:[^\s]+?)(?: "(.+?)?")?)\)/g, (_, content, href, title) => `<a href="${href}"${title ? ` title="${title}"` : ''}>${content}</a>`));
119
- inlineProcess(text => replaceAll(text, /\[(.+?)\]\((?:(\w+?@(?:\w|\.\w)+?)(?: "(.+)?")?)\)/g, (_, content, mail, title) => `<a href="mailto:${mail}"${title ? ` title="${title}"` : ''}>${content}</a>`));
120
- inlineProcess(text => replaceAll(text, /<(\w\w+?:[^\s]+?)>/g, (_, href) => `<a href="${href}">${href}</a>`));
121
- inlineProcess(text => replaceAll(text, /<(\w+?@(?:\w|\.\w)+?)>/g, (_, mail) => `<a href="mailto:${mail}">${mail}</a>`));
@@ -1,73 +0,0 @@
1
- import type { AnchorTarget } from "#html/$Anchor";
2
- import { _bind, _Object_assign, forEach } from "#lib/native";
3
- import type { $NodeContentResolver } from "#node/$Node";
4
- import type { Page } from "./node/Page";
5
- import { Route } from "./node/Route";
6
- import { Router } from "./node/Router";
7
- import { RouterAnchor } from "./node/RouterAnchor";
8
- export * from "./node/Route";
9
- export * from "./node/Router";
10
- export * from "./node/Page";
11
- export * from "./node/RouterAnchor";
12
-
13
- declare module 'amateras/core' {
14
- export function $<P extends string>(nodeName: 'route', path: P, builder: RouteBuilder<Route<P>, RouteDataResolver<P>>): Route<P>;
15
- export function $(nodeName: 'router', page?: Page<any>): Router;
16
- export function $(nodeName: 'ra'): RouterAnchor;
17
- export namespace $ {
18
- export function open(url: string | URL | Nullish, target?: AnchorTarget): typeof Router;
19
- export function replace(url: string | URL | Nullish): typeof Router;
20
- export function back(): typeof Router;
21
- export function forward(): typeof Router;
22
- }
23
- }
24
-
25
- declare global {
26
- interface GlobalEventHandlersEventMap {
27
- 'routeopen': Event;
28
- }
29
- }
30
-
31
- // assign methods
32
- _Object_assign($, {
33
- open: _bind(Router.open, Router),
34
- replace: _bind(Router.replace, Router),
35
- back: _bind(Router.back, Router),
36
- forward: _bind(Router.forward, Router)
37
- });
38
- // define styles
39
- forEach([
40
- `router{display:block}`,
41
- `page{display:block}`
42
- ], $.style);
43
- // assign nodes
44
- $.assign([
45
- ['router', Router],
46
- ['route', Route],
47
- ['ra', RouterAnchor]
48
- ])
49
-
50
- export type RouteData = { params: any, query: any }
51
- export type RouteDataResolver<P extends string> = { params: Prettify<PathParams<P>>, query: Prettify<PathQuery<P>> }
52
- export type AsyncRoute<P extends string> = () => Promise<{default: Route<P>}>
53
- export type RouteBuilder<R extends Route<any>, D extends RouteData> = (page: Page<R, D>) => OrPromise<$NodeContentResolver<Page<R, D>>>;
54
-
55
- type PathParams<Path> = Path extends `${infer Segment}/${infer Rest}`
56
- ? Segment extends `${string}:${infer Param}`
57
- ? Record<Param, string> & PathParams<Rest>
58
- : PathParams<Rest>
59
- : Path extends `${string}:${infer Param}?${infer Query}`
60
- ? Record<Param, string>
61
- : Path extends `${string}:${infer Param}`
62
- ? Record<Param, string>
63
- : {}
64
-
65
- type PathQuery<Path> = Path extends `${string}?${infer Segment}`
66
- ? PathQuery_SetRecord<Segment>
67
- : Path extends `&${infer Segment}`
68
- ? PathQuery_SetRecord<Segment>
69
- : {}
70
-
71
- type PathQuery_SetRecord<Segment extends string> = Segment extends `${infer Param}&${infer Rest}`
72
- ? Record<Param, string> & PathQuery<`&${Rest}`>
73
- : Record<Segment, string>
@@ -1,27 +0,0 @@
1
- import { chain } from "#lib/chain";
2
- import { isUndefined } from "#lib/native";
3
- import { $HTMLElement } from "#node/$HTMLElement";
4
- import type { RouteData } from "..";
5
- import type { Route } from "./Route";
6
-
7
- export class Page<R extends Route<any> = any, Data extends RouteData = any> extends $HTMLElement {
8
- route: R;
9
- page: this;
10
- params: Data['params'];
11
- query: Data['query'];
12
- #pageTitle: null | string = null;
13
- initial = false;
14
- constructor(route: R, data?: {params: any, query: any}) {
15
- super('page');
16
- this.route = route;
17
- this.page = this;
18
- this.params = data?.params ?? {};
19
- this.query = data?.query ?? {};
20
- }
21
-
22
- pageTitle(): string | null;
23
- pageTitle(title: string | null): this;
24
- pageTitle(title?: string | null) {
25
- return chain(this, arguments, () => this.#pageTitle, title, title => this.#pageTitle = title)
26
- }
27
- }
@@ -1,54 +0,0 @@
1
- import { _instanceof, _Object_fromEntries, _Array_from, _Promise } from "#lib/native";
2
- import { $Element } from "#node/$Element";
3
- import type { AsyncRoute, RouteBuilder, RouteDataResolver } from "..";
4
- import { Page } from "./Page";
5
-
6
- export abstract class BaseRouteNode<Path extends string = string> extends $Element {
7
- readonly path: Path;
8
- routes = new Map<string, Route<any>>()
9
- parent: BaseRouteNode<any> | null = null;
10
- builder: RouteBuilder<Route<Path>, RouteDataResolver<Path>> | AsyncRoute<Path>
11
- constructor(path: Path, builder: RouteBuilder<Route<Path>, RouteDataResolver<Path>> | AsyncRoute<Path>, nodeName: string) {
12
- super(nodeName);
13
- this.path = path;
14
- this.builder = builder;
15
- }
16
-
17
- route<P extends string, J extends `${Path}${P}`>(
18
- path: P,
19
- resolver: RouteBuilder<Route<J>, RouteDataResolver<J>> | Route<J> | AsyncRoute<J>,
20
- fn?: (route: Route<J>) => Route<J>
21
- ) {
22
- const fullPath = `${this.path}${path}`;
23
- if (_instanceof(resolver, Route) && fullPath !== resolver.path) throw `Pathname not matched: ${path}`
24
- const route = resolver instanceof Route ? resolver : new Route(fullPath as J, resolver);
25
- route.parent = this;
26
- fn && fn(route);
27
- this.routes.set(path, route);
28
- return this;
29
- }
30
- }
31
-
32
- export class Route<Path extends string = string> extends BaseRouteNode<Path> {
33
- constructor(path: Path, builder: RouteBuilder<Route<Path>, RouteDataResolver<Path>> | AsyncRoute<Path>) {
34
- super(path, builder, 'route');
35
- }
36
-
37
- async build(data: {params: any, query: any} = {params: {}, query: {}}, page?: Page) {
38
- page = page ?? new Page(this, data);
39
- page.params = data.params;
40
- page.initial = true;
41
- let resolver: any = this.builder(page);
42
- if (_instanceof(resolver, _Promise)) {
43
- const result = await resolver as any;
44
- // Module import
45
- if (result[Symbol.toStringTag] === 'Module') {
46
- page.route = this;
47
- resolver = result.default.builder(page);
48
- }
49
- else resolver = result;
50
- }
51
- if (!_instanceof(resolver, Page)) page.content(resolver);
52
- return page;
53
- }
54
- }