amateras 0.10.1 → 0.11.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 (175) hide show
  1. package/README.md +30 -25
  2. package/build/core.js +1 -0
  3. package/build/css.js +1 -0
  4. package/build/for.js +1 -0
  5. package/build/i18n.js +1 -0
  6. package/build/idb.js +1 -0
  7. package/build/if.js +1 -0
  8. package/build/import-map.js +1 -0
  9. package/build/markdown.js +1 -0
  10. package/build/match.js +1 -0
  11. package/build/meta.js +1 -0
  12. package/build/prefetch.js +1 -0
  13. package/build/router.js +1 -0
  14. package/build/signal.js +1 -0
  15. package/build/ui.js +1 -0
  16. package/build/utils.js +1 -0
  17. package/build/widget.js +1 -0
  18. package/package.json +9 -7
  19. package/packages/core/package.json +19 -0
  20. package/packages/core/src/env.browser.ts +21 -0
  21. package/packages/core/src/env.node.ts +21 -0
  22. package/packages/core/src/global.ts +5 -0
  23. package/packages/core/src/index.ts +184 -0
  24. package/packages/core/src/lib/hmr.ts +145 -0
  25. package/packages/core/src/lib/symbols.ts +2 -0
  26. package/packages/core/src/structure/ElementProto.ts +95 -0
  27. package/packages/core/src/structure/GlobalState.ts +9 -0
  28. package/packages/core/src/structure/NodeProto.ts +18 -0
  29. package/packages/core/src/structure/Proto.ts +90 -0
  30. package/packages/core/src/structure/ProxyProto.ts +20 -0
  31. package/packages/core/src/structure/TextProto.ts +22 -0
  32. package/packages/core/src/structure/WidgetEvent.ts +17 -0
  33. package/packages/css/README.md +128 -0
  34. package/packages/css/package.json +15 -0
  35. package/packages/css/src/ext/colors/amber.ts +25 -0
  36. package/packages/css/src/ext/colors/blackwhite.ts +13 -0
  37. package/packages/css/src/ext/colors/blue.ts +25 -0
  38. package/packages/css/src/ext/colors/cyan.ts +25 -0
  39. package/packages/css/src/ext/colors/emerald.ts +25 -0
  40. package/packages/css/src/ext/colors/fuchsia.ts +25 -0
  41. package/packages/css/src/ext/colors/gray.ts +25 -0
  42. package/packages/css/src/ext/colors/green.ts +25 -0
  43. package/packages/css/src/ext/colors/indigo.ts +25 -0
  44. package/packages/css/src/ext/colors/lime.ts +25 -0
  45. package/packages/css/src/ext/colors/neutral.ts +25 -0
  46. package/packages/css/src/ext/colors/orange.ts +25 -0
  47. package/packages/css/src/ext/colors/pink.ts +25 -0
  48. package/packages/css/src/ext/colors/purple.ts +25 -0
  49. package/packages/css/src/ext/colors/red.ts +25 -0
  50. package/packages/css/src/ext/colors/rose.ts +25 -0
  51. package/packages/css/src/ext/colors/sky.ts +25 -0
  52. package/packages/css/src/ext/colors/slate.ts +25 -0
  53. package/packages/css/src/ext/colors/stone.ts +25 -0
  54. package/packages/css/src/ext/colors/teal.ts +25 -0
  55. package/packages/css/src/ext/colors/violet.ts +25 -0
  56. package/packages/css/src/ext/colors/yellow.ts +25 -0
  57. package/packages/css/src/ext/colors/zinc.ts +25 -0
  58. package/packages/css/src/ext/colors.ts +23 -0
  59. package/packages/css/src/ext/keyframes.ts +37 -0
  60. package/packages/css/src/ext/property.ts +68 -0
  61. package/packages/css/src/ext/variable.ts +51 -0
  62. package/packages/css/src/index.ts +103 -0
  63. package/packages/css/src/lib/cache.ts +27 -0
  64. package/packages/css/src/lib/colorAssign.ts +6 -0
  65. package/packages/css/src/lib/createRule.ts +31 -0
  66. package/packages/css/src/lib/utils.ts +1 -0
  67. package/packages/css/src/structure/$CSS.ts +4 -0
  68. package/packages/css/src/structure/$CSSKeyframes.ts +13 -0
  69. package/packages/css/src/structure/$CSSProperty.ts +21 -0
  70. package/packages/css/src/structure/$CSSRule.ts +39 -0
  71. package/packages/css/src/structure/$CSSVariable.ts +34 -0
  72. package/packages/css/src/types.ts +300 -0
  73. package/packages/for/package.json +15 -0
  74. package/packages/for/src/global.ts +7 -0
  75. package/packages/for/src/index.ts +15 -0
  76. package/packages/for/src/structure/For.ts +74 -0
  77. package/packages/hmr/package.json +13 -0
  78. package/packages/hmr/src/index.ts +27 -0
  79. package/packages/i18n/README.md +73 -0
  80. package/packages/i18n/package.json +15 -0
  81. package/packages/i18n/src/index.ts +78 -0
  82. package/packages/i18n/src/structure/I18n.ts +51 -0
  83. package/packages/i18n/src/structure/I18nDictionary.ts +31 -0
  84. package/packages/i18n/src/structure/I18nTranslation.ts +51 -0
  85. package/packages/i18n/src/types.ts +77 -0
  86. package/packages/idb/README.md +127 -0
  87. package/packages/idb/package.json +16 -0
  88. package/packages/idb/src/core.ts +6 -0
  89. package/packages/idb/src/index.ts +17 -0
  90. package/packages/idb/src/lib/$IDBRequest.ts +8 -0
  91. package/packages/idb/src/structure/$IDB.ts +63 -0
  92. package/packages/idb/src/structure/$IDBCursor.ts +34 -0
  93. package/packages/idb/src/structure/$IDBIndex.ts +48 -0
  94. package/packages/idb/src/structure/$IDBStore.ts +103 -0
  95. package/packages/idb/src/structure/$IDBStoreBase.ts +30 -0
  96. package/packages/idb/src/structure/$IDBTransaction.ts +38 -0
  97. package/packages/idb/src/structure/builder/$IDBBuilder.ts +229 -0
  98. package/packages/idb/src/structure/builder/$IDBStoreBuilder.ts +100 -0
  99. package/packages/if/package.json +15 -0
  100. package/packages/if/src/global.ts +15 -0
  101. package/packages/if/src/index.ts +51 -0
  102. package/packages/if/src/structure/Condition.ts +44 -0
  103. package/packages/if/src/structure/ConditionStatement.ts +25 -0
  104. package/packages/if/src/structure/Else.ts +6 -0
  105. package/packages/if/src/structure/ElseIf.ts +6 -0
  106. package/packages/if/src/structure/If.ts +6 -0
  107. package/packages/markdown/README.md +53 -0
  108. package/packages/markdown/package.json +15 -0
  109. package/packages/markdown/src/index.ts +3 -0
  110. package/packages/markdown/src/lib/type.ts +26 -0
  111. package/packages/markdown/src/lib/util.ts +21 -0
  112. package/packages/markdown/src/structure/Markdown.ts +57 -0
  113. package/packages/markdown/src/structure/MarkdownLexer.ts +111 -0
  114. package/packages/markdown/src/structure/MarkdownParser.ts +34 -0
  115. package/packages/markdown/src/syntax/alert.ts +46 -0
  116. package/packages/markdown/src/syntax/blockquote.ts +35 -0
  117. package/packages/markdown/src/syntax/bold.ts +11 -0
  118. package/packages/markdown/src/syntax/code.ts +11 -0
  119. package/packages/markdown/src/syntax/codeblock.ts +44 -0
  120. package/packages/markdown/src/syntax/heading.ts +14 -0
  121. package/packages/markdown/src/syntax/horizontalRule.ts +11 -0
  122. package/packages/markdown/src/syntax/image.ts +23 -0
  123. package/packages/markdown/src/syntax/italic.ts +11 -0
  124. package/packages/markdown/src/syntax/link.ts +46 -0
  125. package/packages/markdown/src/syntax/list.ts +121 -0
  126. package/packages/markdown/src/syntax/table.ts +67 -0
  127. package/packages/markdown/src/syntax/text.ts +19 -0
  128. package/packages/match/package.json +15 -0
  129. package/packages/match/src/global.ts +14 -0
  130. package/packages/match/src/index.ts +33 -0
  131. package/packages/match/src/structure/Case.ts +15 -0
  132. package/packages/match/src/structure/Default.ts +12 -0
  133. package/packages/match/src/structure/Match.ts +78 -0
  134. package/packages/meta/package.json +14 -0
  135. package/packages/meta/src/index.ts +36 -0
  136. package/packages/meta/src/lib/resolveMeta.ts +27 -0
  137. package/packages/meta/src/types.ts +36 -0
  138. package/packages/prefetch/package.json +14 -0
  139. package/packages/prefetch/src/index.ts +70 -0
  140. package/packages/router/README.md +18 -0
  141. package/packages/router/package.json +16 -0
  142. package/packages/router/src/global.ts +22 -0
  143. package/packages/router/src/index.ts +106 -0
  144. package/packages/router/src/structure/Link.ts +17 -0
  145. package/packages/router/src/structure/NavLink.ts +19 -0
  146. package/packages/router/src/structure/Page.ts +30 -0
  147. package/packages/router/src/structure/Route.ts +123 -0
  148. package/packages/router/src/structure/RouteGroup.ts +24 -0
  149. package/packages/router/src/structure/RouteNode.ts +54 -0
  150. package/packages/router/src/structure/RouteSlot.ts +34 -0
  151. package/packages/router/src/structure/Router.ts +192 -0
  152. package/packages/router/src/structure/RouterConstructor.ts +18 -0
  153. package/packages/router/src/types.ts +41 -0
  154. package/packages/signal/README.md +93 -0
  155. package/packages/signal/package.json +15 -0
  156. package/packages/signal/src/index.ts +97 -0
  157. package/packages/signal/src/lib/track.ts +18 -0
  158. package/packages/signal/src/structure/Signal.ts +59 -0
  159. package/packages/ui/package.json +14 -0
  160. package/packages/ui/src/index.ts +4 -0
  161. package/packages/ui/src/lib/slideshowAnimations.ts +39 -0
  162. package/packages/ui/src/structure/Radio.ts +77 -0
  163. package/packages/ui/src/structure/Slide.ts +11 -0
  164. package/packages/ui/src/structure/Slideshow.ts +99 -0
  165. package/packages/utils/package.json +18 -0
  166. package/packages/utils/src/global.ts +39 -0
  167. package/packages/utils/src/index.bun.ts +3 -0
  168. package/packages/utils/src/index.ts +2 -0
  169. package/packages/utils/src/lib/debugger.ts +14 -0
  170. package/packages/utils/src/lib/utils.ts +119 -0
  171. package/packages/utils/src/structure/UID.ts +18 -0
  172. package/packages/widget/README.md +29 -0
  173. package/packages/widget/package.json +14 -0
  174. package/packages/widget/src/index.ts +82 -0
  175. package/packages/widget/src/structure/Widget.ts +42 -0
