@miliastry/quasar 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.
Files changed (172) hide show
  1. package/LICENSE +119 -0
  2. package/README.md +45 -0
  3. package/dist/Visuals/lyne.css +1027 -0
  4. package/dist/Visuals/osu.css +257 -0
  5. package/dist/index.d.mts +4563 -0
  6. package/dist/index.d.ts +4563 -0
  7. package/dist/index.js +11291 -0
  8. package/dist/index.mjs +11197 -0
  9. package/package.json +51 -0
  10. package/src/Analysis/Contracts/AnalysisReport.ts +26 -0
  11. package/src/Analysis/Contracts/Contribution.ts +76 -0
  12. package/src/Analysis/Contracts/Pass.ts +76 -0
  13. package/src/Analysis/Contracts/PipelineContext.ts +48 -0
  14. package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +292 -0
  15. package/src/Analysis/Passes/Analysis/MergeableColorAnalyzer.ts +112 -0
  16. package/src/Analysis/Passes/Analysis/RainbowAnalyzer.ts +233 -0
  17. package/src/Analysis/Passes/Analysis/WaveAnalyzer.ts +211 -0
  18. package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +135 -0
  19. package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +84 -0
  20. package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +99 -0
  21. package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +119 -0
  22. package/src/Analysis/Passes/Decision/DefaultDecision.ts +139 -0
  23. package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +179 -0
  24. package/src/Analysis/Passes/Transform/CollapseGradientTransform.ts +176 -0
  25. package/src/Analysis/Passes/Transform/MergeColorsTransform.ts +126 -0
  26. package/src/Analysis/Passes/Transform/RainbowCollapseTransform.ts +83 -0
  27. package/src/Analysis/Passes/Transform/WaveCollapseTransform.ts +88 -0
  28. package/src/Analysis/Passes/Utility/CharacterCountAnalyzer.ts +45 -0
  29. package/src/Analysis/Pipeline/Pipeline.ts +133 -0
  30. package/src/Analysis/Pipeline/PipelineBuilder.ts +55 -0
  31. package/src/Analysis/Pipeline/PipelineStage.ts +19 -0
  32. package/src/Analysis/Utils/color-utils.ts +132 -0
  33. package/src/Analysis/__tests__/Integration.test.ts +162 -0
  34. package/src/Analysis/__tests__/Pipeline.test.ts +133 -0
  35. package/src/Analysis/index.ts +52 -0
  36. package/src/BBCode/BBCodeDocumentModel.ts +175 -0
  37. package/src/BBCode/BBCodeToGreenNode.ts +755 -0
  38. package/src/BBCode/Parser.ts +384 -0
  39. package/src/BBCode/index.ts +12 -0
  40. package/src/Collab/positions.ts +91 -0
  41. package/src/Commands/Command.ts +44 -0
  42. package/src/Commands/CommandRegistry.ts +78 -0
  43. package/src/Commands/DeleteNode.ts +20 -0
  44. package/src/Commands/InsertText.ts +21 -0
  45. package/src/Commands/SplitMerge.ts +28 -0
  46. package/src/Commands/WrapInTag.ts +21 -0
  47. package/src/Commands/index.ts +6 -0
  48. package/src/Diff/TreeDiffer.ts +264 -0
  49. package/src/Diff/__tests__/TreeDiffer.test.ts +65 -0
  50. package/src/Diff/index.ts +2 -0
  51. package/src/Events/EventBus.ts +160 -0
  52. package/src/Events/index.ts +2 -0
  53. package/src/Formatter/Formatter.ts +54 -0
  54. package/src/Formatter/index.ts +2 -0
  55. package/src/HTML/HTMLDocumentModel.ts +35 -0
  56. package/src/HTML/HTMLToGreenNode.ts +290 -0
  57. package/src/Incremental/ChangeTracker.ts +105 -0
  58. package/src/Incremental/IncrementalParser.ts +591 -0
  59. package/src/Incremental/__tests__/IncrementalParser.test.ts +164 -0
  60. package/src/Incremental/index.ts +4 -0
  61. package/src/Lexer/BBCodeLexer.ts +382 -0
  62. package/src/Lexer/Lexer.ts +181 -0
  63. package/src/Lexer/index.ts +10 -0
  64. package/src/Linter/Linter.ts +193 -0
  65. package/src/Linter/index.ts +2 -0
  66. package/src/Markdown/MarkdownAST.ts +112 -0
  67. package/src/Markdown/MarkdownDocumentModel.ts +55 -0
  68. package/src/Markdown/MarkdownLexer.ts +203 -0
  69. package/src/Markdown/MarkdownParser.ts +455 -0
  70. package/src/Markdown/MarkdownToGreenNode.ts +153 -0
  71. package/src/Model/DocumentModel.ts +694 -0
  72. package/src/Model/NodeFactory.ts +117 -0
  73. package/src/Model/TagRegistry.ts +495 -0
  74. package/src/Model/index.ts +5 -0
  75. package/src/Plugins/PluginAPI.ts +119 -0
  76. package/src/Plugins/PluginRegistry.ts +132 -0
  77. package/src/Plugins/index.ts +3 -0
  78. package/src/Queries/QueryEngine.ts +152 -0
  79. package/src/Queries/index.ts +1 -0
  80. package/src/RenderPipeline/RenderPipeline.ts +125 -0
  81. package/src/RenderPipeline/RenderTree.ts +134 -0
  82. package/src/RenderPipeline/index.ts +4 -0
  83. package/src/Semantic/SemanticAnalyzer.ts +506 -0
  84. package/src/Semantic/index.ts +2 -0
  85. package/src/Symbols/SymbolTable.ts +124 -0
  86. package/src/Symbols/index.ts +1 -0
  87. package/src/Syntax/GreenNode.ts +324 -0
  88. package/src/Syntax/GreenNodePool.ts +269 -0
  89. package/src/Syntax/NodeMatcher.ts +370 -0
  90. package/src/Syntax/RedNode.ts +569 -0
  91. package/src/Syntax/RedNodeStore.ts +184 -0
  92. package/src/Syntax/TreeBuilder.ts +214 -0
  93. package/src/Syntax/__tests__/GreenNode.test.ts +33 -0
  94. package/src/Syntax/__tests__/RedNode.test.ts +81 -0
  95. package/src/Syntax/__tests__/RedNodeStore.test.ts +104 -0
  96. package/src/Syntax/greenEdit.ts +110 -0
  97. package/src/Syntax/hash.ts +30 -0
  98. package/src/Syntax/index.ts +12 -0
  99. package/src/Syntax/partition.ts +161 -0
  100. package/src/Syntax/preserveNodeIds.ts +201 -0
  101. package/src/Tests/ASTOptimizerIdempotence.test.ts +77 -0
  102. package/src/Tests/BlockPatcher.test.ts +437 -0
  103. package/src/Tests/BlockPatcherWindowed.test.ts +364 -0
  104. package/src/Tests/BoxDrawer.test.ts +217 -0
  105. package/src/Tests/BoxRichTitle.test.ts +105 -0
  106. package/src/Tests/Chars500kBenchmark.test.ts +151 -0
  107. package/src/Tests/Chars500kEdits.test.ts +321 -0
  108. package/src/Tests/CollabPositions.test.ts +146 -0
  109. package/src/Tests/CompilerPathProfiling.test.ts +186 -0
  110. package/src/Tests/DOMMorpher.test.ts +142 -0
  111. package/src/Tests/DomPatchPerf.test.ts +60 -0
  112. package/src/Tests/EffectSegments.snapshot.json +616 -0
  113. package/src/Tests/EffectSegments.test.ts +68 -0
  114. package/src/Tests/FindNodeAtOffset.test.ts +65 -0
  115. package/src/Tests/Fuzzer.test.ts +166 -0
  116. package/src/Tests/GreenNodePool.test.ts +153 -0
  117. package/src/Tests/Lexer.test.ts +238 -0
  118. package/src/Tests/LyneMode.test.ts +187 -0
  119. package/src/Tests/ModelCoherence.test.ts +180 -0
  120. package/src/Tests/Partition.test.ts +238 -0
  121. package/src/Tests/PluginTags.test.ts +150 -0
  122. package/src/Tests/ProblematicSection.test.ts +46 -0
  123. package/src/Tests/ProblematicSectionHTML.test.ts +58 -0
  124. package/src/Tests/RedReuse.test.ts +134 -0
  125. package/src/Tests/ReproDelete20k.test.ts +62 -0
  126. package/src/Tests/SemanticValidators.test.ts +136 -0
  127. package/src/Tests/StableNodeIds.test.ts +210 -0
  128. package/src/Tests/StudioColorBloat.test.ts +25 -0
  129. package/src/Tests/StudioDebugText.test.ts +27 -0
  130. package/src/Tests/StudioTrailingChar.test.ts +25 -0
  131. package/src/Tests/StudioValidText.test.ts +25 -0
  132. package/src/Tests/UrlImgBug.test.ts +23 -0
  133. package/src/Tests/VisualBuilderFidelity.test.ts +105 -0
  134. package/src/Tests/referenceDocument.ts +119 -0
  135. package/src/Transactions/Transaction.ts +176 -0
  136. package/src/Transactions/UndoManager.ts +111 -0
  137. package/src/Transactions/index.ts +3 -0
  138. package/src/Transformers/ASTOptimizer.ts +315 -0
  139. package/src/Transformers/GradientTransformer.ts +143 -0
  140. package/src/Transformers/GrowTransformer.ts +115 -0
  141. package/src/Transformers/RainbowTransformer.ts +121 -0
  142. package/src/Transformers/SineWaveTransformer.ts +130 -0
  143. package/src/Transformers/Transformer.ts +22 -0
  144. package/src/Types/core.ts +270 -0
  145. package/src/Types/diagnostics.ts +156 -0
  146. package/src/Types/index.ts +23 -0
  147. package/src/Types/operations.ts +180 -0
  148. package/src/Types/queries.ts +121 -0
  149. package/src/Types/symbols.ts +69 -0
  150. package/src/Types/tokens.ts +186 -0
  151. package/src/Utils/BBCodeGenerator.ts +126 -0
  152. package/src/Utils/ColorMath.ts +276 -0
  153. package/src/Utils/color.ts +112 -0
  154. package/src/Utils/dom-to-svg.test.ts +86 -0
  155. package/src/Utils/dom-to-svg.ts +615 -0
  156. package/src/Utils/treeTransformers.ts +717 -0
  157. package/src/Visitors/BBBlocksExporter.ts +69 -0
  158. package/src/Visitors/BBCodeExporter.ts +318 -0
  159. package/src/Visitors/BlockPatcher.ts +963 -0
  160. package/src/Visitors/DOMMorpher.ts +134 -0
  161. package/src/Visitors/HTMLRenderer.ts +1077 -0
  162. package/src/Visitors/JSONExporter.ts +66 -0
  163. package/src/Visitors/MarkdownExporter.ts +99 -0
  164. package/src/Visitors/SVGRenderer.ts +35 -0
  165. package/src/Visitors/TiptapExporter.ts +145 -0
  166. package/src/Visitors/Visitor.ts +48 -0
  167. package/src/Visitors/index.ts +9 -0
  168. package/src/Visuals/BoxDrawer.ts +175 -0
  169. package/src/Visuals/index.ts +42 -0
  170. package/src/Visuals/lyne.css +1027 -0
  171. package/src/Visuals/osu.css +257 -0
  172. package/src/index.ts +197 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * DocumentEngine — PluginAPI
