@knpkv/confluence-to-markdown 0.2.0 → 0.4.2
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/CHANGELOG.md +73 -0
- package/LICENSE +21 -0
- package/README.md +282 -14
- package/dist/ConfluenceAuth.d.ts +76 -0
- package/dist/ConfluenceAuth.d.ts.map +1 -0
- package/dist/ConfluenceAuth.js +366 -0
- package/dist/ConfluenceAuth.js.map +1 -0
- package/dist/ConfluenceClient.d.ts +26 -12
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +139 -97
- package/dist/ConfluenceClient.js.map +1 -1
- package/dist/ConfluenceConfig.d.ts +4 -24
- package/dist/ConfluenceConfig.d.ts.map +1 -1
- package/dist/ConfluenceConfig.js +45 -7
- package/dist/ConfluenceConfig.js.map +1 -1
- package/dist/ConfluenceError.d.ts +99 -16
- package/dist/ConfluenceError.d.ts.map +1 -1
- package/dist/ConfluenceError.js +88 -5
- package/dist/ConfluenceError.js.map +1 -1
- package/dist/GitError.d.ts +103 -0
- package/dist/GitError.d.ts.map +1 -0
- package/dist/GitError.js +85 -0
- package/dist/GitError.js.map +1 -0
- package/dist/GitService.d.ts +175 -0
- package/dist/GitService.d.ts.map +1 -0
- package/dist/GitService.js +431 -0
- package/dist/GitService.js.map +1 -0
- package/dist/LocalFileSystem.d.ts +29 -4
- package/dist/LocalFileSystem.d.ts.map +1 -1
- package/dist/LocalFileSystem.js +80 -6
- package/dist/LocalFileSystem.js.map +1 -1
- package/dist/MarkdownConverter.d.ts +49 -2
- package/dist/MarkdownConverter.d.ts.map +1 -1
- package/dist/MarkdownConverter.js +73 -111
- package/dist/MarkdownConverter.js.map +1 -1
- package/dist/SchemaConverterError.d.ts +108 -0
- package/dist/SchemaConverterError.d.ts.map +1 -0
- package/dist/SchemaConverterError.js +84 -0
- package/dist/SchemaConverterError.js.map +1 -0
- package/dist/Schemas.d.ts +225 -1
- package/dist/Schemas.d.ts.map +1 -1
- package/dist/Schemas.js +155 -6
- package/dist/Schemas.js.map +1 -1
- package/dist/SyncEngine.d.ts +30 -20
- package/dist/SyncEngine.d.ts.map +1 -1
- package/dist/SyncEngine.js +566 -117
- package/dist/SyncEngine.js.map +1 -1
- package/dist/ast/BlockNode.d.ts +468 -0
- package/dist/ast/BlockNode.d.ts.map +1 -0
- package/dist/ast/BlockNode.js +319 -0
- package/dist/ast/BlockNode.js.map +1 -0
- package/dist/ast/Document.d.ts +244 -0
- package/dist/ast/Document.d.ts.map +1 -0
- package/dist/ast/Document.js +69 -0
- package/dist/ast/Document.js.map +1 -0
- package/dist/ast/InlineNode.d.ts +477 -0
- package/dist/ast/InlineNode.d.ts.map +1 -0
- package/dist/ast/InlineNode.js +263 -0
- package/dist/ast/InlineNode.js.map +1 -0
- package/dist/ast/MacroNode.d.ts +267 -0
- package/dist/ast/MacroNode.d.ts.map +1 -0
- package/dist/ast/MacroNode.js +164 -0
- package/dist/ast/MacroNode.js.map +1 -0
- package/dist/ast/index.d.ts +10 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +14 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/bin.js +33 -149
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +15 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +86 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/clone.d.ts +12 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/clone.js +93 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/delete.d.ts +13 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +48 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/errorHandler.d.ts +14 -0
- package/dist/commands/errorHandler.d.ts.map +1 -0
- package/dist/commands/errorHandler.js +33 -0
- package/dist/commands/errorHandler.js.map +1 -0
- package/dist/commands/git.d.ts +22 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +72 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/layers.d.ts +31 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +137 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +80 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pageTree.d.ts +18 -0
- package/dist/commands/pageTree.d.ts.map +1 -0
- package/dist/commands/pageTree.js +20 -0
- package/dist/commands/pageTree.js.map +1 -0
- package/dist/commands/shared.d.ts +15 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +27 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/sync.d.ts +15 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +101 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/NodeLayers.d.ts +7 -0
- package/dist/internal/NodeLayers.d.ts.map +1 -0
- package/dist/internal/NodeLayers.js +19 -0
- package/dist/internal/NodeLayers.js.map +1 -0
- package/dist/internal/frontmatter.d.ts +10 -0
- package/dist/internal/frontmatter.d.ts.map +1 -1
- package/dist/internal/frontmatter.js +16 -0
- package/dist/internal/frontmatter.js.map +1 -1
- package/dist/internal/gitCommands.d.ts +78 -0
- package/dist/internal/gitCommands.d.ts.map +1 -0
- package/dist/internal/gitCommands.js +156 -0
- package/dist/internal/gitCommands.js.map +1 -0
- package/dist/internal/hashUtils.d.ts +42 -1
- package/dist/internal/hashUtils.d.ts.map +1 -1
- package/dist/internal/hashUtils.js +38 -2
- package/dist/internal/hashUtils.js.map +1 -1
- package/dist/internal/oauthServer.d.ts +55 -0
- package/dist/internal/oauthServer.d.ts.map +1 -0
- package/dist/internal/oauthServer.js +110 -0
- package/dist/internal/oauthServer.js.map +1 -0
- package/dist/internal/pathUtils.d.ts +21 -4
- package/dist/internal/pathUtils.d.ts.map +1 -1
- package/dist/internal/pathUtils.js +24 -13
- package/dist/internal/pathUtils.js.map +1 -1
- package/dist/internal/tokenStorage.d.ts +75 -0
- package/dist/internal/tokenStorage.d.ts.map +1 -0
- package/dist/internal/tokenStorage.js +149 -0
- package/dist/internal/tokenStorage.js.map +1 -0
- package/dist/internal/userCache.d.ts +42 -0
- package/dist/internal/userCache.d.ts.map +1 -0
- package/dist/internal/userCache.js +51 -0
- package/dist/internal/userCache.js.map +1 -0
- package/dist/parsers/ConfluenceParser.d.ts +26 -0
- package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
- package/dist/parsers/ConfluenceParser.js +792 -0
- package/dist/parsers/ConfluenceParser.js.map +1 -0
- package/dist/parsers/MarkdownParser.d.ts +26 -0
- package/dist/parsers/MarkdownParser.d.ts.map +1 -0
- package/dist/parsers/MarkdownParser.js +873 -0
- package/dist/parsers/MarkdownParser.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +8 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/schemas/ConfluenceSchema.d.ts +21 -0
- package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
- package/dist/schemas/ConfluenceSchema.js +38 -0
- package/dist/schemas/ConfluenceSchema.js.map +1 -0
- package/dist/schemas/ConversionSchema.d.ts +35 -0
- package/dist/schemas/ConversionSchema.d.ts.map +1 -0
- package/dist/schemas/ConversionSchema.js +208 -0
- package/dist/schemas/ConversionSchema.js.map +1 -0
- package/dist/schemas/MarkdownSchema.d.ts +21 -0
- package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
- package/dist/schemas/MarkdownSchema.js +38 -0
- package/dist/schemas/MarkdownSchema.js.map +1 -0
- package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
- package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
- package/dist/schemas/hast/HastFromHtml.js +107 -0
- package/dist/schemas/hast/HastFromHtml.js.map +1 -0
- package/dist/schemas/hast/HastSchema.d.ts +195 -0
- package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
- package/dist/schemas/hast/HastSchema.js +183 -0
- package/dist/schemas/hast/HastSchema.js.map +1 -0
- package/dist/schemas/hast/index.d.ts +9 -0
- package/dist/schemas/hast/index.d.ts.map +1 -0
- package/dist/schemas/hast/index.js +3 -0
- package/dist/schemas/hast/index.js.map +1 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +16 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
- package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
- package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastSchema.js +266 -0
- package/dist/schemas/mdast/MdastSchema.js.map +1 -0
- package/dist/schemas/mdast/index.d.ts +10 -0
- package/dist/schemas/mdast/index.d.ts.map +1 -0
- package/dist/schemas/mdast/index.js +4 -0
- package/dist/schemas/mdast/index.js.map +1 -0
- package/dist/schemas/mdast/mdastToString.d.ts +13 -0
- package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
- package/dist/schemas/mdast/mdastToString.js +85 -0
- package/dist/schemas/mdast/mdastToString.js.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.js +634 -0
- package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
- package/dist/schemas/nodes/block/index.d.ts +7 -0
- package/dist/schemas/nodes/block/index.d.ts.map +1 -0
- package/dist/schemas/nodes/block/index.js +7 -0
- package/dist/schemas/nodes/block/index.js.map +1 -0
- package/dist/schemas/nodes/index.d.ts +9 -0
- package/dist/schemas/nodes/index.d.ts.map +1 -0
- package/dist/schemas/nodes/index.js +12 -0
- package/dist/schemas/nodes/index.js.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
- package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
- package/dist/schemas/nodes/inline/index.d.ts +7 -0
- package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/index.js +7 -0
- package/dist/schemas/nodes/inline/index.js.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
- package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
- package/dist/schemas/nodes/macro/index.d.ts +7 -0
- package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/index.js +7 -0
- package/dist/schemas/nodes/macro/index.js.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
- package/dist/schemas/preprocessing/index.d.ts +8 -0
- package/dist/schemas/preprocessing/index.d.ts.map +1 -0
- package/dist/schemas/preprocessing/index.js +2 -0
- package/dist/schemas/preprocessing/index.js.map +1 -0
- package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
- package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
- package/dist/serializers/ConfluenceSerializer.js +551 -0
- package/dist/serializers/ConfluenceSerializer.js.map +1 -0
- package/dist/serializers/MarkdownSerializer.d.ts +34 -0
- package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
- package/dist/serializers/MarkdownSerializer.js +355 -0
- package/dist/serializers/MarkdownSerializer.js.map +1 -0
- package/dist/serializers/index.d.ts +8 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/serializers/index.js +8 -0
- package/dist/serializers/index.js.map +1 -0
- package/package.json +32 -21
- package/src/ConfluenceAuth.ts +581 -0
- package/src/ConfluenceClient.ts +230 -165
- package/src/ConfluenceConfig.ts +63 -7
- package/src/ConfluenceError.ts +110 -14
- package/src/GitError.ts +92 -0
- package/src/GitService.ts +859 -0
- package/src/LocalFileSystem.ts +179 -9
- package/src/MarkdownConverter.ts +126 -122
- package/src/SchemaConverterError.ts +108 -0
- package/src/Schemas.ts +223 -6
- package/src/SyncEngine.ts +745 -162
- package/src/ast/BlockNode.ts +425 -0
- package/src/ast/Document.ts +90 -0
- package/src/ast/InlineNode.ts +323 -0
- package/src/ast/MacroNode.ts +245 -0
- package/src/ast/index.ts +83 -0
- package/src/bin.ts +50 -249
- package/src/commands/auth.ts +117 -0
- package/src/commands/clone.ts +145 -0
- package/src/commands/delete.ts +57 -0
- package/src/commands/errorHandler.ts +32 -0
- package/src/commands/git.ts +114 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/layers.ts +211 -0
- package/src/commands/new.ts +99 -0
- package/src/commands/pageTree.ts +40 -0
- package/src/commands/shared.ts +35 -0
- package/src/commands/sync.ts +129 -0
- package/src/index.ts +21 -1
- package/src/internal/NodeLayers.ts +21 -0
- package/src/internal/frontmatter.ts +21 -0
- package/src/internal/gitCommands.ts +229 -0
- package/src/internal/hashUtils.ts +65 -3
- package/src/internal/oauthServer.ts +199 -0
- package/src/internal/pathUtils.ts +34 -17
- package/src/internal/tokenStorage.ts +240 -0
- package/src/internal/userCache.ts +90 -0
- package/src/parsers/ConfluenceParser.ts +950 -0
- package/src/parsers/MarkdownParser.ts +1198 -0
- package/src/parsers/index.ts +8 -0
- package/src/schemas/ConfluenceSchema.ts +56 -0
- package/src/schemas/ConversionSchema.ts +318 -0
- package/src/schemas/MarkdownSchema.ts +56 -0
- package/src/schemas/hast/HastFromHtml.ts +153 -0
- package/src/schemas/hast/HastSchema.ts +274 -0
- package/src/schemas/hast/index.ts +35 -0
- package/src/schemas/index.ts +20 -0
- package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
- package/src/schemas/mdast/MdastSchema.ts +566 -0
- package/src/schemas/mdast/index.ts +59 -0
- package/src/schemas/mdast/mdastToString.ts +102 -0
- package/src/schemas/nodes/block/BlockSchema.ts +773 -0
- package/src/schemas/nodes/block/index.ts +13 -0
- package/src/schemas/nodes/index.ts +20 -0
- package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
- package/src/schemas/nodes/inline/index.ts +14 -0
- package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
- package/src/schemas/nodes/macro/index.ts +6 -0
- package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
- package/src/schemas/preprocessing/index.ts +8 -0
- package/src/serializers/ConfluenceSerializer.ts +717 -0
- package/src/serializers/MarkdownSerializer.ts +493 -0
- package/src/serializers/index.ts +8 -0
- package/test/GitService.test.ts +209 -0
- package/test/MarkdownConverter.test.ts +37 -3
- package/test/Schemas.test.ts +97 -2
- package/test/ast/BlockNode.test.ts +265 -0
- package/test/ast/Document.test.ts +126 -0
- package/test/ast/InlineNode.test.ts +161 -0
- package/test/fixtures/integration-test.html.fixture +103 -0
- package/test/fixtures/integration-test.md.expected +257 -0
- package/test/integration.test.ts +269 -0
- package/test/oauthServer.test.ts +50 -0
- package/test/parsers/ConfluenceParser.test.ts +283 -0
- package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
- package/test/schemas/ConversionSchema.test.ts +159 -0
- package/test/schemas/HastSchema.test.ts +138 -0
- package/test/schemas/MdastSchema.test.ts +145 -0
- package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
- package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
- package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
- package/test/tokenStorage.test.ts +99 -0
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for Markdown to AST.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import remarkGfm from "remark-gfm";
|
|
8
|
+
import remarkParse from "remark-parse";
|
|
9
|
+
import { unified } from "unified";
|
|
10
|
+
import { CodeBlock, Heading, Image, Paragraph, Table, TableCell, TableRow, ThematicBreak, UnsupportedBlock } from "../ast/BlockNode.js";
|
|
11
|
+
import { makeDocument } from "../ast/Document.js";
|
|
12
|
+
import { ColoredText, DateTime, Emoticon, Emphasis, Highlight, InlineCode, LineBreak, Link, Strikethrough, Strong, Subscript, Superscript, Text, Underline, UnsupportedInline, UserMention } from "../ast/InlineNode.js";
|
|
13
|
+
import { PanelTypes } from "../ast/MacroNode.js";
|
|
14
|
+
import { ParseError } from "../SchemaConverterError.js";
|
|
15
|
+
/**
|
|
16
|
+
* Parse Markdown to Document AST.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { parseMarkdown } from "@knpkv/confluence-to-markdown/parsers/MarkdownParser"
|
|
21
|
+
* import { Effect } from "effect"
|
|
22
|
+
*
|
|
23
|
+
* Effect.gen(function* () {
|
|
24
|
+
* const doc = yield* parseMarkdown("# Title\n\nContent")
|
|
25
|
+
* console.log(doc.children.length) // 2
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @category Parsers
|
|
30
|
+
*/
|
|
31
|
+
export const parseMarkdown = (markdown) => Effect.gen(function* () {
|
|
32
|
+
// Check for embedded rawConfluence comment (for 1-to-1 roundtrip)
|
|
33
|
+
const rawMatch = markdown.match(/<!--cf:raw:([A-Za-z0-9+/=]+)-->/);
|
|
34
|
+
let rawConfluence;
|
|
35
|
+
let cleanMarkdown = markdown;
|
|
36
|
+
if (rawMatch) {
|
|
37
|
+
// Extract and decode the raw Confluence HTML
|
|
38
|
+
const encoded = rawMatch[1] ?? "";
|
|
39
|
+
rawConfluence = Buffer.from(encoded, "base64").toString("utf-8");
|
|
40
|
+
// Remove the raw comment from markdown for parsing
|
|
41
|
+
cleanMarkdown = markdown.replace(/\n*<!--cf:raw:[A-Za-z0-9+/=]+-->\s*$/, "");
|
|
42
|
+
}
|
|
43
|
+
// Preprocess container syntax (:::type ... :::) to HTML comments
|
|
44
|
+
cleanMarkdown = preprocessContainers(cleanMarkdown);
|
|
45
|
+
// Parse Markdown to mdast
|
|
46
|
+
const mdast = yield* Effect.try({
|
|
47
|
+
try: () => unified()
|
|
48
|
+
.use(remarkParse)
|
|
49
|
+
.use(remarkGfm)
|
|
50
|
+
.parse(cleanMarkdown),
|
|
51
|
+
catch: (error) => new ParseError({
|
|
52
|
+
source: "markdown",
|
|
53
|
+
message: `Markdown parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
54
|
+
rawContent: markdown.slice(0, 200)
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
// Convert mdast to AST
|
|
58
|
+
const children = yield* mdastToDocumentNodes(mdast);
|
|
59
|
+
return makeDocument(children, rawConfluence);
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* Preprocess :::type container syntax to HTML comments.
|
|
63
|
+
* Converts :::info\ncontent\n::: to <!--cf:panel:info::encodedContent-->
|
|
64
|
+
* Optionally with title: :::info Title\ncontent\n::: to <!--cf:panel:info:Title:encodedContent-->
|
|
65
|
+
*/
|
|
66
|
+
const preprocessContainers = (markdown) => {
|
|
67
|
+
// Match :::type with optional same-line title, then content, then :::
|
|
68
|
+
// Title must be on same line as opening :::type
|
|
69
|
+
const containerRegex = /^:::(\w+)(?: ([^\n]+))?\n([\s\S]*?)\n:::$/gm;
|
|
70
|
+
return markdown.replace(containerRegex, (_, type, title, content) => {
|
|
71
|
+
const panelType = type.toLowerCase();
|
|
72
|
+
const encodedContent = encodeURIComponent(content.trim());
|
|
73
|
+
const encodedTitle = title ? encodeURIComponent(title.trim()) : "";
|
|
74
|
+
return `<!--cf:panel:${panelType}:${encodedTitle}:${encodedContent}-->`;
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Convert mdast Root to document nodes.
|
|
79
|
+
*/
|
|
80
|
+
const mdastToDocumentNodes = (root) => Effect.gen(function* () {
|
|
81
|
+
const nodes = [];
|
|
82
|
+
for (const child of root.children) {
|
|
83
|
+
const node = yield* mdastNodeToBlock(child);
|
|
84
|
+
if (node !== null)
|
|
85
|
+
nodes.push(node);
|
|
86
|
+
}
|
|
87
|
+
return nodes;
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* Convert mdast node to BlockNode or MacroNode.
|
|
91
|
+
*/
|
|
92
|
+
const mdastNodeToBlock = (node) => Effect.gen(function* () {
|
|
93
|
+
switch (node.type) {
|
|
94
|
+
case "heading": {
|
|
95
|
+
const heading = node;
|
|
96
|
+
const children = yield* mdastChildrenToInline(heading.children);
|
|
97
|
+
return new Heading({ level: heading.depth, children });
|
|
98
|
+
}
|
|
99
|
+
case "paragraph": {
|
|
100
|
+
const para = node;
|
|
101
|
+
// Check if paragraph is just [[toc]] - convert to TocMacro
|
|
102
|
+
if (para.children.length === 1 && para.children[0]?.type === "text") {
|
|
103
|
+
const textContent = para.children[0].value.trim();
|
|
104
|
+
if (textContent === "[[toc]]") {
|
|
105
|
+
return { _tag: "TocMacro", version: 1 };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const children = yield* mdastChildrenToInline(para.children);
|
|
109
|
+
return new Paragraph({ children });
|
|
110
|
+
}
|
|
111
|
+
case "code": {
|
|
112
|
+
const code = node;
|
|
113
|
+
return new CodeBlock({
|
|
114
|
+
code: code.value,
|
|
115
|
+
language: code.lang || undefined
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
case "thematicBreak": {
|
|
119
|
+
return new ThematicBreak({});
|
|
120
|
+
}
|
|
121
|
+
case "image": {
|
|
122
|
+
const img = node;
|
|
123
|
+
return new Image({
|
|
124
|
+
src: img.url,
|
|
125
|
+
alt: img.alt || undefined,
|
|
126
|
+
title: img.title || undefined
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
case "blockquote": {
|
|
130
|
+
const bq = node;
|
|
131
|
+
const children = yield* mdastChildrenToSimpleBlocks(bq.children);
|
|
132
|
+
return { _tag: "BlockQuote", version: 1, children };
|
|
133
|
+
}
|
|
134
|
+
case "list": {
|
|
135
|
+
const list = node;
|
|
136
|
+
return yield* parseList(list);
|
|
137
|
+
}
|
|
138
|
+
case "table": {
|
|
139
|
+
const table = node;
|
|
140
|
+
return yield* parseTable(table);
|
|
141
|
+
}
|
|
142
|
+
case "html": {
|
|
143
|
+
const html = node;
|
|
144
|
+
// Check for comment-encoded task list
|
|
145
|
+
const taskListParsed = yield* parseTaskListComment(html.value);
|
|
146
|
+
if (taskListParsed)
|
|
147
|
+
return taskListParsed;
|
|
148
|
+
// Check for comment-encoded image
|
|
149
|
+
const imageParsed = yield* parseImageComment(html.value);
|
|
150
|
+
if (imageParsed)
|
|
151
|
+
return imageParsed;
|
|
152
|
+
// Check for comment-encoded expand macro
|
|
153
|
+
const expandParsed = yield* parseExpandMacroComment(html.value);
|
|
154
|
+
if (expandParsed)
|
|
155
|
+
return expandParsed;
|
|
156
|
+
// Check for comment-encoded TOC macro
|
|
157
|
+
const tocParsed = yield* parseTocComment(html.value);
|
|
158
|
+
if (tocParsed)
|
|
159
|
+
return tocParsed;
|
|
160
|
+
// Check for comment-encoded status macro (wrap in paragraph)
|
|
161
|
+
const statusParsed = yield* parseStatusComment(html.value);
|
|
162
|
+
if (statusParsed)
|
|
163
|
+
return statusParsed;
|
|
164
|
+
// Check for comment-encoded smart link (wrap in paragraph)
|
|
165
|
+
const smartLinkParsed = yield* parseSmartLinkComment(html.value);
|
|
166
|
+
if (smartLinkParsed)
|
|
167
|
+
return smartLinkParsed;
|
|
168
|
+
// Check for comment-encoded decision list
|
|
169
|
+
const decisionParsed = yield* parseDecisionComment(html.value);
|
|
170
|
+
if (decisionParsed)
|
|
171
|
+
return decisionParsed;
|
|
172
|
+
// Check for comment-encoded layout
|
|
173
|
+
const layoutParsed = yield* parseLayoutComment(html.value);
|
|
174
|
+
if (layoutParsed)
|
|
175
|
+
return layoutParsed;
|
|
176
|
+
// Check for comment-encoded panel (:::type container)
|
|
177
|
+
const panelParsed = yield* parsePanelComment(html.value);
|
|
178
|
+
if (panelParsed)
|
|
179
|
+
return panelParsed;
|
|
180
|
+
// Check for comment-encoded inline elements that should become paragraphs
|
|
181
|
+
const inlineParsed = yield* parseBlockLevelInlineComment(html.value);
|
|
182
|
+
if (inlineParsed)
|
|
183
|
+
return inlineParsed;
|
|
184
|
+
return new UnsupportedBlock({
|
|
185
|
+
rawMarkdown: html.value,
|
|
186
|
+
source: "markdown"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
default:
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
/**
|
|
194
|
+
* Convert mdast children to inline nodes.
|
|
195
|
+
* Handles paired HTML tags like <span style="color:...">...</span> by looking ahead.
|
|
196
|
+
*/
|
|
197
|
+
const mdastChildrenToInline = (children) => Effect.gen(function* () {
|
|
198
|
+
const nodes = [];
|
|
199
|
+
let i = 0;
|
|
200
|
+
while (i < children.length) {
|
|
201
|
+
const child = children[i];
|
|
202
|
+
if (!child) {
|
|
203
|
+
i++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
// Handle text nodes specially - they can contain embedded HTML comments
|
|
207
|
+
if (child.type === "text") {
|
|
208
|
+
const text = child;
|
|
209
|
+
const parsed = yield* parseTextWithEmbeddedHtml(text.value);
|
|
210
|
+
for (const p of parsed)
|
|
211
|
+
nodes.push(p);
|
|
212
|
+
i++;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// Check for paired HTML tags (span with color/background)
|
|
216
|
+
if (child.type === "html") {
|
|
217
|
+
const html = child;
|
|
218
|
+
const pairedResult = yield* tryParsePairedHtmlTag(html.value, children, i);
|
|
219
|
+
if (pairedResult) {
|
|
220
|
+
nodes.push(pairedResult.node);
|
|
221
|
+
i = pairedResult.nextIndex;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Regular node processing
|
|
226
|
+
const node = yield* mdastNodeToInline(child);
|
|
227
|
+
if (node !== null)
|
|
228
|
+
nodes.push(node);
|
|
229
|
+
i++;
|
|
230
|
+
}
|
|
231
|
+
return nodes;
|
|
232
|
+
});
|
|
233
|
+
/**
|
|
234
|
+
* Try to parse paired HTML tags like <span style="color:...">content</span>.
|
|
235
|
+
* Returns the parsed node and next index if successful, null otherwise.
|
|
236
|
+
*/
|
|
237
|
+
const tryParsePairedHtmlTag = (openingTag, children, startIndex) => Effect.gen(function* () {
|
|
238
|
+
// Check for color span: <span style="color: ...;">
|
|
239
|
+
const colorMatch = openingTag.match(/^<span\s+style="color:\s*([^;]+);">$/);
|
|
240
|
+
if (colorMatch) {
|
|
241
|
+
const result = yield* collectUntilClosingTag(children, startIndex + 1, "</span>");
|
|
242
|
+
if (result) {
|
|
243
|
+
const innerNodes = yield* mdastChildrenToInline(result.innerChildren);
|
|
244
|
+
const baseNodes = inlineNodesToBase(innerNodes);
|
|
245
|
+
return {
|
|
246
|
+
node: new ColoredText({ color: colorMatch[1] ?? "", children: baseNodes }),
|
|
247
|
+
nextIndex: result.nextIndex
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Check for highlight span: <span style="background-color: ...;">
|
|
252
|
+
const bgMatch = openingTag.match(/^<span\s+style="background-color:\s*([^;]+);">$/);
|
|
253
|
+
if (bgMatch) {
|
|
254
|
+
const result = yield* collectUntilClosingTag(children, startIndex + 1, "</span>");
|
|
255
|
+
if (result) {
|
|
256
|
+
const innerNodes = yield* mdastChildrenToInline(result.innerChildren);
|
|
257
|
+
const baseNodes = inlineNodesToBase(innerNodes);
|
|
258
|
+
return {
|
|
259
|
+
node: new Highlight({ backgroundColor: bgMatch[1] ?? "", children: baseNodes }),
|
|
260
|
+
nextIndex: result.nextIndex
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Check for underline: <u>
|
|
265
|
+
if (openingTag === "<u>") {
|
|
266
|
+
const result = yield* collectUntilClosingTag(children, startIndex + 1, "</u>");
|
|
267
|
+
if (result) {
|
|
268
|
+
const innerNodes = yield* mdastChildrenToInline(result.innerChildren);
|
|
269
|
+
const baseNodes = inlineNodesToBase(innerNodes);
|
|
270
|
+
return {
|
|
271
|
+
node: new Underline({ children: baseNodes }),
|
|
272
|
+
nextIndex: result.nextIndex
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Check for subscript: <sub>
|
|
277
|
+
if (openingTag === "<sub>") {
|
|
278
|
+
const result = yield* collectUntilClosingTag(children, startIndex + 1, "</sub>");
|
|
279
|
+
if (result) {
|
|
280
|
+
const innerNodes = yield* mdastChildrenToInline(result.innerChildren);
|
|
281
|
+
const baseNodes = inlineNodesToBase(innerNodes);
|
|
282
|
+
return {
|
|
283
|
+
node: new Subscript({ children: baseNodes }),
|
|
284
|
+
nextIndex: result.nextIndex
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Check for superscript: <sup>
|
|
289
|
+
if (openingTag === "<sup>") {
|
|
290
|
+
const result = yield* collectUntilClosingTag(children, startIndex + 1, "</sup>");
|
|
291
|
+
if (result) {
|
|
292
|
+
const innerNodes = yield* mdastChildrenToInline(result.innerChildren);
|
|
293
|
+
const baseNodes = inlineNodesToBase(innerNodes);
|
|
294
|
+
return {
|
|
295
|
+
node: new Superscript({ children: baseNodes }),
|
|
296
|
+
nextIndex: result.nextIndex
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
});
|
|
302
|
+
/**
|
|
303
|
+
* Collect mdast nodes until a closing HTML tag is found.
|
|
304
|
+
* Returns the inner children and the index after the closing tag.
|
|
305
|
+
*/
|
|
306
|
+
const collectUntilClosingTag = (children, startIndex, closingTag) => Effect.gen(function* () {
|
|
307
|
+
const innerChildren = [];
|
|
308
|
+
for (let i = startIndex; i < children.length; i++) {
|
|
309
|
+
const child = children[i];
|
|
310
|
+
if (!child)
|
|
311
|
+
continue;
|
|
312
|
+
if (child.type === "html") {
|
|
313
|
+
const html = child;
|
|
314
|
+
if (html.value === closingTag) {
|
|
315
|
+
return { innerChildren, nextIndex: i + 1 };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
innerChildren.push(child);
|
|
319
|
+
}
|
|
320
|
+
// No closing tag found
|
|
321
|
+
return null;
|
|
322
|
+
});
|
|
323
|
+
/**
|
|
324
|
+
* Convert InlineNode array to base inline nodes for nested formatting.
|
|
325
|
+
*/
|
|
326
|
+
const inlineNodesToBase = (nodes) => {
|
|
327
|
+
const result = [];
|
|
328
|
+
for (const node of nodes) {
|
|
329
|
+
switch (node._tag) {
|
|
330
|
+
case "Text":
|
|
331
|
+
case "InlineCode":
|
|
332
|
+
case "LineBreak":
|
|
333
|
+
case "UnsupportedInline":
|
|
334
|
+
result.push(node);
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
// For complex nodes, serialize to raw string
|
|
338
|
+
result.push(new UnsupportedInline({ raw: JSON.stringify(node), source: "markdown" }));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* Convert mdast node to InlineNode.
|
|
345
|
+
*/
|
|
346
|
+
const mdastNodeToInline = (node) => Effect.gen(function* () {
|
|
347
|
+
switch (node.type) {
|
|
348
|
+
case "text": {
|
|
349
|
+
const text = node;
|
|
350
|
+
return new Text({ value: text.value });
|
|
351
|
+
}
|
|
352
|
+
case "strong": {
|
|
353
|
+
const strong = node;
|
|
354
|
+
const children = yield* mdastChildrenToBaseInline(strong.children);
|
|
355
|
+
return new Strong({ children });
|
|
356
|
+
}
|
|
357
|
+
case "emphasis": {
|
|
358
|
+
const em = node;
|
|
359
|
+
const children = yield* mdastChildrenToBaseInline(em.children);
|
|
360
|
+
return new Emphasis({ children });
|
|
361
|
+
}
|
|
362
|
+
case "delete": {
|
|
363
|
+
const del = node;
|
|
364
|
+
const children = yield* mdastChildrenToBaseInline(del.children);
|
|
365
|
+
return new Strikethrough({ children });
|
|
366
|
+
}
|
|
367
|
+
case "inlineCode": {
|
|
368
|
+
const code = node;
|
|
369
|
+
return new InlineCode({ value: code.value });
|
|
370
|
+
}
|
|
371
|
+
case "link": {
|
|
372
|
+
const link = node;
|
|
373
|
+
const children = yield* mdastChildrenToBaseInline(link.children);
|
|
374
|
+
return new Link({
|
|
375
|
+
href: link.url,
|
|
376
|
+
title: link.title || undefined,
|
|
377
|
+
children
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
case "break": {
|
|
381
|
+
return new LineBreak({});
|
|
382
|
+
}
|
|
383
|
+
case "image": {
|
|
384
|
+
const img = node;
|
|
385
|
+
return new UnsupportedInline({
|
|
386
|
+
raw: ``,
|
|
387
|
+
source: "markdown"
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
case "html": {
|
|
391
|
+
const html = node;
|
|
392
|
+
const parsed = yield* parseInlineHtml(html.value);
|
|
393
|
+
if (parsed)
|
|
394
|
+
return parsed;
|
|
395
|
+
return new UnsupportedInline({
|
|
396
|
+
raw: html.value,
|
|
397
|
+
source: "markdown"
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
default:
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
/**
|
|
405
|
+
* Parse inline HTML that was preserved for roundtrip.
|
|
406
|
+
*/
|
|
407
|
+
const parseInlineHtml = (html) => Effect.gen(function* () {
|
|
408
|
+
// Comment-encoded Emoticon: <!--cf:emoticon:shortname|emojiId|fallback-->
|
|
409
|
+
// Use non-greedy match since values can contain special chars
|
|
410
|
+
const emoticonCommentMatch = html.match(/<!--cf:emoticon:([^|]*)\|([^|]*)\|(.+?)-->/);
|
|
411
|
+
if (emoticonCommentMatch) {
|
|
412
|
+
return new Emoticon({
|
|
413
|
+
shortname: decodeURIComponent(emoticonCommentMatch[1] ?? ""),
|
|
414
|
+
emojiId: decodeURIComponent(emoticonCommentMatch[2] ?? ""),
|
|
415
|
+
fallback: decodeURIComponent(emoticonCommentMatch[3] ?? "")
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
// Comment-encoded User mention: <!--cf:user:accountId-->
|
|
419
|
+
// Account IDs can contain dashes, colons, etc. Match everything until -->
|
|
420
|
+
const userCommentMatch = html.match(/<!--cf:user:(.+?)-->/);
|
|
421
|
+
if (userCommentMatch) {
|
|
422
|
+
return new UserMention({ accountId: userCommentMatch[1] ?? "" });
|
|
423
|
+
}
|
|
424
|
+
// Comment-encoded DateTime: <!--cf:date:datetime-->
|
|
425
|
+
// Use non-greedy match since dates can contain dashes, allow empty datetime
|
|
426
|
+
const dateCommentMatch = html.match(/<!--cf:date:(.*?)-->/);
|
|
427
|
+
if (dateCommentMatch) {
|
|
428
|
+
return new DateTime({ datetime: dateCommentMatch[1] ?? "" });
|
|
429
|
+
}
|
|
430
|
+
// Colored text
|
|
431
|
+
const colorMatch = html.match(/<span style="color:\s*([^;]+);">([^<]*)<\/span>/);
|
|
432
|
+
if (colorMatch) {
|
|
433
|
+
return new ColoredText({
|
|
434
|
+
color: colorMatch[1] ?? "",
|
|
435
|
+
children: [new Text({ value: colorMatch[2] ?? "" })]
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
// Highlight
|
|
439
|
+
const bgMatch = html.match(/<span style="background-color:\s*([^;]+);">([^<]*)<\/span>/);
|
|
440
|
+
if (bgMatch) {
|
|
441
|
+
return new Highlight({
|
|
442
|
+
backgroundColor: bgMatch[1] ?? "",
|
|
443
|
+
children: [new Text({ value: bgMatch[2] ?? "" })]
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
});
|
|
448
|
+
/**
|
|
449
|
+
* Parse comment-encoded task list.
|
|
450
|
+
* Format: <!--cf:tasklist:id|uuid|status|body;id|uuid|status|body-->
|
|
451
|
+
*/
|
|
452
|
+
const parseTaskListComment = (html) => Effect.gen(function* () {
|
|
453
|
+
// Check if this is a task list
|
|
454
|
+
const match = html.match(/<!--cf:tasklist:(.*)-->/);
|
|
455
|
+
if (!match) {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
const itemsStr = match[1] ?? "";
|
|
459
|
+
const items = [];
|
|
460
|
+
for (const itemStr of itemsStr.split(";")) {
|
|
461
|
+
const parts = itemStr.split("|");
|
|
462
|
+
if (parts.length >= 4) {
|
|
463
|
+
items.push({
|
|
464
|
+
_tag: "TaskItem",
|
|
465
|
+
id: parts[0] ?? "",
|
|
466
|
+
uuid: parts[1] ?? "",
|
|
467
|
+
status: (parts[2] === "complete" ? "complete" : "incomplete"),
|
|
468
|
+
body: [new Text({ value: decodeURIComponent(parts[3] ?? "") })]
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (items.length === 0) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
_tag: "TaskList",
|
|
477
|
+
version: 1,
|
|
478
|
+
children: items
|
|
479
|
+
};
|
|
480
|
+
});
|
|
481
|
+
/**
|
|
482
|
+
* Parse block-level HTML that contains comment-encoded inline elements.
|
|
483
|
+
* Wraps them in a paragraph if found.
|
|
484
|
+
*/
|
|
485
|
+
const parseBlockLevelInlineComment = (html) => Effect.gen(function* () {
|
|
486
|
+
// Check for patterns that should be inline within a paragraph
|
|
487
|
+
const inlinePattern = /<!--cf:(emoticon|user|date):/;
|
|
488
|
+
if (!inlinePattern.test(html)) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
// Parse the text which may contain multiple inline elements
|
|
492
|
+
const parsed = yield* parseTextWithEmbeddedHtml(html);
|
|
493
|
+
if (parsed.length === 0) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
// Filter out empty text nodes
|
|
497
|
+
const nonEmpty = parsed.filter((n) => n._tag !== "Text" || n.value.trim() !== "");
|
|
498
|
+
if (nonEmpty.length === 0) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
return new Paragraph({ children: parsed });
|
|
502
|
+
});
|
|
503
|
+
/**
|
|
504
|
+
* Parse comment-encoded image.
|
|
505
|
+
* Format: <!--cf:image:f=filename|v=version|s=src|a=alt|t=title|al=align|w=width-->
|
|
506
|
+
*/
|
|
507
|
+
const parseImageComment = (html) => Effect.gen(function* () {
|
|
508
|
+
const match = html.match(/<!--cf:image:(.*)-->/);
|
|
509
|
+
if (!match) {
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
const partsStr = match[1] ?? "";
|
|
513
|
+
const props = {};
|
|
514
|
+
for (const part of partsStr.split("|")) {
|
|
515
|
+
const [key, ...valueParts] = part.split("=");
|
|
516
|
+
if (key) {
|
|
517
|
+
props[key] = valueParts.join("=");
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
const attachment = props["f"]
|
|
521
|
+
? {
|
|
522
|
+
filename: decodeURIComponent(props["f"]),
|
|
523
|
+
version: props["v"] ? parseInt(props["v"], 10) : undefined
|
|
524
|
+
}
|
|
525
|
+
: undefined;
|
|
526
|
+
return new Image({
|
|
527
|
+
src: props["s"] ? decodeURIComponent(props["s"]) : undefined,
|
|
528
|
+
alt: props["a"] ? decodeURIComponent(props["a"]) : undefined,
|
|
529
|
+
title: props["t"] ? decodeURIComponent(props["t"]) : undefined,
|
|
530
|
+
align: props["al"] ?? undefined,
|
|
531
|
+
width: props["w"] ? parseInt(props["w"], 10) : undefined,
|
|
532
|
+
attachment
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
const parseExpandMacroComment = (html) => Effect.gen(function* () {
|
|
536
|
+
const match = html.match(/<!--cf:expand:([^:]*):(.*)-->/);
|
|
537
|
+
if (!match) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const titleStr = decodeURIComponent(match[1] ?? "");
|
|
541
|
+
const content = decodeURIComponent(match[2] ?? "");
|
|
542
|
+
// Parse content as simple paragraphs
|
|
543
|
+
const children = content
|
|
544
|
+
.split("\n")
|
|
545
|
+
.filter((line) => line.trim())
|
|
546
|
+
.map((line) => new Paragraph({ children: [new Text({ value: line })] }));
|
|
547
|
+
const result = {
|
|
548
|
+
_tag: "ExpandMacro",
|
|
549
|
+
version: 1,
|
|
550
|
+
children
|
|
551
|
+
};
|
|
552
|
+
if (titleStr) {
|
|
553
|
+
result.title = titleStr;
|
|
554
|
+
}
|
|
555
|
+
return result;
|
|
556
|
+
});
|
|
557
|
+
/**
|
|
558
|
+
* Parse comment-encoded panel (from :::type container syntax).
|
|
559
|
+
* Format: <!--cf:panel:type:title:content-->
|
|
560
|
+
*/
|
|
561
|
+
const parsePanelComment = (html) => Effect.gen(function* () {
|
|
562
|
+
const match = html.match(/<!--cf:panel:(\w+):([^:]*):(.*)-->/);
|
|
563
|
+
if (!match) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
const panelType = match[1] ?? "info";
|
|
567
|
+
const titleStr = decodeURIComponent(match[2] ?? "");
|
|
568
|
+
const content = decodeURIComponent(match[3] ?? "");
|
|
569
|
+
// Verify panel type is valid
|
|
570
|
+
if (!PanelTypes.includes(panelType)) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
// Parse content as simple paragraphs
|
|
574
|
+
const children = content
|
|
575
|
+
.split("\n")
|
|
576
|
+
.filter((line) => line.trim())
|
|
577
|
+
.map((line) => new Paragraph({ children: [new Text({ value: line })] }));
|
|
578
|
+
return {
|
|
579
|
+
_tag: "InfoPanel",
|
|
580
|
+
version: 1,
|
|
581
|
+
panelType: panelType,
|
|
582
|
+
...(titleStr ? { title: titleStr } : {}),
|
|
583
|
+
children
|
|
584
|
+
};
|
|
585
|
+
});
|
|
586
|
+
/**
|
|
587
|
+
* Parse comment-encoded TOC macro.
|
|
588
|
+
* Format: <!--cf:toc:minLevel;maxLevel-->
|
|
589
|
+
*/
|
|
590
|
+
const parseTocComment = (html) => Effect.gen(function* () {
|
|
591
|
+
const match = html.match(/<!--cf:toc:([^;]*);([^;]*)-->/);
|
|
592
|
+
if (!match) {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
const minStr = match[1] ?? "";
|
|
596
|
+
const maxStr = match[2] ?? "";
|
|
597
|
+
return {
|
|
598
|
+
_tag: "TocMacro",
|
|
599
|
+
version: 1,
|
|
600
|
+
minLevel: minStr ? parseInt(minStr) : undefined,
|
|
601
|
+
maxLevel: maxStr ? parseInt(maxStr) : undefined
|
|
602
|
+
};
|
|
603
|
+
});
|
|
604
|
+
/**
|
|
605
|
+
* Parse comment-encoded Status macro(s).
|
|
606
|
+
* Format: <!--cf:status:title;color-->
|
|
607
|
+
* Returns a paragraph containing all status macros found.
|
|
608
|
+
*/
|
|
609
|
+
const parseStatusComment = (html) => Effect.gen(function* () {
|
|
610
|
+
// Match all status comments in the string
|
|
611
|
+
const statusPattern = /<!--cf:status:([^;]*);([^;]*)-->/g;
|
|
612
|
+
const matches = [...html.matchAll(statusPattern)];
|
|
613
|
+
if (matches.length === 0) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
// Create StatusMacro nodes wrapped in UnsupportedInline for now
|
|
617
|
+
// (since StatusMacro isn't an InlineNode)
|
|
618
|
+
const children = [];
|
|
619
|
+
let lastIndex = 0;
|
|
620
|
+
for (const match of matches) {
|
|
621
|
+
// Add any text/whitespace between matches (preserve spaces)
|
|
622
|
+
if (match.index !== undefined && match.index > lastIndex) {
|
|
623
|
+
const textBetween = html.slice(lastIndex, match.index);
|
|
624
|
+
if (textBetween) {
|
|
625
|
+
children.push(new Text({ value: textBetween }));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Add status as UnsupportedInline to preserve through roundtrip
|
|
629
|
+
children.push(new UnsupportedInline({
|
|
630
|
+
raw: match[0],
|
|
631
|
+
source: "markdown"
|
|
632
|
+
}));
|
|
633
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
634
|
+
}
|
|
635
|
+
// Add any trailing text
|
|
636
|
+
if (lastIndex < html.length) {
|
|
637
|
+
const trailing = html.slice(lastIndex);
|
|
638
|
+
if (trailing.trim()) {
|
|
639
|
+
children.push(new Text({ value: trailing }));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return new Paragraph({ children });
|
|
643
|
+
});
|
|
644
|
+
/**
|
|
645
|
+
* Parse comment-encoded Smart link.
|
|
646
|
+
* Format: <!--cf:smartlink:href;appearance;datasource-->
|
|
647
|
+
*/
|
|
648
|
+
const parseSmartLinkComment = (html) => Effect.gen(function* () {
|
|
649
|
+
const match = html.match(/<!--cf:smartlink:([^;]*);([^;]*);(.*)-->/);
|
|
650
|
+
if (!match) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
// Preserve as UnsupportedInline for roundtrip
|
|
654
|
+
return new Paragraph({
|
|
655
|
+
children: [
|
|
656
|
+
new UnsupportedInline({
|
|
657
|
+
raw: html.trim(),
|
|
658
|
+
source: "markdown"
|
|
659
|
+
})
|
|
660
|
+
]
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
/**
|
|
664
|
+
* Parse comment-encoded Decision list.
|
|
665
|
+
* Format: <!--cf:decision:localId;state;content|localId;state;content-->
|
|
666
|
+
*/
|
|
667
|
+
const parseDecisionComment = (html) => Effect.gen(function* () {
|
|
668
|
+
const match = html.match(/<!--cf:decision:(.*)-->/);
|
|
669
|
+
if (!match) {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
// Preserve as UnsupportedBlock with the raw comment for roundtrip
|
|
673
|
+
return new UnsupportedBlock({
|
|
674
|
+
rawHtml: html.trim(),
|
|
675
|
+
source: "markdown"
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
/**
|
|
679
|
+
* Parse layout marker comments.
|
|
680
|
+
* Markers:
|
|
681
|
+
* - <!--cf:layout-start-->
|
|
682
|
+
* - <!--cf:section:index;type;breakoutMode;breakoutWidth;cellCount-->
|
|
683
|
+
* - <!--cf:cell:sectionIndex;cellIndex-->
|
|
684
|
+
* - <!--cf:section-end:index-->
|
|
685
|
+
* - <!--cf:layout-end-->
|
|
686
|
+
*/
|
|
687
|
+
const parseLayoutComment = (html) => Effect.gen(function* () {
|
|
688
|
+
// Check for any layout marker pattern
|
|
689
|
+
if (html.trim() === "<!--cf:layout-start-->" ||
|
|
690
|
+
html.trim() === "<!--cf:layout-end-->" ||
|
|
691
|
+
/<!--cf:section:\d+;[^;]*;[^;]*;[^;]*;\d+-->/.test(html) ||
|
|
692
|
+
/<!--cf:section-end:\d+-->/.test(html) ||
|
|
693
|
+
/<!--cf:cell:\d+;\d+-->/.test(html)) {
|
|
694
|
+
// Preserve as UnsupportedBlock with the raw comment for roundtrip
|
|
695
|
+
return new UnsupportedBlock({
|
|
696
|
+
rawHtml: html.trim(),
|
|
697
|
+
source: "markdown"
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
});
|
|
702
|
+
/**
|
|
703
|
+
* Parse text that may contain embedded HTML patterns not recognized by remark.
|
|
704
|
+
* This handles ac: and ri: namespaced elements that remark treats as text.
|
|
705
|
+
*/
|
|
706
|
+
const parseTextWithEmbeddedHtml = (text) => Effect.gen(function* () {
|
|
707
|
+
const nodes = [];
|
|
708
|
+
// Pattern to match all embedded HTML we care about (comment-encoded)
|
|
709
|
+
// Use non-greedy match for content since account IDs can contain dashes
|
|
710
|
+
// Date can be empty, so use .*? instead of .+?
|
|
711
|
+
const htmlPattern = /<!--cf:emoticon:.+?-->|<!--cf:user:.+?-->|<!--cf:date:.*?-->/g;
|
|
712
|
+
let lastIndex = 0;
|
|
713
|
+
let match;
|
|
714
|
+
while ((match = htmlPattern.exec(text)) !== null) {
|
|
715
|
+
// Add text before the match
|
|
716
|
+
if (match.index > lastIndex) {
|
|
717
|
+
nodes.push(new Text({ value: text.slice(lastIndex, match.index) }));
|
|
718
|
+
}
|
|
719
|
+
// Parse the HTML match
|
|
720
|
+
const parsed = yield* parseInlineHtml(match[0]);
|
|
721
|
+
if (parsed) {
|
|
722
|
+
nodes.push(parsed);
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
// If we can't parse it, keep as text
|
|
726
|
+
nodes.push(new Text({ value: match[0] }));
|
|
727
|
+
}
|
|
728
|
+
lastIndex = match.index + match[0].length;
|
|
729
|
+
}
|
|
730
|
+
// Add remaining text
|
|
731
|
+
if (lastIndex < text.length) {
|
|
732
|
+
nodes.push(new Text({ value: text.slice(lastIndex) }));
|
|
733
|
+
}
|
|
734
|
+
// If no matches, return original text
|
|
735
|
+
if (nodes.length === 0) {
|
|
736
|
+
nodes.push(new Text({ value: text }));
|
|
737
|
+
}
|
|
738
|
+
return nodes;
|
|
739
|
+
});
|
|
740
|
+
/**
|
|
741
|
+
* Convert mdast children to base inline nodes.
|
|
742
|
+
*/
|
|
743
|
+
const mdastChildrenToBaseInline = (children) => Effect.gen(function* () {
|
|
744
|
+
const nodes = [];
|
|
745
|
+
for (const child of children) {
|
|
746
|
+
switch (child.type) {
|
|
747
|
+
case "text": {
|
|
748
|
+
const text = child;
|
|
749
|
+
nodes.push(new Text({ value: text.value }));
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
case "inlineCode": {
|
|
753
|
+
const code = child;
|
|
754
|
+
nodes.push(new InlineCode({ value: code.value }));
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
case "break": {
|
|
758
|
+
nodes.push(new LineBreak({}));
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
default: {
|
|
762
|
+
nodes.push(new UnsupportedInline({ raw: JSON.stringify(child), source: "markdown" }));
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return nodes;
|
|
767
|
+
});
|
|
768
|
+
/**
|
|
769
|
+
* Convert mdast children to simple block nodes.
|
|
770
|
+
*/
|
|
771
|
+
const mdastChildrenToSimpleBlocks = (children) => Effect.gen(function* () {
|
|
772
|
+
const blocks = [];
|
|
773
|
+
for (const child of children) {
|
|
774
|
+
switch (child.type) {
|
|
775
|
+
case "heading": {
|
|
776
|
+
const heading = child;
|
|
777
|
+
const inlineChildren = yield* mdastChildrenToInline(heading.children);
|
|
778
|
+
blocks.push(new Heading({ level: heading.depth, children: inlineChildren }));
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
case "paragraph": {
|
|
782
|
+
const para = child;
|
|
783
|
+
const inlineChildren = yield* mdastChildrenToInline(para.children);
|
|
784
|
+
blocks.push(new Paragraph({ children: inlineChildren }));
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
case "code": {
|
|
788
|
+
const code = child;
|
|
789
|
+
blocks.push(new CodeBlock({ code: code.value, language: code.lang || undefined }));
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
case "thematicBreak": {
|
|
793
|
+
blocks.push(new ThematicBreak({}));
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
case "image": {
|
|
797
|
+
const img = child;
|
|
798
|
+
blocks.push(new Image({ src: img.url, alt: img.alt || undefined }));
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
case "table": {
|
|
802
|
+
const table = child;
|
|
803
|
+
blocks.push(yield* parseTable(table));
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
case "html": {
|
|
807
|
+
// HTML nodes in list items - preserve as-is for roundtrip
|
|
808
|
+
// Trim leading/trailing whitespace that remark may add
|
|
809
|
+
const html = child;
|
|
810
|
+
blocks.push(new UnsupportedBlock({ rawHtml: html.value.trim(), source: "markdown" }));
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
case "list": {
|
|
814
|
+
// Nested lists - when markdown nested lists are parsed, we lose Confluence local-ids
|
|
815
|
+
// This should rarely happen as Confluence nested lists are preserved as HTML
|
|
816
|
+
blocks.push(new UnsupportedBlock({ rawMarkdown: "", source: "markdown" }));
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
default: {
|
|
820
|
+
blocks.push(new UnsupportedBlock({ rawMarkdown: JSON.stringify(child), source: "markdown" }));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return blocks;
|
|
825
|
+
});
|
|
826
|
+
/**
|
|
827
|
+
* Parse mdast list.
|
|
828
|
+
*/
|
|
829
|
+
const parseList = (list) => Effect.gen(function* () {
|
|
830
|
+
const items = [];
|
|
831
|
+
const ordered = list.ordered === true;
|
|
832
|
+
const start = ordered && list.start != null ? list.start : undefined;
|
|
833
|
+
for (const item of list.children) {
|
|
834
|
+
const children = yield* mdastChildrenToSimpleBlocks(item.children);
|
|
835
|
+
if (item.checked != null) {
|
|
836
|
+
items.push({ _tag: "ListItem", checked: item.checked, children });
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
items.push({ _tag: "ListItem", children });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (start !== undefined) {
|
|
843
|
+
return { _tag: "List", version: 1, ordered, start, children: items };
|
|
844
|
+
}
|
|
845
|
+
return { _tag: "List", version: 1, ordered, children: items };
|
|
846
|
+
});
|
|
847
|
+
/**
|
|
848
|
+
* Parse mdast table.
|
|
849
|
+
*/
|
|
850
|
+
const parseTable = (table) => Effect.gen(function* () {
|
|
851
|
+
let header;
|
|
852
|
+
const rows = [];
|
|
853
|
+
for (let i = 0; i < table.children.length; i++) {
|
|
854
|
+
const row = table.children[i];
|
|
855
|
+
if (!row)
|
|
856
|
+
continue;
|
|
857
|
+
const cells = [];
|
|
858
|
+
for (const cell of row.children) {
|
|
859
|
+
const children = yield* mdastChildrenToInline(cell.children);
|
|
860
|
+
const isHeader = i === 0;
|
|
861
|
+
cells.push(new TableCell({ isHeader, children }));
|
|
862
|
+
}
|
|
863
|
+
const tableRow = new TableRow({ cells });
|
|
864
|
+
if (i === 0) {
|
|
865
|
+
header = tableRow;
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
rows.push(tableRow);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return new Table({ header, rows });
|
|
872
|
+
});
|
|
873
|
+
//# sourceMappingURL=MarkdownParser.js.map
|