@@ -0,0 +1,15 @@
1
+ import type { Condition } from "#structure/Condition";
2
+ import * as _Else from "#structure/Else";
3
+ import * as _ElseIf from "#structure/ElseIf";
4
+ import * as _If from "#structure/If";
5
+ import type { Signal } from "@amateras/signal";
6
+
7
+ declare global {
8
+ export var If: typeof _If.If
9
+ export var Else: typeof _Else.Else
10
+ export var ElseIf: typeof _ElseIf.ElseIf
11
+
12
+ export function $(statement: typeof _If.If, signal: Signal<any>, layout: _If.IfLayout): Condition;
13
+ export function $(statement: typeof _ElseIf.ElseIf, signal: Signal<any>, layout: _ElseIf.ElseIfLayout): Condition;
14
+ export function $(statement: typeof _Else.Else, layout: _Else.ElseLayout): Condition;
15
+ }
@@ -0,0 +1,51 @@
1
+ import './global';
2
+ import { If } from "#structure/If";
3
+ import { Proto } from "@amateras/core";
4
+ import { _instanceof, _null, isEqual } from "@amateras/utils";
5
+ import { Signal } from '@amateras/signal';
6
+ import { ElseIf } from '#structure/ElseIf';
7
+ import { Else } from '#structure/Else';
8
+ import { Condition } from '#structure/Condition';
9
+
10
+ globalThis.If = If;
11
+ globalThis.Else = Else;
12
+ globalThis.ElseIf = ElseIf;
13
+
14
+ /**This map store condition with parent Proto,
15
+ * all related Statement should be under same parent Proto.
16
+ */
17
+ let conditionMap = new WeakMap<Proto, Condition>();
18
+
19
+ // add condition statement craft function
20
+ $.process.craft.add((value, arg1, arg2) => {
21
+ let parentProto = Proto.proto;
22
+ let condition = parentProto ? conditionMap.get(parentProto) : _null;
23
+ // when value equal If, mean this is a new start of condition statement
24
+ if (value === If) {
25
+ condition = new Condition();
26
+ if (parentProto) {
27
+ condition.parent = parentProto;
28
+ conditionMap.set(parentProto, condition);
29
+ }
30
+ }
31
+
32
+ // if condition is null, mean this is not a condition statement code
33
+ // or Else/ElseIf not in right place
34
+ if (!_instanceof(condition, Condition)) {
35
+ if (isEqual(value, [Else, ElseIf])) throw 'ElseIf/Else must be after If or ElseIf';
36
+ return;
37
+ }
38
+
39
+ // handle If/Else/ElseIf constructor
40
+ // add them into condition child statements
41
+ if (isEqual(value, [If, Else, ElseIf])) {
42
+ let args: [Signal | null, () => void] = _instanceof(arg1, Signal) ? [arg1, arg2] : [_null, arg1];
43
+ let statement = new value(...args);
44
+ condition.statements.add(statement);
45
+ }
46
+ else {
47
+ if (parentProto) conditionMap.delete(parentProto);
48
+ return;
49
+ }
50
+ return condition;
51
+ })
@@ -0,0 +1,44 @@
1
+ import { symbol_Statement } from "@amateras/core";
2
+ import { ProxyProto } from "@amateras/core";
3
+ import { _null, forEach } from "@amateras/utils";
4
+ import type { ConditionStatement } from "./ConditionStatement";
5
+
6
+ export class Condition extends ProxyProto {
7
+ static override [symbol_Statement] = true;
8
+ statements = new Set<ConditionStatement>();
9
+ declare protos: Set<ConditionStatement>;
10
+ declare layout: null;
11
+ statement: ConditionStatement | null = _null;
12
+
13
+ override build() {
14
+ // run base build method with empty protos
15
+ super.build(false);
16
+ // set condition matched proto
17
+ this.validate()?.build();
18
+ // update function for Signal subscribe
19
+ let update = () => {
20
+ let matchProto = this.validate();
21
+ if (!matchProto?.builded) matchProto?.build();
22
+ if (this.statement === matchProto) return;
23
+ this.statement = matchProto ?? _null;
24
+ forEach(this.statements, proto => proto !== matchProto && proto.removeNode())
25
+ this.node?.replaceWith(...this.toDOM());
26
+ }
27
+ // build statements proto and subscribe expression signal
28
+ forEach(this.statements, proto => {
29
+ proto.exp$?.subscribe(update);
30
+ proto.disposers.add(() => proto.exp$?.unsubscribe(update));
31
+ })
32
+ return this;
33
+ }
34
+
35
+ validate() {
36
+ this.clear();
37
+ for (let proto of this.statements) {
38
+ if (proto.validate()) {
39
+ proto.parent = this;
40
+ return proto;
41
+ }
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,25 @@
1
+ import { symbol_Statement } from "@amateras/core";
2
+ import { Proto } from "@amateras/core";
3
+ import type { Signal } from "@amateras/signal";
4
+
5
+ export abstract class ConditionStatement extends Proto {
6
+ static override [symbol_Statement] = true;
7
+ exp$: Signal<any> | null;
8
+ declare layout: $.Layout;
9
+ builded = false;
10
+ constructor(expression: Signal<any> | null, layout: $.Layout) {
11
+ super(layout);
12
+ this.exp$ = expression;
13
+ }
14
+
15
+ override build(children?: boolean): this {
16
+ super.build(children);
17
+ this.builded = true;
18
+ return this;
19
+ }
20
+
21
+ validate() {
22
+ if (!this.exp$) return true;
23
+ return !!this.exp$.value;
24
+ }
25
+ }
@@ -0,0 +1,6 @@
1
+ import { ConditionStatement } from "./ConditionStatement";
2
+
3
+ export type ElseLayout = () => void;
4
+ export class Else extends ConditionStatement {
5
+ declare statementType: 'Else'
6
+ }
@@ -0,0 +1,6 @@
1
+ import { ConditionStatement } from "./ConditionStatement";
2
+
3
+ export type ElseIfLayout = () => void;
4
+ export class ElseIf extends ConditionStatement {
5
+ declare statementType: 'ElseIf'
6
+ }
@@ -0,0 +1,6 @@
1
+ import { ConditionStatement } from "./ConditionStatement";
2
+
3
+ export type IfLayout = () => void;
4
+ export class If extends ConditionStatement {
5
+ declare statementType: 'If'
6
+ }
@@ -0,0 +1,53 @@
1
+ # amateras/markdown
2
+
3
+ ## Usage
4
+ ```ts
5
+ import { Markdown } from 'amateras/markdown';
6
+
7
+ const markdown = new Markdown();
8
+
9
+ markdown.parseHTML('# Title'); // => <h1>Title</h1>
10
+ ```
11
+
12
+ ## Add Custom Markdown Rules
13
+ ```ts
14
+ markdown.lexer.blockTokenizers.set('CUSTOM_TYPE', {
15
+ regex: /#! (.+)/,
16
+ handle: matches => {
17
+ content: lexer.inlineTokenize(matches[1]!)
18
+ }
19
+ })
20
+
21
+ markdown.parser.processors.set('CUSTOM_TYPE', token => {
22
+ return `<custom>${markdown.parser.parse(token.content!)}</custom>`
23
+ })
24
+ ```
25
+
26
+ ## Import Syntax
27
+ ```ts
28
+ import { MarkdownParser, MarkdownLexer } from 'amateras/markdown';
29
+ import { blockquoteProcessor, blockquoteTokenizer } from 'amateras/markdown/syntax/blockquote';
30
+ import { headingProcessor, headingTokenizer } from 'amateras/markdown/syntax/heading';
31
+
32
+ const lexer = new MarkdownLexer();
33
+ const parser = new MarkdownParser();
34
+
35
+ lexer.use(
36
+ blockquoteTokenizer,
37
+ headingTokenizer
38
+ )
39
+
40
+ parser.use(
41
+ blockquoteProcessor,
42
+ headingProcessor
43
+ )
44
+
45
+ function parseHTML(str: string) {
46
+ const tokens = lexer.blockTokenize(str);
47
+ return parser.parse(tokens);
48
+ }
49
+
50
+ parseHTML('# Title') // => <h1>Title</h1>
51
+ parseHTML('> This is Blockquote') // => <blockquote>This is Blockquote</blockquote>
52
+ parseHTML('- List') // => - List
53
+ ```
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@amateras/markdown",
3
+ "peerDependencies": {
4
+ "@amateras/core": "workspace:*",
5
+ "@amateras/utils": "workspace:*"
6
+ },
7
+ "imports": {
8
+ "#structure/*": "./src/structure/*.ts",
9
+ "#lib/*": "./src/lib/*.ts",
10
+ "#node/*": "./src/node/*.ts"
11
+ },
12
+ "exports": {
13
+ ".": "./src/index.ts"
14
+ }
15
+ }
@@ -0,0 +1,3 @@
1
+ export * from '#structure/MarkdownLexer';
2
+ export * from '#structure/MarkdownParser';
3
+ export * from "#structure/Markdown";
@@ -0,0 +1,26 @@
1
+ export const INLINE = 'INLINE';
2
+ export const BLOCK = 'BLOCK';
3
+ export const TEXT = 'TEXT';
4
+ export const IMAGE = 'IMAGE';
5
+ export const LINK = 'LINK';
6
+ export const QUICK_LINK = 'QUICK_LINK';
7
+ export const CODE = 'CODE';
8
+ export const ITALIC = 'ITALIC';
9
+ export const BOLD = 'BOLD';
10
+ export const TEXT_LINE = 'TEXT_LINE';
11
+ export const HEADING = 'HEADING';
12
+ export const CODE_START = 'CODE_START';
13
+ export const CODE_LINE = 'CODE_LINE';
14
+ export const CODE_END = 'CODE_END';
15
+ export const UNORDERED_LIST_ITEM = 'UNORDERED_LIST_ITEM';
16
+ export const ORDERED_LIST_ITEM = 'ORDERED_LIST_ITEM';
17
+ export const BLOCKQUOTE = 'BLOCKQUOTE';
18
+ export const ALERT = 'ALERT';
19
+ export const ALERT_LINE = 'ALERT_LINE';
20
+ export const HORIZONTAL_RULE = 'HORIZONTAL_RULE';
21
+ export const TABLE = 'TABLE';
22
+ export const TABLE_ROW = 'TABLE_ROW';
23
+ export const TABLE_COLUMN = 'TABLE_COLUMN';
24
+ export const EMPTY_LINE = 'EMPTY_LINE'
25
+ export const INLINE_TEXT = "INLINE_TEXT";
26
+ export const INLINE_CONTENT = "INLINE_CONTENT"
@@ -0,0 +1,21 @@
1
+ import type { BlockTokenizer, InlineTokenizer, MarkdownLexer } from "#structure/MarkdownLexer";
2
+ import type { MarkdownParseProcessor, MarkdownParser } from "#structure/MarkdownParser";
3
+ import { forEach } from "@amateras/utils";
4
+
5
+ export const setBlockTokenizer = (lexer: MarkdownLexer, type: string, tokenizer: BlockTokenizer) => lexer.blockTokenizers.set(type, tokenizer);
6
+ export const setInlineTokenizer = (lexer: MarkdownLexer, type: string, tokenizer: InlineTokenizer) => lexer.inlineTokenizers.set(type, tokenizer);
7
+
8
+ export const setProcessor = (parser: MarkdownParser, type: string, processor: MarkdownParseProcessor) => parser.processors.set(type, processor);
9
+
10
+ export const htmltag = (tagname: string, content: string) => `<${tagname}>${content}</${tagname}>`
11
+
12
+ export const htmlEscapeChar = (str: string) => {
13
+ forEach([
14
+ ['&', '&amp;'],
15
+ ['<', '&lt;'],
16
+ ['>', '&gt;'],
17
+ ['"', '&quot;'],
18
+ ["'", '&#39;']
19
+ ] as [string, string][], group => str = str.replaceAll(...group))
20
+ return str;
21
+ }
@@ -0,0 +1,57 @@
1
+ import { alertProcessor, alertTokenizer } from "../syntax/alert";
2
+ import { blockquoteProcessor, blockquoteTokenizer } from "../syntax/blockquote";
3
+ import { boldProcessor, boldTokenizer } from "../syntax/bold";
4
+ import { codeProcessor, codeTokenizer } from "../syntax/code";
5
+ import { codeblockProcessor, codeblockTokenizer } from "../syntax/codeblock";
6
+ import { headingProcessor, headingTokenizer } from "../syntax/heading";
7
+ import { horizontalRuleProcessor, horizontalRuleTokenizer } from "../syntax/horizontalRule";
8
+ import { imageProcessor, imageTokenizer } from "../syntax/image";
9
+ import { italicProcessor, italicTokenizer } from "../syntax/italic";
10
+ import { linkProcessor, linkTokenizer } from "../syntax/link";
11
+ import { listProcessor, listTokenizer } from "../syntax/list";
12
+ import { tableProcessor, tableTokenizer } from "../syntax/table";
13
+ import { textLineProcessor, textProcessor } from "../syntax/text";
14
+ import { MarkdownLexer } from "./MarkdownLexer";
15
+ import { MarkdownParser } from "./MarkdownParser";
16
+
17
+ export class Markdown {
18
+ lexer = new MarkdownLexer();
19
+ parser = new MarkdownParser();
20
+ constructor() {
21
+ this.lexer.use(
22
+ headingTokenizer,
23
+ codeblockTokenizer,
24
+ listTokenizer,
25
+ tableTokenizer,
26
+ alertTokenizer,
27
+ blockquoteTokenizer,
28
+ horizontalRuleTokenizer,
29
+ imageTokenizer, // image tokenizer must before link
30
+ linkTokenizer, // link tokenizer must before bold and italic and code
31
+ codeTokenizer,
32
+ boldTokenizer,
33
+ italicTokenizer,
34
+ )
35
+
36
+ this.parser.use(
37
+ textProcessor,
38
+ imageProcessor,
39
+ linkProcessor,
40
+ codeProcessor,
41
+ italicProcessor,
42
+ boldProcessor,
43
+ textLineProcessor,
44
+ headingProcessor,
45
+ codeblockProcessor,
46
+ listProcessor,
47
+ tableProcessor,
48
+ alertProcessor,
49
+ blockquoteProcessor,
50
+ horizontalRuleProcessor
51
+ )
52
+ }
53
+
54
+ parseHTML(str: string) {
55
+ return this.parser.parse(this.lexer.blockTokenize(str));
56
+ }
57
+ }
@@ -0,0 +1,111 @@
1
+ import { BLOCK, EMPTY_LINE, INLINE_CONTENT, INLINE_TEXT, TEXT_LINE } from "#lib/type";
2
+ import { forEach, isString } from "@amateras/utils";
3
+
4
+ export class MarkdownLexer {
5
+ blockTokenizers = new Map<string, BlockTokenizer>();
6
+ inlineTokenizers = new Map<string, InlineTokenizer>();
7
+
8
+ blockTokenize(str: string) {
9
+ const lines = str?.split(/\r?\n/) ?? [];
10
+ const tokens: BlockToken[] = [];
11
+ let lineIndex = 0;
12
+ lineLoop: while (lineIndex < lines.length) {
13
+ let line = lines[lineIndex];
14
+ if (line === undefined) throw 'LINE ERROR';
15
+ let token: BlockToken | undefined;
16
+ for (const [type, tokenizer] of this.blockTokenizers) {
17
+ const matched = line.match(tokenizer.regex);
18
+ if (matched) {
19
+ const {content, multiLine, data} = tokenizer.handle(matched, lineIndex, lines);
20
+ token = { layout: BLOCK, type, content, data }
21
+ if (multiLine) {
22
+ tokens.push(token);
23
+ tokens.push(...multiLine.tokens)
24
+ lineIndex = multiLine.skip;
25
+ continue lineLoop;
26
+ }
27
+ break;
28
+ }
29
+ }
30
+ if (!token) token = {
31
+ layout: BLOCK,
32
+ ...(
33
+ line.length
34
+ ? { type: TEXT_LINE, content: this.inlineTokenize(line) }
35
+ : { type: EMPTY_LINE, content: [] }
36
+ )
37
+ };
38
+ tokens.push(token);
39
+ lineIndex++;
40
+ }
41
+ return tokens;
42
+ }
43
+
44
+ inlineTokenize(str: string): InlineToken[] {
45
+ const tokens: InlineToken[] = [];
46
+ let remainStr = str;
47
+ while (remainStr.length) {
48
+ let token: InlineToken | undefined;
49
+ for (const [type, tokenizer] of this.inlineTokenizers) {
50
+ const matched = remainStr.match(tokenizer.regex);
51
+ if (matched) {
52
+ const {index, 0: matchStr} = matched;
53
+ // handle before matched string
54
+ if (index != 0) tokens.push(...this.inlineTokenize(remainStr.substring(0, index)));
55
+ // handle matched string
56
+ const {content, data} = tokenizer.handle(matched);
57
+ token = { type, ...(isString(content) ? { layout: INLINE_TEXT, text: content } : { layout: INLINE_CONTENT, content })};
58
+ if (data) token.data = data;
59
+ remainStr = remainStr.substring(index! + matchStr.length);
60
+ break;
61
+ }
62
+ }
63
+ if (!token) {
64
+ token = { type: 'TEXT', layout: INLINE_TEXT, text: remainStr };
65
+ remainStr = '';
66
+ }
67
+ tokens.push(token);
68
+ }
69
+ return tokens;
70
+ }
71
+
72
+ use(...handle: ((parser: this) => void)[]) {
73
+ forEach(handle, fn => fn(this));
74
+ return this;
75
+ }
76
+ }
77
+
78
+ export type BlockTokenizer = {
79
+ regex: RegExp;
80
+ handle: (matches: RegExpMatchArray, position: number, lines: string[]) => { content: (InlineToken | BlockToken)[], multiLine?: BlockTokenizerMultiLine, data?: {[key: string]: any} };
81
+ }
82
+ export type BlockTokenizerMultiLine = {
83
+ skip: number;
84
+ tokens: BlockToken[];
85
+ }
86
+ export type InlineTokenizer = {
87
+ regex: RegExp;
88
+ handle: (matches: RegExpMatchArray) => { content: InlineToken[] | string, data?: {[key: string]: any} }
89
+ }
90
+
91
+ export interface TokenBase {
92
+ type: string;
93
+ layout: 'BLOCK' | 'INLINE_CONTENT' | 'INLINE_TEXT';
94
+ content?: Token[];
95
+ text?: string;
96
+ data?: {[key: string]: any};
97
+ }
98
+ export interface BlockToken extends TokenBase {
99
+ layout: 'BLOCK'
100
+ content: Token[];
101
+ }
102
+ export interface InlineTextToken extends TokenBase {
103
+ layout: 'INLINE_TEXT'
104
+ text: string;
105
+ }
106
+ export interface InlineContentToken extends TokenBase {
107
+ layout: 'INLINE_CONTENT'
108
+ content: (InlineToken)[];
109
+ }
110
+ export type InlineToken = InlineTextToken | InlineContentToken;
111
+ export type Token = BlockToken | InlineToken;
@@ -0,0 +1,34 @@
1
+ import { forEach, isString } from "@amateras/utils";
2
+ import { type Token } from "./MarkdownLexer";
3
+
4
+ export class MarkdownParser {
5
+ processors = new Map<string, MarkdownParseProcessor>();
6
+
7
+ parse(tokens: (Token)[]) {
8
+ let html = '';
9
+ let i = 0;
10
+ if (!tokens) return html;
11
+ while (i < tokens.length) {
12
+ const token = tokens[i]!;
13
+ const processor = this.processors.get(token.type);
14
+ if (processor) {
15
+ const result = processor(token, tokens.slice(i));
16
+ if (isString(result)) {
17
+ html += result;
18
+ } else {
19
+ html += result.html;
20
+ i += result.skipTokens;
21
+ }
22
+ }
23
+ i++;
24
+ }
25
+ return html;
26
+ }
27
+
28
+ use(...handle: ((parser: this) => void)[]) {
29
+ forEach(handle, fn => fn(this));
30
+ return this;
31
+ }
32
+ }
33
+
34
+ export type MarkdownParseProcessor = (token: Token, tokens: Token[]) => (string | { html: string, skipTokens: number })
@@ -0,0 +1,46 @@
1
+ import { ALERT, ALERT_LINE, BLOCK } from "#lib/type";
2
+ import { setBlockTokenizer, setProcessor } from "#lib/util";
3
+ import type { BlockToken, MarkdownLexer } from "#structure/MarkdownLexer";
4
+ import type { MarkdownParser } from "#structure/MarkdownParser";
5
+ import { uppercase } from "@amateras/utils";
6
+
7
+ export const alertProcessor = (parser: MarkdownParser) => setProcessor(parser, ALERT, (token, tokens) => {
8
+ let html = '';
9
+ let i = 1;
10
+ while (i < tokens.length) {
11
+ const token = tokens[i]!;
12
+ if (token.type !== ALERT_LINE) break;
13
+ html += parser.parse(token.content![0]!.content!);
14
+ i++;
15
+ }
16
+ const alertType = token.data?.alertType as string;
17
+ return {
18
+ html: `<blockquote class="alert alert-${alertType}"><p class="alert-title">${uppercase(alertType, 0, 1)}</p>${html}</blockquote>`,
19
+ skipTokens: i
20
+ }
21
+ })
22
+
23
+ export const alertTokenizer = (lexer: MarkdownLexer) => setBlockTokenizer(lexer, ALERT, {
24
+ regex: /^> ?\[!(?:(?:NOTE)|(?:TIP)|(?:IMPORTANT)|(?:WARNING)|(?:CAUTION))\]/,
25
+ handle(_, position, lines) {
26
+ const tokens: BlockToken[] = [];
27
+ const match = lines[position]!.match(/> ?\[!(.+?)\]/);
28
+ const alertType = match?.[1]?.toLowerCase();
29
+ position++
30
+ while (position < lines.length) {
31
+ const line = lines[position]!;
32
+ const match = line.match(/^> ?(.+)/);
33
+ if (match) tokens.push({ layout: BLOCK, type: ALERT_LINE, content: lexer.blockTokenize(match[1]!) });
34
+ else break;
35
+ position++;
36
+ }
37
+ return {
38
+ content: [],
39
+ data: { alertType },
40
+ multiLine: {
41
+ skip: position,
42
+ tokens
43
+ }
44
+ }
45
+ },
46
+ })
@@ -0,0 +1,35 @@
1
+ import { BLOCKQUOTE } from "#lib/type";
2
+ import { htmltag, setBlockTokenizer, setProcessor } from "#lib/util";
3
+ import type { MarkdownLexer } from "#structure/MarkdownLexer";
4
+ import type { MarkdownParser } from "#structure/MarkdownParser";
5
+
6
+ export const blockquoteProcessor = (parser: MarkdownParser) => setProcessor(parser, BLOCKQUOTE, (token, tokens) => {
7
+ let i = 0;
8
+ const blockquote = (deep: number) => {
9
+ let html = '';
10
+ while (i < tokens.length) {
11
+ const {type, content, data} = tokens[i]!;
12
+ if (type !== BLOCKQUOTE) break;
13
+ if (data!.deep > deep) html += blockquote(data!.deep);
14
+ else if (data!.deep < deep) break;
15
+ else { html += parser.parse(content!); i++ }
16
+ }
17
+ return htmltag('blockquote', html)
18
+ }
19
+ return {
20
+ html: blockquote(token.data!.deep),
21
+ skipTokens: i
22
+ }
23
+ })
24
+
25
+ export const blockquoteTokenizer = (lexer: MarkdownLexer) => setBlockTokenizer(lexer, BLOCKQUOTE, {
26
+ regex: /^(>+) ?(.+)?/,
27
+ handle(matches) {
28
+ return {
29
+ content: lexer.blockTokenize(matches[2] ?? ''),
30
+ data: {
31
+ deep: (matches[1]!.length - 1)
32
+ }
33
+ }
34
+ }
35
+ })
@@ -0,0 +1,11 @@
1
+ import { BOLD } from "#lib/type";
2
+ import { htmltag, setInlineTokenizer, setProcessor } from "#lib/util";
3
+ import type { MarkdownLexer } from "#structure/MarkdownLexer";
4
+ import type { MarkdownParser } from "#structure/MarkdownParser";
5
+
6
+ export const boldProcessor = (parser: MarkdownParser) => setProcessor(parser, BOLD, token => htmltag('b', parser.parse(token.content!)))
7
+
8
+ export const boldTokenizer = (lexer: MarkdownLexer) => setInlineTokenizer(lexer, BOLD, {
9
+ regex: /\*\*(.+?\*?)\*\*/,
10
+ handle: matches => ({ content: lexer.inlineTokenize(matches[1]!) })
11
+ })
@@ -0,0 +1,11 @@
1
+ import { CODE } from "#lib/type";
2
+ import { htmlEscapeChar, htmltag, setInlineTokenizer, setProcessor } from "#lib/util";
3
+ import type { MarkdownLexer } from "#structure/MarkdownLexer";
4
+ import type { MarkdownParser } from "#structure/MarkdownParser";
5
+
6
+ export const codeProcessor = (parser: MarkdownParser) => setProcessor(parser, CODE, token => htmltag('code', htmlEscapeChar(token.text!)))
7
+
8
+ export const codeTokenizer = (lexer: MarkdownLexer) => setInlineTokenizer(lexer, CODE, {
9
+ regex: /`(.+?)`/,
10
+ handle: matches => ({ content: matches[1]! })
11
+ })
@@ -0,0 +1,44 @@
1
+ import { BLOCK, CODE_END, CODE_LINE, CODE_START } from "#lib/type";
2
+ import { htmlEscapeChar, setBlockTokenizer, setProcessor } from "#lib/util";
3
+ import type { BlockToken, MarkdownLexer } from "#structure/MarkdownLexer";
4
+ import type { MarkdownParser } from "#structure/MarkdownParser";
5
+
6
+ export const codeblockProcessor = (parser: MarkdownParser) => setProcessor(parser, CODE_START, (token, tokens) => {
7
+ let html = '';
8
+ let i = 1;
9
+ while (i < tokens.length) {
10
+ const token = tokens[i]!;
11
+ if (token.type === CODE_END) break;
12
+ html += token.content![0]!.text;
13
+ i++;
14
+ }
15
+ return {
16
+ html: `<pre><code${token.data?.lang ? ` lang="${token.data.lang}"` : ''}>${htmlEscapeChar(html)}</code></pre>`,
17
+ skipTokens: i
18
+ }
19
+ })
20
+
21
+ export const codeblockTokenizer = (lexer: MarkdownLexer) => setBlockTokenizer(lexer, CODE_START, {
22
+ regex: /^```(\w+)?/,
23
+ handle: (matches, position, lines) => {
24
+ const tokens: BlockToken[] = [];
25
+ position++;
26
+ while (position < lines.length) {
27
+ const line = lines[position]!;
28
+ position++;
29
+ if (line.includes('```')) {
30
+ tokens.push({ layout: BLOCK, type: CODE_END, content: [] })
31
+ break;
32
+ }
33
+ tokens.push({ layout: BLOCK, type: CODE_LINE, content: [{ layout: "INLINE_TEXT", type: 'CODE_TEXT', text: `${line}\n` }] });
34
+ }
35
+ return {
36
+ content: [],
37
+ data: { lang: matches[1] },
38
+ multiLine: {
39
+ skip: position,
40
+ tokens
41
+ }
42
+ }
43
+ }
44
+ })
@@ -0,0 +1,14 @@
1
+ import { HEADING } from "#lib/type";
2
+ import { htmltag, setBlockTokenizer, setProcessor } from "#lib/util";
3
+ import type { MarkdownLexer } from "#structure/MarkdownLexer";
4
+ import type { MarkdownParser } from "#structure/MarkdownParser";
5
+
6
+ export const headingProcessor = (parser: MarkdownParser) => setProcessor(parser, HEADING, token => {
7
+ const tagname = `h${token.data!.level}`;
8
+ return htmltag(tagname, parser.parse(token.content!))
9
+ })
10
+
11
+ export const headingTokenizer = (lexer: MarkdownLexer) => setBlockTokenizer(lexer, HEADING, {
12
+ regex: /^(#+) (.+)/,
13
+ handle: matches => ({ content: lexer.inlineTokenize(matches[2]!), data: { level: matches[1]!.length } })
14
+ })