@miliastry/quasar 1.0.6 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/Visuals/lyne.css +374 -9
- package/dist/Visuals/osu.css +393 -11
- package/dist/index.js +10470 -3336
- package/dist/index.mjs +10336 -3337
- package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
- package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
- package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
- package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
- package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
- package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
- package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
- package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
- package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
- package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
- package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
- package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
- package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
- package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
- package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
- package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
- package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
- package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
- package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
- package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
- package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
- package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
- package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
- package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
- package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
- package/dist/types/Analysis/index.d.ts +40 -0
- package/dist/types/BBCode/BBCodeDocumentModel.d.ts +102 -0
- package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
- package/dist/types/BBCode/Parser.d.ts +78 -0
- package/dist/types/BBCode/index.d.ts +4 -0
- package/dist/types/Collab/positions.d.ts +55 -0
- package/dist/types/Commands/Command.d.ts +37 -0
- package/dist/types/Commands/CommandRegistry.d.ts +48 -0
- package/dist/types/Commands/DeleteNode.d.ts +7 -0
- package/dist/types/Commands/InsertText.d.ts +8 -0
- package/dist/types/Commands/SplitMerge.d.ts +9 -0
- package/dist/types/Commands/WrapInTag.d.ts +8 -0
- package/dist/types/Commands/index.d.ts +6 -0
- package/dist/types/Diff/TreeDiffer.d.ts +52 -0
- package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
- package/dist/types/Diff/index.d.ts +2 -0
- package/dist/types/Edits/EditPlan.d.ts +144 -0
- package/dist/types/Edits/Optimizer.d.ts +78 -0
- package/dist/types/Edits/Rules/Rule.d.ts +91 -0
- package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
- package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
- package/dist/types/Edits/Rules/index.d.ts +9 -0
- package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
- package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
- package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
- package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
- package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
- package/dist/types/Edits/applyEdits.d.ts +36 -0
- package/dist/types/Edits/index.d.ts +6 -0
- package/dist/types/Events/EventBus.d.ts +103 -0
- package/dist/types/Events/index.d.ts +2 -0
- package/dist/types/Formatter/Formatter.d.ts +32 -0
- package/dist/types/Formatter/index.d.ts +2 -0
- package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
- package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
- package/dist/types/Incremental/BracketIndex.d.ts +101 -0
- package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
- package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
- package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
- package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
- package/dist/types/Incremental/index.d.ts +4 -0
- package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
- package/dist/types/Lexer/Lexer.d.ts +54 -0
- package/dist/types/Lexer/index.d.ts +4 -0
- package/dist/types/Linter/Linter.d.ts +58 -0
- package/dist/types/Linter/index.d.ts +2 -0
- package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
- package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
- package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
- package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
- package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
- package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
- package/dist/types/Model/DocumentModel.d.ts +318 -0
- package/dist/types/Model/NodeFactory.d.ts +29 -0
- package/dist/types/Model/TagRegistry.d.ts +165 -0
- package/dist/types/Model/index.d.ts +5 -0
- package/dist/types/Plugins/PluginAPI.d.ts +44 -0
- package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
- package/dist/types/Plugins/index.d.ts +3 -0
- package/dist/types/Queries/QueryEngine.d.ts +32 -0
- package/dist/types/Queries/index.d.ts +1 -0
- package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
- package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
- package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
- package/dist/types/RenderPipeline/index.d.ts +4 -0
- package/dist/types/Repair/NestingRepair.d.ts +48 -0
- package/dist/types/Repair/index.d.ts +1 -0
- package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
- package/dist/types/Semantic/index.d.ts +2 -0
- package/dist/types/Symbols/SymbolTable.d.ts +47 -0
- package/dist/types/Symbols/index.d.ts +1 -0
- package/dist/types/Syntax/GreenNode.d.ts +154 -0
- package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
- package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
- package/dist/types/Syntax/RedNode.d.ts +280 -0
- package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
- package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
- package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
- package/dist/types/Syntax/greenEdit.d.ts +64 -0
- package/dist/types/Syntax/hash.d.ts +17 -0
- package/dist/types/Syntax/index.d.ts +8 -0
- package/dist/types/Syntax/nodeAttr.d.ts +63 -0
- package/dist/types/Syntax/partition.d.ts +67 -0
- package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
- package/dist/types/Tokens/TokenResolver.d.ts +25 -0
- package/dist/types/Tokens/index.d.ts +1 -0
- package/dist/types/Transactions/Transaction.d.ts +36 -0
- package/dist/types/Transactions/UndoManager.d.ts +63 -0
- package/dist/types/Transactions/index.d.ts +3 -0
- package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
- package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
- package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
- package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
- package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
- package/dist/types/Transformers/Transformer.d.ts +17 -0
- package/dist/types/Types/core.d.ts +104 -0
- package/dist/types/Types/diagnostics.d.ts +85 -0
- package/dist/types/Types/index.d.ts +11 -0
- package/dist/types/Types/operations.d.ts +109 -0
- package/dist/types/Types/queries.d.ts +67 -0
- package/dist/types/Types/symbols.d.ts +42 -0
- package/dist/types/Types/tokens.d.ts +75 -0
- package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
- package/dist/types/Utils/ColorMath.d.ts +92 -0
- package/dist/types/Utils/EffectMath.d.ts +643 -0
- package/dist/types/Utils/TagVocabulary.d.ts +104 -0
- package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
- package/dist/types/Utils/color.d.ts +29 -0
- package/dist/types/Utils/dom-to-svg.d.ts +28 -0
- package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
- package/dist/types/Utils/treeTransformers.d.ts +160 -0
- package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
- package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
- package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
- package/dist/types/Visitors/DOMMorpher.d.ts +19 -0
- package/dist/types/Visitors/HTMLRenderer.d.ts +492 -0
- package/dist/types/Visitors/JSONExporter.d.ts +42 -0
- package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
- package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
- package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
- package/dist/types/Visitors/Visitor.d.ts +35 -0
- package/dist/types/Visitors/index.d.ts +9 -0
- package/dist/types/Visuals/BoxDrawer.d.ts +54 -0
- package/dist/types/Visuals/LyneAudio.d.ts +8 -0
- package/dist/types/Visuals/index.d.ts +39 -0
- package/dist/types/index.d.ts +155 -0
- package/package.json +14 -10
- package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
- package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
- package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
- package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
- package/src/Analysis/Pipeline/Pipeline.ts +15 -2
- package/src/Analysis/index.ts +8 -2
- package/src/BBCode/BBCodeDocumentModel.ts +39 -1
- package/src/BBCode/BBCodeToGreenNode.ts +18 -12
- package/src/BBCode/Parser.ts +38 -1
- package/src/Edits/EditPlan.ts +290 -0
- package/src/Edits/Optimizer.ts +185 -0
- package/src/Edits/Rules/Rule.ts +145 -0
- package/src/Edits/Rules/dropEmptyTags.ts +110 -0
- package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
- package/src/Edits/Rules/index.ts +28 -0
- package/src/Edits/Rules/mergeAdjacent.ts +224 -0
- package/src/Edits/Rules/reorderWrappers.ts +154 -0
- package/src/Edits/Rules/shortenHex.ts +79 -0
- package/src/Edits/Rules/tagValue.ts +105 -0
- package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
- package/src/Edits/applyEdits.ts +54 -0
- package/src/Edits/index.ts +22 -0
- package/src/Events/EventBus.ts +18 -0
- package/src/HTML/HTMLToGreenNode.ts +193 -37
- package/src/Incremental/BracketIndex.ts +249 -0
- package/src/Incremental/IncrementalParser.ts +464 -41
- package/src/Lexer/BBCodeLexer.ts +25 -3
- package/src/Lexer/index.ts +1 -1
- package/src/Markdown/MarkdownAST.ts +65 -2
- package/src/Markdown/MarkdownLexer.ts +69 -14
- package/src/Markdown/MarkdownParser.ts +749 -62
- package/src/Markdown/MarkdownToGreenNode.ts +148 -27
- package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
- package/src/Model/DocumentModel.ts +138 -5
- package/src/Model/TagRegistry.ts +129 -134
- package/src/Reconciler/SurgicalReconciler.ts +460 -0
- package/src/Repair/NestingRepair.ts +116 -0
- package/src/Repair/index.ts +1 -0
- package/src/Semantic/SemanticAnalyzer.ts +1845 -65
- package/src/Syntax/GreenNodePool.ts +12 -12
- package/src/Syntax/RedNode.ts +17 -3
- package/src/Syntax/RedNodeStore.ts +9 -1
- package/src/Syntax/nodeAttr.ts +172 -0
- package/src/Tokens/TokenResolver.ts +55 -0
- package/src/Tokens/index.ts +1 -0
- package/src/Types/core.ts +15 -0
- package/src/Utils/ColorMath.ts +276 -55
- package/src/Utils/EffectMath.ts +2462 -0
- package/src/Utils/TagVocabulary.ts +141 -0
- package/src/Utils/dom-to-svg.ts +28 -12
- package/src/Visitors/BBBlocksExporter.ts +15 -1
- package/src/Visitors/BBCodeExporter.ts +365 -32
- package/src/Visitors/BlockPatcher.ts +123 -22
- package/src/Visitors/DOMMorpher.ts +44 -28
- package/src/Visitors/HTMLRenderer.ts +771 -205
- package/src/Visitors/MarkdownExporter.ts +98 -41
- package/src/Visitors/TiptapExporter.ts +1 -0
- package/src/Visitors/index.ts +1 -1
- package/src/Visuals/BoxDrawer.ts +56 -10
- package/src/Visuals/LyneAudio.ts +346 -0
- package/src/Visuals/index.ts +8 -4
- package/src/Visuals/lyne.css +374 -9
- package/src/Visuals/osu.css +393 -11
- package/src/index.ts +178 -8
- package/dist/index.d.mts +0 -4612
- package/dist/index.d.ts +0 -4612
- package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
- package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
- package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
- package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
- package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
- package/src/Analysis/__tests__/Integration.test.ts +0 -162
- package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
- package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
- package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
- package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
- package/src/Syntax/__tests__/RedNode.test.ts +0 -81
- package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
- package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
- package/src/Tests/BlockPatcher.test.ts +0 -437
- package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
- package/src/Tests/BoxDrawer.test.ts +0 -217
- package/src/Tests/BoxRichTitle.test.ts +0 -105
- package/src/Tests/Chars500kBenchmark.test.ts +0 -151
- package/src/Tests/Chars500kEdits.test.ts +0 -321
- package/src/Tests/CollabPositions.test.ts +0 -146
- package/src/Tests/CompilerPathProfiling.test.ts +0 -186
- package/src/Tests/DOMMorpher.test.ts +0 -142
- package/src/Tests/DomPatchPerf.test.ts +0 -60
- package/src/Tests/EffectSegments.snapshot.json +0 -616
- package/src/Tests/EffectSegments.test.ts +0 -68
- package/src/Tests/FindNodeAtOffset.test.ts +0 -65
- package/src/Tests/Fuzzer.test.ts +0 -166
- package/src/Tests/GreenNodePool.test.ts +0 -153
- package/src/Tests/Lexer.test.ts +0 -238
- package/src/Tests/LyneMode.test.ts +0 -291
- package/src/Tests/ModelCoherence.test.ts +0 -180
- package/src/Tests/Partition.test.ts +0 -238
- package/src/Tests/PluginTags.test.ts +0 -150
- package/src/Tests/ProblematicSection.test.ts +0 -46
- package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
- package/src/Tests/RedReuse.test.ts +0 -134
- package/src/Tests/ReproDelete20k.test.ts +0 -62
- package/src/Tests/SemanticValidators.test.ts +0 -136
- package/src/Tests/StableNodeIds.test.ts +0 -210
- package/src/Tests/StudioColorBloat.test.ts +0 -25
- package/src/Tests/StudioDebugText.test.ts +0 -27
- package/src/Tests/StudioTrailingChar.test.ts +0 -25
- package/src/Tests/StudioValidText.test.ts +0 -25
- package/src/Tests/UrlImgBug.test.ts +0 -23
- package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
- package/src/Tests/referenceDocument.ts +0 -119
- package/src/Utils/dom-to-svg.test.ts +0 -86
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — HTMLRenderer
|
|
3
|
+
*
|
|
4
|
+
* Renders the Document Model to HTML for preview.
|
|
5
|
+
* This is the main renderer for the visual BBCode preview.
|
|
6
|
+
*
|
|
7
|
+
* Uses the TagRegistry for custom rendering.
|
|
8
|
+
* Plugins can register custom renderers for preview components.
|
|
9
|
+
*/
|
|
10
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
11
|
+
import { Visitor } from './Visitor';
|
|
12
|
+
import type { TagRegistry } from '../Model/TagRegistry';
|
|
13
|
+
import type { BBCodeDialect } from '../BBCode/BBCodeToGreenNode';
|
|
14
|
+
import { type TokenResolverFn, type TokenSource } from '../Tokens';
|
|
15
|
+
export interface HTMLRendererOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Replicate osu! forum BBCode spacing quirks.
|
|
18
|
+
*
|
|
19
|
+
* NO LO LEE NADIE desde que el motor implementa las reglas reales de
|
|
20
|
+
* consumo de saltos (ver {@link HTMLRenderer.NEWLINE_RULES}). Sólo gobernaba
|
|
21
|
+
* dos heurísticas sobre `[code]` que resultaron ser falsas — osu no se come
|
|
22
|
+
* ningún salto ANTES de un bloque, y el resto de las reglas nunca estuvo
|
|
23
|
+
* condicionado. El espaciado ya no es opcional: Miliastry es "osu con
|
|
24
|
+
* esteroides" y rompe líneas igual, así que apagarlo no tendría a qué
|
|
25
|
+
* volver.
|
|
26
|
+
*
|
|
27
|
+
* Se conserva porque es API pública del renderer y quitarlo rompería a quien
|
|
28
|
+
* lo pase. Es candidato a borrarse en la próxima ruptura de versión.
|
|
29
|
+
*
|
|
30
|
+
* @deprecated Sin efecto. El espaciado de osu es ahora incondicional.
|
|
31
|
+
*/
|
|
32
|
+
osuBehaviour?: boolean;
|
|
33
|
+
/** Registry for resolving custom tags */
|
|
34
|
+
registry?: TagRegistry;
|
|
35
|
+
/** BBCode dialect to render for ('osu' | 'miliastry' | 'lyne') */
|
|
36
|
+
dialect?: BBCodeDialect;
|
|
37
|
+
/** Visual theme for markup classes ('osu' | 'lyne' | 'miliastry') */
|
|
38
|
+
theme?: 'osu' | 'lyne' | 'miliastry';
|
|
39
|
+
/** Safe media proxy callback to rewrite image and media URLs */
|
|
40
|
+
mediaProxy?: (url: string) => string;
|
|
41
|
+
/** Resolver for entity links like profile, guild, map */
|
|
42
|
+
entityLinkResolver?: (kind: string, value: string) => {
|
|
43
|
+
href: string;
|
|
44
|
+
external?: boolean;
|
|
45
|
+
} | null;
|
|
46
|
+
/**
|
|
47
|
+
* Forum-style `@mention` linkifier. Called with the bare name (no `@`);
|
|
48
|
+
* return null to leave the mention as plain text. Used by forum hosts that
|
|
49
|
+
* linkify `@username` to a profile route.
|
|
50
|
+
*/
|
|
51
|
+
mentionResolver?: (name: string) => {
|
|
52
|
+
href: string;
|
|
53
|
+
external?: boolean;
|
|
54
|
+
} | null;
|
|
55
|
+
/**
|
|
56
|
+
* Timestamp chip linkifier (`1:23`, `01:23.456`, `01:23:456`). Called with
|
|
57
|
+
* the millisecond offset and the display label; return null to leave the
|
|
58
|
+
* timestamp as plain text. Used by map hosts that deep-link into an editor.
|
|
59
|
+
*/
|
|
60
|
+
timestampResolver?: (ms: number, label: string) => {
|
|
61
|
+
href: string;
|
|
62
|
+
external?: boolean;
|
|
63
|
+
} | null;
|
|
64
|
+
/** Design tokens or resolver function */
|
|
65
|
+
tokens?: TokenSource;
|
|
66
|
+
}
|
|
67
|
+
export declare class HTMLRenderer extends Visitor<string> {
|
|
68
|
+
private tokenResolver?;
|
|
69
|
+
private options;
|
|
70
|
+
constructor(options?: HTMLRendererOptions);
|
|
71
|
+
setTokens(tokens?: TokenSource): void;
|
|
72
|
+
getTokenResolver(): TokenResolverFn | undefined;
|
|
73
|
+
private readonly BLOCK_TAGS;
|
|
74
|
+
private readonly INLINE_TAGS;
|
|
75
|
+
/**
|
|
76
|
+
* The ` data-node-id="…"` attribute, or `''` for nodes nobody looks up.
|
|
77
|
+
*
|
|
78
|
+
* ─── Why this is not emitted on everything ──────────────────────────────
|
|
79
|
+
*
|
|
80
|
+
* It used to be, carrying `node.id` — a process-global counter minted per
|
|
81
|
+
* RedNode, so a reparse renames every node in the document. Measured on a
|
|
82
|
+
* one-character edit: zero ids survive.
|
|
83
|
+
*
|
|
84
|
+
* `DOMMorpher` already noted that this rules the ids out as morph KEYS. The
|
|
85
|
+
* larger cost is that it also breaks the morpher's prefix/suffix trim, which
|
|
86
|
+
* uses `isEqualNode` — a comparison that includes attributes. An attribute
|
|
87
|
+
* that always differs makes every element compare unequal, so the trim never
|
|
88
|
+
* fires and the morpher walks the whole document. Measured on one keystroke
|
|
89
|
+
* in the reference document: **1665 `setAttribute` calls across 1725
|
|
90
|
+
* elements**, to write new numbers meaning the same thing, when the actual
|
|
91
|
+
* change was 10 insertions and 9 removals.
|
|
92
|
+
*
|
|
93
|
+
* ─── Why BLOCKS, and not stable ids ─────────────────────────────────────
|
|
94
|
+
*
|
|
95
|
+
* Making the id stable was the obvious repair and it does not work. Keying on
|
|
96
|
+
* the node's SPAN was measured: an edit at the END drops the writes to 6, but
|
|
97
|
+
* an insertion shifts every offset after it, so an edit near the START still
|
|
98
|
+
* cost 1637 — and spans are longer strings than `nN`, so the emitted HTML grew
|
|
99
|
+
* 10%. Stability under insertion is not something a position can have.
|
|
100
|
+
*
|
|
101
|
+
* The real observation is that no consumer ever wanted these on inline nodes.
|
|
102
|
+
* Both readers in the app are block-level: `usePreviewClick` walks up with
|
|
103
|
+
* `closest('[data-node-id]')` for *block selection*, and `useBlockHighlight`
|
|
104
|
+
* highlights and scrolls to a *block*. Emitting ids on the per-character
|
|
105
|
+
* spans of a gradient did not just cost — it made `closest()` stop at a
|
|
106
|
+
* character instead of the block the click meant.
|
|
107
|
+
*
|
|
108
|
+
* So the attribute goes where it is read. Inline nodes carry no id, compare
|
|
109
|
+
* equal, and let `isEqualNode` skip their subtrees natively; the handful of
|
|
110
|
+
* block containers that do carry one are few enough that their churn is
|
|
111
|
+
* noise.
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* Text leaves emit their content directly, with no wrapper element.
|
|
115
|
+
*
|
|
116
|
+
* They used to come wrapped in `<span class="bb-text">` — one extra DOM
|
|
117
|
+
* element per text leaf, which is HALF the preview's elements (measured:
|
|
118
|
+
* 1726 → 851 on a 19.6 KB post, 17251 → 8510 on a 196 KB one). The class
|
|
119
|
+
* earned none of it: it has no CSS rule anywhere in the repo, carries no
|
|
120
|
+
* `data-node-id` (text is not an id-bearing kind, so click mapping and
|
|
121
|
+
* highlighting never looked at it), and every style a text leaf can have
|
|
122
|
+
* still emits its own `<span style="…">` below.
|
|
123
|
+
*
|
|
124
|
+
* A/B in a production build, steady state: flush 41.4 → 39.7 ms at 19.6 KB
|
|
125
|
+
* and 55.4 → 46.4 ms at 196 KB, plus ~27% less HTML to serialize and parse.
|
|
126
|
+
*/
|
|
127
|
+
private textWrap;
|
|
128
|
+
/**
|
|
129
|
+
* A qué elementos se les pone `data-node-id`.
|
|
130
|
+
*
|
|
131
|
+
* `'all'` (por defecto) — a todos. Es lo que permite que un clic en
|
|
132
|
+
* CUALQUIER punto del preview señale ese nodo exacto en el editor: en un
|
|
133
|
+
* degradado cada carácter es su propio nodo, y sin id no hay nada a lo que
|
|
134
|
+
* `closest()` pueda agarrarse.
|
|
135
|
+
*
|
|
136
|
+
* Esto estuvo desactivado por una buena razón que ya no aplica. Los ids se
|
|
137
|
+
* regeneraban en cada parseo, así que el atributo cambiaba en TODOS los
|
|
138
|
+
* elementos por pulsación: `isEqualNode` no casaba nunca, el morpher no
|
|
139
|
+
* podía saltarse ningún subárbol y se medían 1665 `setAttribute` sobre 1725
|
|
140
|
+
* elementos para escribir números nuevos que significaban lo mismo. Con la
|
|
141
|
+
* identidad estable entre reparseos eso desapareció: un nodo que no cambia
|
|
142
|
+
* conserva su id, su HTML es idéntico y el camino rápido del morpher sigue
|
|
143
|
+
* funcionando.
|
|
144
|
+
*
|
|
145
|
+
* El coste que queda es el tamaño: ~10 bytes por elemento, que hay que
|
|
146
|
+
* serializar y parsear. Medido con A/B en la misma sesión sobre el post con
|
|
147
|
+
* degradados (19,6 KB, 852 elementos):
|
|
148
|
+
*
|
|
149
|
+
* solo bloques (9 con id): latencia 48 ms · p95 72 ms
|
|
150
|
+
* todos (808 con id): latencia 56 ms · p95 104 ms
|
|
151
|
+
*
|
|
152
|
+
* Se paga a propósito: la precisión del clic es una función que se pidió, y
|
|
153
|
+
* 56 ms sigue holgadamente dentro de lo que se percibe como inmediato.
|
|
154
|
+
* `'blocks'` queda disponible para quien priorice la latencia, y `'none'`
|
|
155
|
+
* para los consumidores de solo lectura (foros, render estático): sin
|
|
156
|
+
* `data-node-id` en absoluto, el HTML es más pequeño y no hay nada que
|
|
157
|
+
* mantenga vivos los nodos del árbol.
|
|
158
|
+
*/
|
|
159
|
+
static idMode: 'blocks' | 'all' | 'none';
|
|
160
|
+
/**
|
|
161
|
+
* Nesting depth of `[tables]` while rendering. `[col]`/`[row]`/`[th]` are
|
|
162
|
+
* table cells only INSIDE a `[tables]`; orphaned ones (e.g. `[col]` inside
|
|
163
|
+
* `[columns]`, or stray cells) render as plain inline spans — the same rule
|
|
164
|
+
* LYNE's forum renderer applies.
|
|
165
|
+
*/
|
|
166
|
+
private tableDepth;
|
|
167
|
+
/**
|
|
168
|
+
* ¿Estamos emitiendo el vocabulario de clases de osu!?
|
|
169
|
+
*
|
|
170
|
+
* osu estiliza box, spoilerbox, notice, imagemap, youtube, los alineados y
|
|
171
|
+
* los perfiles POR NOMBRE DE CLASE, no por estilo inline. Sobre una userpage
|
|
172
|
+
* real el HTML de Quasar salía sin estilo porque emitía su propio
|
|
173
|
+
* vocabulario (`<details>`, `.notice`, `.imagemap-container`…). Bajo
|
|
174
|
+
* `dialect: 'osu'` se emiten las clases y la estructura de osu; el resto de
|
|
175
|
+
* dialectos conserva la suya, que es la que sus hojas de estilo esperan.
|
|
176
|
+
*/
|
|
177
|
+
private isOsu;
|
|
178
|
+
/** osu recorta los saltos pegados a la apertura y al cierre de box/notice. */
|
|
179
|
+
private static trimOsuEdges;
|
|
180
|
+
private idAttr;
|
|
181
|
+
/**
|
|
182
|
+
* Kinds that carry `data-node-id`.
|
|
183
|
+
*
|
|
184
|
+
* The block containers a user can select or be scrolled to, plus the media
|
|
185
|
+
* nodes, which are atomic and clickable in their own right. Deliberately NOT
|
|
186
|
+
* `text` or any inline formatting kind — see {@link idAttr}.
|
|
187
|
+
*/
|
|
188
|
+
private static readonly ID_BEARING_KINDS;
|
|
189
|
+
visit(node: RedNode): string;
|
|
190
|
+
/**
|
|
191
|
+
* Ancho de cada tabla abierta, apilado igual que `tableDepth`.
|
|
192
|
+
*
|
|
193
|
+
* `getTableContext` corre una vez POR CELDA y recorría todas las filas de
|
|
194
|
+
* la tabla en cada llamada, con un `.filter()` —y su array— por fila: una
|
|
195
|
+
* tabla de 20×20 hacía 8.000 arrays intermedios por renderizado para
|
|
196
|
+
* llegar 400 veces al mismo número. Ahora se calcula una vez al entrar en
|
|
197
|
+
* la tabla. Va en una pila y no en un caché por nodo porque los hijos de un
|
|
198
|
+
* `RedNode` SON mutables (`appendChild`/`removeChild`), así que la
|
|
199
|
+
* identidad del nodo no garantiza que su estructura siga siendo la misma
|
|
200
|
+
* entre renderizados.
|
|
201
|
+
*/
|
|
202
|
+
private maxColsStack;
|
|
203
|
+
render(root: RedNode): string;
|
|
204
|
+
/**
|
|
205
|
+
* Render every child and concatenate.
|
|
206
|
+
*
|
|
207
|
+
* Replaces the `children.map(c => this.renderNode(c)).join('')` idiom, which
|
|
208
|
+
* was repeated at 16 call sites and allocated a closure plus an intermediate
|
|
209
|
+
* array of N strings at every level of the tree. Appending to one string lets
|
|
210
|
+
* the engine use its rope representation instead.
|
|
211
|
+
*/
|
|
212
|
+
/**
|
|
213
|
+
* Render the direct children of `node` to HTML (no wrapper element).
|
|
214
|
+
*
|
|
215
|
+
* Public so the incremental preview (`BlockPatcher`) can morph a block
|
|
216
|
+
* element's inner content without re-rendering the whole document.
|
|
217
|
+
*/
|
|
218
|
+
renderChildren(node: RedNode): string;
|
|
219
|
+
private renderNode;
|
|
220
|
+
/** How many newlines a construct swallows around its own tags. */
|
|
221
|
+
private static readonly NEWLINE_RULES;
|
|
222
|
+
/**
|
|
223
|
+
* What a Miliastry-only block does. This is what the old
|
|
224
|
+
* `isPrevBlockBoundary` / `isTrailingBlockBoundary` pair did for every block:
|
|
225
|
+
* eat the first newline after the open, every newline before the close, and
|
|
226
|
+
* the first newline after the close.
|
|
227
|
+
*/
|
|
228
|
+
private static readonly LEGACY_BLOCK_RULE;
|
|
229
|
+
/**
|
|
230
|
+
* Containers whose opening tag occupies no source text, so a backwards scan
|
|
231
|
+
* has to walk straight through them.
|
|
232
|
+
*/
|
|
233
|
+
private static readonly WIDTHLESS_OPEN;
|
|
234
|
+
/**
|
|
235
|
+
* Same for the closing side. `list_item` is here because `[/*]` is never
|
|
236
|
+
* written: an item ends where the next `[*]` or the `[/list]` begins, so
|
|
237
|
+
* `\s*\[/list\]` sees the newline that Quasar stores inside the item.
|
|
238
|
+
*/
|
|
239
|
+
private static readonly WIDTHLESS_CLOSE;
|
|
240
|
+
private newlineRule;
|
|
241
|
+
private static isNewlineNode;
|
|
242
|
+
private static isBlankText;
|
|
243
|
+
/**
|
|
244
|
+
* Whether this `spacing` / `empty_line` leaf is eaten by a neighbouring tag
|
|
245
|
+
* and therefore renders nothing.
|
|
246
|
+
*
|
|
247
|
+
* Each leaf is exactly ONE source newline (the parser splits a run into one
|
|
248
|
+
* node per `\n`), so the four scans below can be read straight off the
|
|
249
|
+
* regexes they mirror. A newline eaten by any of them is eaten: osu's passes
|
|
250
|
+
* run in a fixed order, but since a consumed newline is consumed whichever
|
|
251
|
+
* pass claimed it, the union is enough — the per-pass order only matters for
|
|
252
|
+
* a budget that could be spent elsewhere, and budgets here are counted from
|
|
253
|
+
* the tag outwards, exactly as `\n?\n?` counts.
|
|
254
|
+
*/
|
|
255
|
+
private isNewlineSwallowed;
|
|
256
|
+
/** `\[box\]\n*`, `\[quote\]\s*`, `[centre]\n`. */
|
|
257
|
+
private eatenByOpeningTag;
|
|
258
|
+
/** `\n*\[/box\]`, `\s*\[/quote\]`, `\s*\[/list\]`. */
|
|
259
|
+
private eatenByClosingTag;
|
|
260
|
+
/** `\[/box\]\n?`, `\[/list\]\n?\n?`. */
|
|
261
|
+
private eatenAfterClosingTag;
|
|
262
|
+
/** `\s*\[\*\]` — the only pass that eats whitespace BEFORE an opening tag. */
|
|
263
|
+
private eatenBeforeOpeningTag;
|
|
264
|
+
private renderError;
|
|
265
|
+
private wrapInline;
|
|
266
|
+
private wrapBlock;
|
|
267
|
+
/** Characters `escapeHtml` has to rewrite. Non-global on purpose: `test` must not carry `lastIndex`. */
|
|
268
|
+
private static readonly HTML_ESCAPE_RE;
|
|
269
|
+
private static readonly NAMED_COLORS;
|
|
270
|
+
/** Keyword-or-number CSS values (font-weight, font-style, text-decoration). */
|
|
271
|
+
private isCssKeyword;
|
|
272
|
+
/** Read a metadata field, falling back to the raw tag attribute. */
|
|
273
|
+
/**
|
|
274
|
+
* Lo único que osu! acepta en `[color=…]`.
|
|
275
|
+
*
|
|
276
|
+
* Su `BBCodeForDB::parseColour` sella el tag con un uid sólo si el valor
|
|
277
|
+
* matchea `#[[:xdigit:]]{6}` o `[[:alpha:]]+` — nada más. No valida que el
|
|
278
|
+
* nombre sea un color CSS de verdad (`banana` pasa), pero `#fff`, `#ffffffff`,
|
|
279
|
+
* `rgb(...)`, `$token` o un hex sin `#` no pasan. Sin uid, la segunda pasada
|
|
280
|
+
* no ve el tag y el opener *y* el closer quedan como texto en la página.
|
|
281
|
+
*/
|
|
282
|
+
private static readonly OSU_COLOR_RE;
|
|
283
|
+
/**
|
|
284
|
+
* `[color]` con el vocabulario de cada dialecto.
|
|
285
|
+
*
|
|
286
|
+
* Miliastry (y Lyne) aceptan a propósito más que osu: `#RGB`, `#RGBA`, un
|
|
287
|
+
* `$token` de diseño, nombres propios. Bajo `dialect: 'osu'` eso es una
|
|
288
|
+
* mentira: el editor pintaría color donde la página publicada muestra el
|
|
289
|
+
* BBCode crudo. Así que replicamos lo que hace osu — literal el opener,
|
|
290
|
+
* literal el closer, y los hijos renderizados normalmente en el medio.
|
|
291
|
+
*
|
|
292
|
+
* El chequeo mira el texto crudo del atributo, no el valor saneado: osu
|
|
293
|
+
* matchea sobre la fuente, así que `[color="#ffffff"]` (con comillas) también
|
|
294
|
+
* se le escapa.
|
|
295
|
+
*/
|
|
296
|
+
private renderColor;
|
|
297
|
+
private colorStyle;
|
|
298
|
+
private fontSizeStyle;
|
|
299
|
+
private fontStyle;
|
|
300
|
+
private renderLink;
|
|
301
|
+
private renderProfile;
|
|
302
|
+
private renderEntity;
|
|
303
|
+
/**
|
|
304
|
+
* El aviso que ocupa el sitio de un medio que no se puede pintar.
|
|
305
|
+
*
|
|
306
|
+
* Lleva `data-bb-empty` con la etiqueta de origen porque el camino
|
|
307
|
+
* HTML→BBCode lee de vuelta lo que hay en el lienzo: sin la marca, el texto
|
|
308
|
+
* del aviso entraba al documento como contenido y se comía la etiqueta. En
|
|
309
|
+
* `docs/ai/hxovc.bbcode`, con cinco `[img][/img]` vacíos, cada edición del
|
|
310
|
+
* bloque convertía otro en la frase «[img] missing source URL».
|
|
311
|
+
*/
|
|
312
|
+
private mediaError;
|
|
313
|
+
private renderImage;
|
|
314
|
+
private renderVideo;
|
|
315
|
+
private renderAudio;
|
|
316
|
+
private hexToRgba;
|
|
317
|
+
private renderNotice;
|
|
318
|
+
private renderTables;
|
|
319
|
+
/** Celdas directas de una fila, sin materializar un array intermedio. */
|
|
320
|
+
private static countCells;
|
|
321
|
+
/**
|
|
322
|
+
* Columnas de la tabla más ancha bajo `tableNode`, memoizado por tabla.
|
|
323
|
+
*
|
|
324
|
+
* `getTableContext` se llama una vez POR CELDA, y recorría todas las filas
|
|
325
|
+
* de la tabla en cada llamada, con un `.filter()` —y su array— por fila.
|
|
326
|
+
* Una tabla de 20×20 hacía 8.000 arrays intermedios en cada renderizado
|
|
327
|
+
* para llegar 400 veces al mismo número. El caché se vacía al empezar cada
|
|
328
|
+
* render, así que no retiene nodos entre documentos.
|
|
329
|
+
*/
|
|
330
|
+
private static widestRow;
|
|
331
|
+
private getTableContext;
|
|
332
|
+
private renderGallery;
|
|
333
|
+
private renderColumns;
|
|
334
|
+
private renderSeparator;
|
|
335
|
+
private renderScroll;
|
|
336
|
+
private renderAbbr;
|
|
337
|
+
private renderTooltip;
|
|
338
|
+
private renderRaw;
|
|
339
|
+
private renderPlain;
|
|
340
|
+
private renderAlign;
|
|
341
|
+
/**
|
|
342
|
+
* `[centre]` / `[left]` / `[right]` (y `[align=…]`).
|
|
343
|
+
*
|
|
344
|
+
* osu! no usa `text-align` inline: estiliza el bloque por nombre de clase,
|
|
345
|
+
* con la grafía británica `centre`. Fuera del dialecto osu el estilo inline
|
|
346
|
+
* se mantiene, porque ni Miliastry ni Lyne traen esas reglas.
|
|
347
|
+
*/
|
|
348
|
+
private renderAlignAs;
|
|
349
|
+
private renderEffect;
|
|
350
|
+
private renderAnim;
|
|
351
|
+
private renderContainer;
|
|
352
|
+
private static readonly STYLE_PROP_WHITELIST;
|
|
353
|
+
private renderStyleTag;
|
|
354
|
+
private renderQuote;
|
|
355
|
+
/**
|
|
356
|
+
* La estructura exacta que `bbcode-spoilerbox` de osu-web espera.
|
|
357
|
+
*
|
|
358
|
+
* El toggle de osu es JS: `js-spoilerbox__link` es el gancho del click y
|
|
359
|
+
* `js-spoilerbox__body` el panel que abre. Si falta cualquiera de las dos
|
|
360
|
+
* clases el box queda mudo, así que la estructura no es decorativa.
|
|
361
|
+
*/
|
|
362
|
+
private renderOsuSpoilerbox;
|
|
363
|
+
/** El rótulo de un box bajo osu: el del autor, o `SPOILER` en mayúsculas. */
|
|
364
|
+
private osuBoxTitle;
|
|
365
|
+
private renderSpoilerbox;
|
|
366
|
+
private renderBox;
|
|
367
|
+
/**
|
|
368
|
+
* Marca un `[box]`/`[spoilerbox]` sin título propio.
|
|
369
|
+
*
|
|
370
|
+
* El `<summary>` siempre lleva texto ("Box", "Spoiler"), así que sin esta
|
|
371
|
+
* marca el camino HTML→BBCode leía ese relleno como si el autor lo hubiera
|
|
372
|
+
* escrito y devolvía `[box=Box]`.
|
|
373
|
+
*/
|
|
374
|
+
private bareTitleAttr;
|
|
375
|
+
/**
|
|
376
|
+
* ¿El título del box lo escribió el autor, o es el relleno del parser?
|
|
377
|
+
*
|
|
378
|
+
* `BBCodeToGreenNode` ya deja `metadata.title = 'Box'`/`'Spoiler'` para un
|
|
379
|
+
* tag pelado, así que el `fallback` de `renderTitle` nunca llega a usarse:
|
|
380
|
+
* quien quiera otro rótulo por defecto tiene que preguntar por aquí.
|
|
381
|
+
*/
|
|
382
|
+
private hasOwnTitle;
|
|
383
|
+
private renderTitle;
|
|
384
|
+
/**
|
|
385
|
+
* `[box=Title:#hex]`, `[tables=striped:#hex]`, `[columns=2:#hex]` → un
|
|
386
|
+
* ` style="--<suffix>-accent:#hex;"` del que el CSS deriva la paleta
|
|
387
|
+
* (título + chevron y borde en boxw; gradientes/header/hover en tables;
|
|
388
|
+
* borde + fondo en columns). Devuelve '' si no hay color válido.
|
|
389
|
+
*/
|
|
390
|
+
private boxAccentStyle;
|
|
391
|
+
private renderList;
|
|
392
|
+
private renderListItem;
|
|
393
|
+
private renderCode;
|
|
394
|
+
private renderSVG;
|
|
395
|
+
/**
|
|
396
|
+
* Render an osu! BBCode imagemap.
|
|
397
|
+
*
|
|
398
|
+
* Structure:
|
|
399
|
+
* [imagemap]
|
|
400
|
+
* https://example.com/image.png ← first line = image URL
|
|
401
|
+
* 10 20 50 60 https://... Label ← subsequent lines = clickable areas
|
|
402
|
+
* [/imagemap]
|
|
403
|
+
*
|
|
404
|
+
* Each area line format: x y width height url [label]
|
|
405
|
+
* All values are PERCENTAGES (0–100) relative to the image dimensions.
|
|
406
|
+
* Uses CSS absolute positioning with percentage coordinates.
|
|
407
|
+
*/
|
|
408
|
+
private renderImagemap;
|
|
409
|
+
private collectNodeText;
|
|
410
|
+
/**
|
|
411
|
+
* Effect parameters off a node, with every colour re-validated.
|
|
412
|
+
*
|
|
413
|
+
* `startsWith('#')` was NOT a filter: `#a" onmouseover="alert(1)` passes
|
|
414
|
+
* it. Every stop has to survive the full colour allowlist or be dropped,
|
|
415
|
+
* because these values end up inside a `style` attribute.
|
|
416
|
+
*/
|
|
417
|
+
private effectParams;
|
|
418
|
+
/** Text inside an effect node, ignoring layout-only children. */
|
|
419
|
+
/**
|
|
420
|
+
* The plain text an effect modulates, line breaks included.
|
|
421
|
+
*
|
|
422
|
+
* A break is a childless `spacing` leaf carrying no text, so collecting
|
|
423
|
+
* it naively contributes nothing and the effect sees one long line.
|
|
424
|
+
* Every two-dimensional axis then collapses: `line` reports zero for
|
|
425
|
+
* the whole block, `column` stretches one row across the paragraph, and
|
|
426
|
+
* `radial` measures a rectangle one line tall. The break was being
|
|
427
|
+
* dropped from the OUTPUT as well, so a multi-line effect previewed as
|
|
428
|
+
* a single run-on line.
|
|
429
|
+
*
|
|
430
|
+
* The document's own plain text spells a break `\n` — that is what
|
|
431
|
+
* `stripColorWrappers` and the studio's sampler both count — so this
|
|
432
|
+
* agrees with them rather than inventing a third answer.
|
|
433
|
+
*/
|
|
434
|
+
private effectText;
|
|
435
|
+
private effectChildText;
|
|
436
|
+
/**
|
|
437
|
+
* Paint an effect's computed segments as spans.
|
|
438
|
+
*
|
|
439
|
+
* Shared by rainbow, grow and any modulated gradient, so the preview
|
|
440
|
+
* shows exactly the colours and sizes the BBCode export will contain
|
|
441
|
+
* rather than an approximation of them.
|
|
442
|
+
*/
|
|
443
|
+
private renderEffectSegments;
|
|
444
|
+
/**
|
|
445
|
+
* A gradient previews as a real CSS gradient when it is a plain
|
|
446
|
+
* left-to-right ramp, and as computed per-character spans otherwise.
|
|
447
|
+
*
|
|
448
|
+
* The CSS path is genuinely smoother — it interpolates per pixel rather
|
|
449
|
+
* than per glyph — but it can only express a linear sweep. A gradient
|
|
450
|
+
* with a waveform, a non-index axis or quantisation steps looked
|
|
451
|
+
* nothing like its own export while this was the only path.
|
|
452
|
+
*/
|
|
453
|
+
private renderGradient;
|
|
454
|
+
/**
|
|
455
|
+
* Escape for both text content and double-quoted attribute values.
|
|
456
|
+
*
|
|
457
|
+
* `'` is included because attribute values elsewhere in the codebase (and in
|
|
458
|
+
* consumer-supplied tag handlers) may be single-quoted; leaving it raw makes
|
|
459
|
+
* the escaping context-dependent, which is how injections get reintroduced.
|
|
460
|
+
*/
|
|
461
|
+
/**
|
|
462
|
+
* Escape the five HTML-significant characters.
|
|
463
|
+
*
|
|
464
|
+
* Was five chained `.replace(/x/g, …)` calls: five full scans of the string
|
|
465
|
+
* and up to five intermediate allocations for *every* text node, even though
|
|
466
|
+
* ordinary prose contains none of these characters. This tests once and
|
|
467
|
+
* returns the input untouched in that common case, then does a single pass
|
|
468
|
+
* when there is actually something to escape.
|
|
469
|
+
*/
|
|
470
|
+
/** Resolver configured at construction (read through a getter for brevity). */
|
|
471
|
+
private get mentionResolver();
|
|
472
|
+
/**
|
|
473
|
+
* `@mention` shape used by forum hosts: 3-15 word chars, boundaries that
|
|
474
|
+
* exclude longer usernames (same rule LYNE's forum uses). Non-global so
|
|
475
|
+
* `lastIndex` never leaks between calls.
|
|
476
|
+
*/
|
|
477
|
+
private static readonly MENTION_RE;
|
|
478
|
+
/**
|
|
479
|
+
* Linkify a plain-text leaf: `@mention` runs and (optionally) timestamp
|
|
480
|
+
* chips. Both run through their resolver; null leaves the run as text.
|
|
481
|
+
* Every interpolated string is HTML-escaped, and hrefs with dangerous
|
|
482
|
+
* protocols (javascript:, data:, vbscript:) are rejected (defence in depth;
|
|
483
|
+
* the resolvers themselves should never produce one).
|
|
484
|
+
*/
|
|
485
|
+
private linkifyText;
|
|
486
|
+
/** `1:23`, `01:23.456`, `01:23:456` — the editor deep-link shape. */
|
|
487
|
+
private static readonly TS_RE;
|
|
488
|
+
private linkifyTimestamps;
|
|
489
|
+
/** Root-relative, http(s), mailto and line:// hrefs are the only safe shapes. */
|
|
490
|
+
private isSafeHref;
|
|
491
|
+
private escapeHtml;
|
|
492
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — JSONExporter
|
|
3
|
+
*
|
|
4
|
+
* Exports the Document Model to JSON.
|
|
5
|
+
* Useful for:
|
|
6
|
+
* - Saving/loading documents in a structured format
|
|
7
|
+
* - Sending over the wire (collaboration)
|
|
8
|
+
* - Debugging and inspecting the document tree
|
|
9
|
+
* - Import/export between different tools
|
|
10
|
+
*/
|
|
11
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
12
|
+
import { Visitor } from './Visitor';
|
|
13
|
+
export interface JSONDocument {
|
|
14
|
+
version: number;
|
|
15
|
+
kind: string;
|
|
16
|
+
nodes: JSONNode[];
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface JSONNode {
|
|
20
|
+
id: string;
|
|
21
|
+
kind: string;
|
|
22
|
+
text: string;
|
|
23
|
+
version: number;
|
|
24
|
+
children: JSONNode[];
|
|
25
|
+
attributes?: Record<string, unknown>;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
range?: {
|
|
28
|
+
start: number;
|
|
29
|
+
end: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare class JSONExporter extends Visitor<string> {
|
|
33
|
+
/**
|
|
34
|
+
* Export a RedNode tree to a JSON string.
|
|
35
|
+
*/
|
|
36
|
+
visit(node: RedNode): string;
|
|
37
|
+
/**
|
|
38
|
+
* Export to a JSON object.
|
|
39
|
+
*/
|
|
40
|
+
toJSON(node: RedNode): JSONDocument;
|
|
41
|
+
private serializeNode;
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — MarkdownExporter
|
|
3
|
+
*
|
|
4
|
+
* Exports the Document Model to Markdown with hybrid BBCode embedding
|
|
5
|
+
* for features not natively supported in standard CommonMark (color, size,
|
|
6
|
+
* underline, alignment, collapsible boxes).
|
|
7
|
+
*/
|
|
8
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
9
|
+
import { Visitor } from './Visitor';
|
|
10
|
+
export declare class MarkdownExporter extends Visitor<string> {
|
|
11
|
+
visit(node: RedNode): string;
|
|
12
|
+
export(node: RedNode): string;
|
|
13
|
+
private exportNode;
|
|
14
|
+
private renderTitleMarkdown;
|
|
15
|
+
/**
|
|
16
|
+
* Read a BBCode tag's `=VALUE` attribute.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* El atributo del nodo, o `undefined` si no lleva.
|
|
20
|
+
*
|
|
21
|
+
* El recorte lo hace `nodeAttrValue`, el mismo lector del `HTMLRenderer`;
|
|
22
|
+
* aquí sólo se cambia el contrato del caso vacío. Este exportador encadena
|
|
23
|
+
* `metadata ?? atributo ?? contenido`, así que necesita un `undefined`
|
|
24
|
+
* explícito donde `nodeAttrValue` devuelve el texto crudo — es la única
|
|
25
|
+
* diferencia, y por eso no se llama directamente.
|
|
26
|
+
*/
|
|
27
|
+
private extractValue;
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
2
|
+
import { Visitor } from './Visitor';
|
|
3
|
+
import type { VisitorContext } from './Visitor';
|
|
4
|
+
/**
|
|
5
|
+
* SVGRenderer (Runtime ForeignObject Strategy)
|
|
6
|
+
*
|
|
7
|
+
* Instead of attempting to calculate CSS layouts manually,
|
|
8
|
+
* this runtime renderer wraps the exact HTML output in an SVG <foreignObject>.
|
|
9
|
+
*
|
|
10
|
+
* This gives 100% precision with zero DOM scanning overhead for real-time previews.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SVGRenderer extends Visitor<string> {
|
|
13
|
+
private htmlRenderer;
|
|
14
|
+
visit(node: RedNode, context?: VisitorContext): string;
|
|
15
|
+
render(root: RedNode): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — TiptapExporter
|
|
3
|
+
*
|
|
4
|
+
* Exports the Document Model to Tiptap (ProseMirror) JSON format.
|
|
5
|
+
* This proves how easily Quasar's AST bridges to visual WYSIWYG editors.
|
|
6
|
+
*
|
|
7
|
+
* Tiptap uses a JSON format structured like:
|
|
8
|
+
* {
|
|
9
|
+
* type: "doc",
|
|
10
|
+
* content: [
|
|
11
|
+
* {
|
|
12
|
+
* type: "paragraph",
|
|
13
|
+
* content: [
|
|
14
|
+
* { type: "text", text: "Hello ", marks: [{ type: "bold" }] }
|
|
15
|
+
* ]
|
|
16
|
+
* }
|
|
17
|
+
* ]
|
|
18
|
+
* }
|
|
19
|
+
*/
|
|
20
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
21
|
+
import { Visitor } from './Visitor';
|
|
22
|
+
export interface TiptapNode {
|
|
23
|
+
type: string;
|
|
24
|
+
attrs?: Record<string, any>;
|
|
25
|
+
content?: TiptapNode[];
|
|
26
|
+
marks?: TiptapMark[];
|
|
27
|
+
text?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface TiptapMark {
|
|
30
|
+
type: string;
|
|
31
|
+
attrs?: Record<string, any>;
|
|
32
|
+
}
|
|
33
|
+
export declare class TiptapExporter extends Visitor<string> {
|
|
34
|
+
visit(node: RedNode): string;
|
|
35
|
+
toTiptap(node: RedNode): TiptapNode;
|
|
36
|
+
private processChildren;
|
|
37
|
+
private processNode;
|
|
38
|
+
private kindToMark;
|
|
39
|
+
private kindToBlockType;
|
|
40
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — Visitor
|
|
3
|
+
*
|
|
4
|
+
* Base visitor pattern for traversing the Red Tree.
|
|
5
|
+
* All exporters and renderers extend this.
|
|
6
|
+
*
|
|
7
|
+
* Instead of switch statements on tag names, use the visitor pattern.
|
|
8
|
+
* New languages/exporters don't modify existing code.
|
|
9
|
+
*
|
|
10
|
+
* Inspired by SwiftSyntax's Visitor pattern.
|
|
11
|
+
*/
|
|
12
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
13
|
+
export interface VisitorContext {
|
|
14
|
+
source: string;
|
|
15
|
+
options: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export declare abstract class Visitor<T = string> {
|
|
18
|
+
protected context: VisitorContext;
|
|
19
|
+
/**
|
|
20
|
+
* Visit a node and produce an output value.
|
|
21
|
+
*/
|
|
22
|
+
abstract visit(node: RedNode, context?: VisitorContext): T;
|
|
23
|
+
/**
|
|
24
|
+
* Visit all children and combine results.
|
|
25
|
+
*/
|
|
26
|
+
visitChildren(node: RedNode, separator?: string): T[];
|
|
27
|
+
/**
|
|
28
|
+
* Get the text content of a node.
|
|
29
|
+
*/
|
|
30
|
+
getText(node: RedNode): string;
|
|
31
|
+
/**
|
|
32
|
+
* Set the visitor context.
|
|
33
|
+
*/
|
|
34
|
+
setContext(context: VisitorContext): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Visitor } from './Visitor';
|
|
2
|
+
export type { VisitorContext } from './Visitor';
|
|
3
|
+
export { BBCodeExporter } from './BBCodeExporter';
|
|
4
|
+
export { HTMLRenderer } from './HTMLRenderer';
|
|
5
|
+
export { morphHTML, morphElement } from './DOMMorpher';
|
|
6
|
+
export { MarkdownExporter } from './MarkdownExporter';
|
|
7
|
+
export { JSONExporter } from './JSONExporter';
|
|
8
|
+
export { SVGRenderer } from './SVGRenderer';
|
|
9
|
+
export type { JSONDocument, JSONNode } from './JSONExporter';
|