3
+ *
4
+ * The public API exposed to plugins.
5
+ * Plugin authors use this to register tags, commands, validators, etc.
6
+ *
7
+ * Inspired by VSCode's `vscode` module.
8
+ */
9
+
10
+ import { DocumentModel } from '../Model/DocumentModel'
11
+ import { TagRegistry, type TagDefinition } from '../Model/TagRegistry'
12
+ import { CommandRegistry, type Command } from '../Commands'
13
+ import { SemanticAnalyzer, type Validator } from '../Semantic/SemanticAnalyzer'
14
+ import { Linter, type LintRule } from '../Linter/Linter'
15
+ import { RenderPipeline, type RenderHook } from '../RenderPipeline/RenderPipeline'
16
+ import { PluginRegistry, type PluginManifest, type PluginContribution } from './PluginRegistry'
17
+
18
+ export class PluginAPI {
19
+ readonly model: DocumentModel
20
+ readonly tags: TagRegistry
21
+ readonly commands: CommandRegistry
22
+ readonly semantic: SemanticAnalyzer
23
+ readonly linter: Linter
24
+ readonly renderer: RenderPipeline
25
+ readonly plugins: PluginRegistry
26
+
27
+ constructor(model: DocumentModel) {
28
+ this.model = model
29
+ this.tags = model.tagRegistry
30
+ this.commands = new CommandRegistry()
31
+ this.semantic = model.semanticAnalyzer
32
+ this.linter = new Linter()
33
+ this.renderer = new RenderPipeline()
34
+ this.plugins = new PluginRegistry()
35
+ }
36
+
37
+ /**
38
+ * Register a plugin with all its contributions.
39
+ *
40
+ * Example:
41
+ * ```ts
42
+ * api.registerPlugin({
43
+ * name: 'my-plugin',
44
+ * version: '1.0.0'
45
+ * }, {
46
+ * tags: [{ name: 'blur', kind: 'custom', ... }],
47
+ * commands: [{ id: 'my-command', ... }]
48
+ * })
49
+ * ```
50
+ */
51
+ registerPlugin(manifest: PluginManifest, contributions: PluginContribution): void {
52
+ const plugin = this.plugins.register(manifest, contributions)
53
+
54
+ // Apply contributions
55
+ if (contributions.tags) {
56
+ for (const tag of contributions.tags) {
57
+ this.tags.register(tag)
58
+ }
59
+ }
60
+
61
+ if (contributions.commands) {
62
+ for (const cmd of contributions.commands) {
63
+ this.commands.register(cmd)
64
+ }
65
+ }
66
+
67
+ if (contributions.validators) {
68
+ for (const v of contributions.validators) {
69
+ this.semantic.register(v)
70
+ }
71
+ }
72
+
73
+ if (contributions.lintRules) {
74
+ for (const rule of contributions.lintRules) {
75
+ this.linter.register(rule)
76
+ }
77
+ }
78
+
79
+ if (contributions.renderHooks) {
80
+ for (const hook of contributions.renderHooks) {
81
+ this.renderer.register(hook)
82
+ }
83
+ }
84
+
85
+ // Activate
86
+ this.plugins.activate(plugin)
87
+ }
88
+
89
+ /**
90
+ * Unregister a plugin and remove its contributions.
91
+ */
92
+ unregisterPlugin(name: string): void {
93
+ const plugin = this.plugins.get(name)
94
+ if (!plugin) return
95
+
96
+ if (plugin.contributions.tags) {
97
+ for (const tag of plugin.contributions.tags) {
98
+ this.tags.unregister(tag.name)
99
+ }
100
+ }
101
+ if (plugin.contributions.commands) {
102
+ for (const cmd of plugin.contributions.commands) {
103
+ this.commands.unregister(cmd.id)
104
+ }
105
+ }
106
+ if (plugin.contributions.validators) {
107
+ for (const v of plugin.contributions.validators) {
108
+ this.semantic.unregister(v.code)
109
+ }
110
+ }
111
+ if (plugin.contributions.lintRules) {
112
+ for (const rule of plugin.contributions.lintRules) {
113
+ this.linter.unregister(rule.code)
114
+ }
115
+ }
116
+
117
+ this.plugins.unregister(name)
118
+ }
119
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * DocumentEngine — PluginRegistry
3
+ *
4
+ * Manages the lifecycle of plugins/extensions.
5
+ * Each plugin registers contributions (tags, commands, validators, etc.)
6
+ * and can be loaded/unloaded at runtime.
7
+ *
8
+ * Inspired by VSCode's extension system.
9
+ */
10
+
11
+ import type { TagDefinition } from '../Model/TagRegistry'
12
+ import type { Command } from '../Commands/Command'
13
+ import type { Validator } from '../Semantic/SemanticAnalyzer'
14
+ import type { LintRule } from '../Linter/Linter'
15
+ import type { RenderHook } from '../RenderPipeline/RenderPipeline'
16
+
17
+ export interface PluginManifest {
18
+ name: string
19
+ version: string
20
+ description?: string
21
+ author?: string
22
+ /** Dependencies on other plugins */
23
+ dependencies?: string[]
24
+ }
25
+
26
+ export interface PluginContribution {
27
+ /** Tags to register */
28
+ tags?: TagDefinition[]
29
+ /** Commands to register */
30
+ commands?: Command[]
31
+ /** Semantic validators */
32
+ validators?: Validator[]
33
+ /** Lint rules */
34
+ lintRules?: LintRule[]
35
+ /** Render hooks */
36
+ renderHooks?: RenderHook[]
37
+ /** CSS to inject */
38
+ styles?: string[]
39
+ /** Initialization function */
40
+ activate?: () => void | Promise<void>
41
+ /** Cleanup function */
42
+ deactivate?: () => void
43
+ }
44
+
45
+ export interface Plugin {
46
+ manifest: PluginManifest
47
+ contributions: PluginContribution
48
+ /** Whether the plugin is currently active */
49
+ active: boolean
50
+ }
51
+
52
+ export class PluginRegistry {
53
+ private plugins: Map<string, Plugin> = new Map()
54
+
55
+ /**
56
+ * Register a plugin.
57
+ */
58
+ register(manifest: PluginManifest, contributions: PluginContribution): Plugin {
59
+ const plugin: Plugin = { manifest, contributions, active: false }
60
+ this.plugins.set(manifest.name, plugin)
61
+ return plugin
62
+ }
63
+
64
+ /**
65
+ * Unregister and deactivate a plugin.
66
+ */
67
+ unregister(name: string): boolean {
68
+ const plugin = this.plugins.get(name)
69
+ if (!plugin) return false
70
+ this.deactivate(plugin)
71
+ this.plugins.delete(name)
72
+ return true
73
+ }
74
+
75
+ /**
76
+ * Get a plugin by name.
77
+ */
78
+ get(name: string): Plugin | undefined {
79
+ return this.plugins.get(name)
80
+ }
81
+
82
+ /**
83
+ * Get all registered plugins.
84
+ */
85
+ getAll(): Plugin[] {
86
+ return Array.from(this.plugins.values())
87
+ }
88
+
89
+ /**
90
+ * Activate a plugin (calls activate function).
91
+ */
92
+ activate(plugin: Plugin): void {
93
+ if (plugin.active) return
94
+ try {
95
+ plugin.contributions.activate?.()
96
+ plugin.active = true
97
+ } catch (error) {
98
+ console.warn(`[PluginRegistry] Failed to activate plugin "${plugin.manifest.name}":`, error)
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Deactivate a plugin (calls deactivate function).
104
+ */
105
+ deactivate(plugin: Plugin): void {
106
+ if (!plugin.active) return
107
+ try {
108
+ plugin.contributions.deactivate?.()
109
+ plugin.active = false
110
+ } catch (error) {
111
+ console.warn(`[PluginRegistry] Failed to deactivate plugin "${plugin.manifest.name}":`, error)
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Activate all registered plugins.
117
+ */
118
+ activateAll(): void {
119
+ for (const [, plugin] of this.plugins) {
120
+ this.activate(plugin)
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Deactivate all plugins.
126
+ */
127
+ deactivateAll(): void {
128
+ for (const [, plugin] of this.plugins) {
129
+ this.deactivate(plugin)
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,3 @@
1
+ export { PluginAPI } from './PluginAPI'
2
+ export { PluginRegistry } from './PluginRegistry'
3
+ export type { Plugin, PluginManifest, PluginContribution } from './PluginRegistry'
@@ -0,0 +1,152 @@
1
+ /**
2
+ * DocumentEngine — QueryEngine
3
+ *
4
+ * CSS-inspired query engine for the document tree.
5
+ * Enables queries like:
6
+ * query('paragraph > bold')
7
+ * query('quote text')
8
+ * query('[color]')
9
+ * query('list > *')
10
+ *
11
+ * Useful for:
12
+ * - Finding nodes by structure
13
+ * - AI context gathering
14
+ * - Plugin discovery
15
+ * - Lint rule targeting
16
+ */
17
+
18
+ import { RedNode } from '../Syntax/RedNode'
19
+ import type { Query, QueryStep, QuerySelector, QueryMatch, QueryResult } from '../Types/queries'
20
+
21
+ export class QueryEngine {
22
+ /**
23
+ * Execute a query against a RedNode tree.
24
+ */
25
+ execute(query: Query, root: RedNode): QueryResult {
26
+ const startTime = performance.now()
27
+ const matches: QueryMatch[] = []
28
+
29
+ // Start from root, apply each step
30
+ let candidates: RedNode[] = [root]
31
+
32
+ for (const step of query.steps) {
33
+ if (candidates.length === 0) break
34
+
35
+ if (step.combinator === 'root') {
36
+ // First step: match against root
37
+ candidates = this.matchStep(step, candidates)
38
+ } else if (step.combinator === 'descendant') {
39
+ // Space: any descendant
40
+ const allDescendants: RedNode[] = []
41
+ for (const c of candidates) {
42
+ c.walk(node => {
43
+ if (node !== c) allDescendants.push(node)
44
+ })
45
+ }
46
+ candidates = this.matchStep(step, allDescendants)
47
+ } else if (step.combinator === 'child') {
48
+ // > direct child
49
+ const children: RedNode[] = []
50
+ for (const c of candidates) {
51
+ children.push(...c.children)
52
+ }
53
+ candidates = this.matchStep(step, children)
54
+ } else if (step.combinator === 'adjacent') {
55
+ // + next sibling
56
+ const nextSiblings: RedNode[] = []
57
+ for (const c of candidates) {
58
+ const next = c.nextSibling
59
+ if (next) nextSiblings.push(next)
60
+ }
61
+ candidates = this.matchStep(step, nextSiblings)
62
+ } else if (step.combinator === 'sibling') {
63
+ // ~ any sibling
64
+ const siblings: RedNode[] = []
65
+ for (const c of candidates) {
66
+ if (c.parent) {
67
+ siblings.push(...c.parent.children.filter(s => s.id !== c.id))
68
+ }
69
+ }
70
+ candidates = this.matchStep(step, siblings)
71
+ }
72
+ }
73
+
74
+ for (const node of candidates) {
75
+ matches.push({
76
+ node,
77
+ score: 1,
78
+ matchedSelectors: [],
79
+ })
80
+ }
81
+
82
+ return {
83
+ matches,
84
+ total: matches.length,
85
+ time: performance.now() - startTime,
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Filter candidates by applying a query step's selectors.
91
+ */
92
+ private matchStep(step: QueryStep, candidates: RedNode[]): RedNode[] {
93
+ return candidates.filter(node =>
94
+ step.selector.every(sel => this.matchesSelector(node, sel))
95
+ )
96
+ }
97
+
98
+ /**
99
+ * Check if a single node matches a single selector.
100
+ */
101
+ private matchesSelector(node: RedNode, selector: QuerySelector): boolean {
102
+ switch (selector.type) {
103
+ case 'kind':
104
+ return node.kind === selector.kind
105
+ case 'tag': {
106
+ const tagName = node.metadata?.tagName as string | undefined
107
+ return tagName === selector.tag || node.kind === selector.tag
108
+ }
109
+ case 'attribute': {
110
+ const val = node.metadata?.[selector.name]
111
+ if (selector.value !== undefined) {
112
+ return String(val) === selector.value
113
+ }
114
+ return val !== undefined && val !== null
115
+ }
116
+ case 'has_attribute':
117
+ return node.metadata?.[selector.name] !== undefined
118
+ case 'text': {
119
+ const text = node.text || ''
120
+ return text.includes(selector.pattern)
121
+ }
122
+ case 'has_child':
123
+ return node.children.some(c => this.matchesSelector(c, selector.selector))
124
+ case 'has_descendant': {
125
+ let found = false
126
+ node.walk(c => {
127
+ if (c !== node && this.matchesSelector(c, selector.selector)) {
128
+ found = true
129
+ return 'skip'
130
+ }
131
+ })
132
+ return found
133
+ }
134
+ case 'first_child':
135
+ return node.index === 0
136
+ case 'last_child':
137
+ return node.parent ? node.index === node.parent.children.length - 1 : false
138
+ case 'nth_child':
139
+ return node.index === selector.n - 1
140
+ case 'position':
141
+ if (selector.min !== undefined && node.index < selector.min) return false
142
+ if (selector.max !== undefined && node.index > selector.max) return false
143
+ return true
144
+ case 'depth':
145
+ if (selector.min !== undefined && node.depth < selector.min) return false
146
+ if (selector.max !== undefined && node.depth > selector.max) return false
147
+ return true
148
+ default:
149
+ return false
150
+ }
151
+ }
152
+ }
@@ -0,0 +1 @@
1
+ export { QueryEngine } from './QueryEngine'
@@ -0,0 +1,125 @@
1
+ /**
2
+ * DocumentEngine — RenderPipeline
3
+ *
4
+ * The Render Pipeline transforms a DocumentModel into renderable output.
5
+ * It sits between the model and the view layer.
6
+ *
7
+ * The pipeline is:
8
+ * DocumentModel → RenderTree → HTML/SVG/Canvas/React
9
+ *
10
+ * Plugins can intercept at any stage:
11
+ * - Add/transform render nodes
12
+ * - Provide custom renderers for custom tags
13
+ * - Post-process the output
14
+ */
15
+
16
+ import { RedNode } from '../Syntax/RedNode'
17
+ import { RenderTree, type RenderNode, type RenderVariant } from './RenderTree'
18
+ import type { TagRegistry, TagHandlerContext } from '../Model/TagRegistry'
19
+
20
+ export type RenderPhase =
21
+ | 'build' // DocumentModel → RenderNode tree
22
+ | 'transform' // Plugin transforms
23
+ | 'serialize' // RenderNode → Output string
24
+
25
+ export interface RenderHook {
26
+ phase: RenderPhase
27
+ hook: (node: RenderNode, context: RenderContext) => RenderNode | null
28
+ }
29
+
30
+ export interface RenderContext {
31
+ variant: RenderVariant
32
+ registry: TagRegistry
33
+ source: string
34
+ }
35
+
36
+ export class RenderPipeline {
37
+ private hooks: RenderHook[] = []
38
+
39
+ /**
40
+ * Register a render hook.
41
+ */
42
+ register(hook: RenderHook): void {
43
+ this.hooks.push(hook)
44
+ }
45
+
46
+ /**
47
+ * Unregister a render hook.
48
+ */
49
+ unregister(hook: RenderHook): void {
50
+ const idx = this.hooks.indexOf(hook)
51
+ if (idx >= 0) this.hooks.splice(idx, 1)
52
+ }
53
+
54
+ /**
55
+ * Render a RedNode tree to a string.
56
+ */
57
+ render(
58
+ root: RedNode,
59
+ variant: RenderVariant = 'html',
60
+ registry: TagRegistry,
61
+ source: string = '',
62
+ ): string {
63
+ const context: RenderContext = { variant, registry, source }
64
+
65
+ // Phase 1: Build render tree
66
+ let renderTree = this.buildRenderTree(root, context)
67
+
68
+ // Phase 2: Apply transformations
69
+ for (const hook of this.hooks) {
70
+ if (hook.phase === 'transform') {
71
+ renderTree = hook.hook(renderTree, context) ?? renderTree
72
+ }
73
+ }
74
+
75
+ // Phase 3: Serialize
76
+ for (const hook of this.hooks) {
77
+ if (hook.phase === 'serialize') {
78
+ const result = hook.hook(renderTree, context)
79
+ if (result) return result.text
80
+ }
81
+ }
82
+
83
+ // Default serialization
84
+ return RenderTree.toHTML(renderTree)
85
+ }
86
+
87
+ /**
88
+ * Build a RenderNode tree from a RedNode tree.
89
+ */
90
+ private buildRenderTree(node: RedNode, context: RenderContext): RenderNode {
91
+ // Text nodes
92
+ if (node.kind === 'text') {
93
+ return RenderTree.text(node.text)
94
+ }
95
+
96
+ // Document root
97
+ if (node.kind === 'document') {
98
+ const children = node.children.map(c => this.buildRenderTree(c, context))
99
+ return RenderTree.container('document', children)
100
+ }
101
+
102
+ // Check tag registry for custom render handlers
103
+ const tagDef = context.registry.getByKind(node.kind)
104
+ if (tagDef?.toRenderNode) {
105
+ return tagDef.toRenderNode({
106
+ node,
107
+ source: context.source,
108
+ visitChildren: (n) => {
109
+ const r = this.buildRenderTree(n, context)
110
+ return RenderTree.toHTML(r)
111
+ },
112
+ renderChild: (n) => this.buildRenderTree(n, context),
113
+ })
114
+ }
115
+
116
+ // Default rendering
117
+ const children = node.children.map(c => this.buildRenderTree(c, context))
118
+ const kind = node.kind
119
+
120
+ return RenderTree.container(kind, children, {
121
+ tag: node.metadata?.tagName,
122
+ ...node.metadata,
123
+ })
124
+ }
125
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * DocumentEngine — RenderTree
3
+ *
4
+ * An intermediate representation between DocumentModel and final output.
5
+ * The RenderTree is what the RenderPipeline processes.
6
+ *
7
+ * This abstraction allows:
8
+ * - Same DocumentModel → multiple render outputs (HTML, Canvas, SVG, PDF)
9
+ * - Plugin renderers that intercept/modify render nodes
10
+ * - Smooth visual transitions between document states
11
+ * - Partial rendering for performance
12
+ */
13
+
14
+ import type { NodeKind, NodeAttributes } from '../Types/core'
15
+
16
+ export type RenderVariant = 'html' | 'canvas' | 'svg' | 'pdf' | 'react' | 'text'
17
+
18
+ export interface RenderNode {
19
+ /** Semantic kind */
20
+ kind: NodeKind | string
21
+ /** Text content */
22
+ text: string
23
+ /** Child render nodes */
24
+ children: RenderNode[]
25
+ /** Props/attributes for the renderer */
26
+ props: Record<string, unknown>
27
+ /** Which render variant this node targets */
28
+ variant?: RenderVariant
29
+ /** CSS class names */
30
+ className?: string
31
+ /** Inline styles */
32
+ style?: Record<string, string | number>
33
+ /** Events/handlers (for interactive renders) */
34
+ events?: Record<string, string>
35
+ /** Metadata for renderers */
36
+ metadata?: Record<string, unknown>
37
+ }
38
+
39
+ export class RenderTree {
40
+ /**
41
+ * Create a render tree from a document node.
42
+ */
43
+ static fromNode(
44
+ kind: NodeKind | string,
45
+ text: string = '',
46
+ children: RenderNode[] = [],
47
+ props: Record<string, unknown> = {},
48
+ ): RenderNode {
49
+ return { kind, text, children, props }
50
+ }
51
+
52
+ /**
53
+ * Create a text render node.
54
+ */
55
+ static text(content: string): RenderNode {
56
+ return { kind: 'text', text: content, children: [], props: {} }
57
+ }
58
+
59
+ /**
60
+ * Create a container render node.
61
+ */
62
+ static container(
63
+ kind: NodeKind | string,
64
+ children: RenderNode[],
65
+ props: Record<string, unknown> = {},
66
+ ): RenderNode {
67
+ return { kind, text: '', children, props }
68
+ }
69
+
70
+ /**
71
+ * Serialize a render tree to HTML.
72
+ */
73
+ static toHTML(node: RenderNode): string {
74
+ if (node.kind === 'text') return this.escapeHtml(node.text)
75
+
76
+ const tag = this.kindToTag(node.kind)
77
+ const propsStr = this.propsToHtml(node)
78
+ const childrenStr = node.children.map(c => this.toHTML(c)).join('')
79
+
80
+ if (tag) {
81
+ return `<${tag}${propsStr}>${childrenStr}</${tag}>`
82
+ }
83
+
84
+ return childrenStr
85
+ }
86
+
87
+ private static kindToTag(kind: string): string | null {
88
+ const map: Record<string, string> = {
89
+ bold: 'strong',
90
+ italic: 'em',
91
+ underline: 'u',
92
+ strikethrough: 's',
93
+ inline_code: 'code',
94
+ code: 'pre',
95
+ heading: 'h2',
96
+ paragraph: 'p',
97
+ list: 'ul',
98
+ list_item: 'li',
99
+ quote: 'blockquote',
100
+ center: 'div',
101
+ notice: 'div',
102
+ image: 'img',
103
+ video: 'iframe',
104
+ audio: 'audio',
105
+ spoiler: 'span',
106
+ url: 'a',
107
+ email: 'a',
108
+ profile: 'a',
109
+ document: 'div',
110
+ group: 'div',
111
+ text: 'span',
112
+ }
113
+ return map[kind] ?? 'span'
114
+ }
115
+
116
+ private static propsToHtml(node: RenderNode): string {
117
+ const parts: string[] = []
118
+ if (node.className) parts.push(`class="${node.className}"`)
119
+ if (node.style && Object.keys(node.style).length > 0) {
120
+ const styles = Object.entries(node.style)
121
+ .map(([k, v]) => `${k}:${v}`)
122
+ .join(';')
123
+ parts.push(`style="${styles}"`)
124
+ }
125
+ return parts.length > 0 ? ' ' + parts.join(' ') : ''
126
+ }
127
+
128
+ private static escapeHtml(text: string): string {
129
+ return text
130
+ .replace(/&/g, '&amp;')
131
+ .replace(/</g, '&lt;')
132
+ .replace(/>/g, '&gt;')
133
+ }
134
+ }
@@ -0,0 +1,4 @@
1
+ export { RenderPipeline } from './RenderPipeline'
2
+ export type { RenderPhase, RenderHook, RenderContext } from './RenderPipeline'
3
+ export { RenderTree } from './RenderTree'
4
+ export type { RenderNode, RenderVariant } from './RenderTree'