@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.
Files changed (336) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +21 -0
  3. package/README.md +282 -14
  4. package/dist/ConfluenceAuth.d.ts +76 -0
  5. package/dist/ConfluenceAuth.d.ts.map +1 -0
  6. package/dist/ConfluenceAuth.js +366 -0
  7. package/dist/ConfluenceAuth.js.map +1 -0
  8. package/dist/ConfluenceClient.d.ts +26 -12
  9. package/dist/ConfluenceClient.d.ts.map +1 -1
  10. package/dist/ConfluenceClient.js +139 -97
  11. package/dist/ConfluenceClient.js.map +1 -1
  12. package/dist/ConfluenceConfig.d.ts +4 -24
  13. package/dist/ConfluenceConfig.d.ts.map +1 -1
  14. package/dist/ConfluenceConfig.js +45 -7
  15. package/dist/ConfluenceConfig.js.map +1 -1
  16. package/dist/ConfluenceError.d.ts +99 -16
  17. package/dist/ConfluenceError.d.ts.map +1 -1
  18. package/dist/ConfluenceError.js +88 -5
  19. package/dist/ConfluenceError.js.map +1 -1
  20. package/dist/GitError.d.ts +103 -0
  21. package/dist/GitError.d.ts.map +1 -0
  22. package/dist/GitError.js +85 -0
  23. package/dist/GitError.js.map +1 -0
  24. package/dist/GitService.d.ts +175 -0
  25. package/dist/GitService.d.ts.map +1 -0
  26. package/dist/GitService.js +431 -0
  27. package/dist/GitService.js.map +1 -0
  28. package/dist/LocalFileSystem.d.ts +29 -4
  29. package/dist/LocalFileSystem.d.ts.map +1 -1
  30. package/dist/LocalFileSystem.js +80 -6
  31. package/dist/LocalFileSystem.js.map +1 -1
  32. package/dist/MarkdownConverter.d.ts +49 -2
  33. package/dist/MarkdownConverter.d.ts.map +1 -1
  34. package/dist/MarkdownConverter.js +73 -111
  35. package/dist/MarkdownConverter.js.map +1 -1
  36. package/dist/SchemaConverterError.d.ts +108 -0
  37. package/dist/SchemaConverterError.d.ts.map +1 -0
  38. package/dist/SchemaConverterError.js +84 -0
  39. package/dist/SchemaConverterError.js.map +1 -0
  40. package/dist/Schemas.d.ts +225 -1
  41. package/dist/Schemas.d.ts.map +1 -1
  42. package/dist/Schemas.js +155 -6
  43. package/dist/Schemas.js.map +1 -1
  44. package/dist/SyncEngine.d.ts +30 -20
  45. package/dist/SyncEngine.d.ts.map +1 -1
  46. package/dist/SyncEngine.js +566 -117
  47. package/dist/SyncEngine.js.map +1 -1
  48. package/dist/ast/BlockNode.d.ts +468 -0
  49. package/dist/ast/BlockNode.d.ts.map +1 -0
  50. package/dist/ast/BlockNode.js +319 -0
  51. package/dist/ast/BlockNode.js.map +1 -0
  52. package/dist/ast/Document.d.ts +244 -0
  53. package/dist/ast/Document.d.ts.map +1 -0
  54. package/dist/ast/Document.js +69 -0
  55. package/dist/ast/Document.js.map +1 -0
  56. package/dist/ast/InlineNode.d.ts +477 -0
  57. package/dist/ast/InlineNode.d.ts.map +1 -0
  58. package/dist/ast/InlineNode.js +263 -0
  59. package/dist/ast/InlineNode.js.map +1 -0
  60. package/dist/ast/MacroNode.d.ts +267 -0
  61. package/dist/ast/MacroNode.d.ts.map +1 -0
  62. package/dist/ast/MacroNode.js +164 -0
  63. package/dist/ast/MacroNode.js.map +1 -0
  64. package/dist/ast/index.d.ts +10 -0
  65. package/dist/ast/index.d.ts.map +1 -0
  66. package/dist/ast/index.js +14 -0
  67. package/dist/ast/index.js.map +1 -0
  68. package/dist/bin.js +33 -149
  69. package/dist/bin.js.map +1 -1
  70. package/dist/commands/auth.d.ts +15 -0
  71. package/dist/commands/auth.d.ts.map +1 -0
  72. package/dist/commands/auth.js +86 -0
  73. package/dist/commands/auth.js.map +1 -0
  74. package/dist/commands/clone.d.ts +12 -0
  75. package/dist/commands/clone.d.ts.map +1 -0
  76. package/dist/commands/clone.js +93 -0
  77. package/dist/commands/clone.js.map +1 -0
  78. package/dist/commands/delete.d.ts +13 -0
  79. package/dist/commands/delete.d.ts.map +1 -0
  80. package/dist/commands/delete.js +48 -0
  81. package/dist/commands/delete.js.map +1 -0
  82. package/dist/commands/errorHandler.d.ts +14 -0
  83. package/dist/commands/errorHandler.d.ts.map +1 -0
  84. package/dist/commands/errorHandler.js +33 -0
  85. package/dist/commands/errorHandler.js.map +1 -0
  86. package/dist/commands/git.d.ts +22 -0
  87. package/dist/commands/git.d.ts.map +1 -0
  88. package/dist/commands/git.js +72 -0
  89. package/dist/commands/git.js.map +1 -0
  90. package/dist/commands/index.d.ts +11 -0
  91. package/dist/commands/index.d.ts.map +1 -0
  92. package/dist/commands/index.js +11 -0
  93. package/dist/commands/index.js.map +1 -0
  94. package/dist/commands/layers.d.ts +31 -0
  95. package/dist/commands/layers.d.ts.map +1 -0
  96. package/dist/commands/layers.js +137 -0
  97. package/dist/commands/layers.js.map +1 -0
  98. package/dist/commands/new.d.ts +9 -0
  99. package/dist/commands/new.d.ts.map +1 -0
  100. package/dist/commands/new.js +80 -0
  101. package/dist/commands/new.js.map +1 -0
  102. package/dist/commands/pageTree.d.ts +18 -0
  103. package/dist/commands/pageTree.d.ts.map +1 -0
  104. package/dist/commands/pageTree.js +20 -0
  105. package/dist/commands/pageTree.js.map +1 -0
  106. package/dist/commands/shared.d.ts +15 -0
  107. package/dist/commands/shared.d.ts.map +1 -0
  108. package/dist/commands/shared.js +27 -0
  109. package/dist/commands/shared.js.map +1 -0
  110. package/dist/commands/sync.d.ts +15 -0
  111. package/dist/commands/sync.d.ts.map +1 -0
  112. package/dist/commands/sync.js +101 -0
  113. package/dist/commands/sync.js.map +1 -0
  114. package/dist/index.d.ts +10 -1
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +14 -0
  117. package/dist/index.js.map +1 -1
  118. package/dist/internal/NodeLayers.d.ts +7 -0
  119. package/dist/internal/NodeLayers.d.ts.map +1 -0
  120. package/dist/internal/NodeLayers.js +19 -0
  121. package/dist/internal/NodeLayers.js.map +1 -0
  122. package/dist/internal/frontmatter.d.ts +10 -0
  123. package/dist/internal/frontmatter.d.ts.map +1 -1
  124. package/dist/internal/frontmatter.js +16 -0
  125. package/dist/internal/frontmatter.js.map +1 -1
  126. package/dist/internal/gitCommands.d.ts +78 -0
  127. package/dist/internal/gitCommands.d.ts.map +1 -0
  128. package/dist/internal/gitCommands.js +156 -0
  129. package/dist/internal/gitCommands.js.map +1 -0
  130. package/dist/internal/hashUtils.d.ts +42 -1
  131. package/dist/internal/hashUtils.d.ts.map +1 -1
  132. package/dist/internal/hashUtils.js +38 -2
  133. package/dist/internal/hashUtils.js.map +1 -1
  134. package/dist/internal/oauthServer.d.ts +55 -0
  135. package/dist/internal/oauthServer.d.ts.map +1 -0
  136. package/dist/internal/oauthServer.js +110 -0
  137. package/dist/internal/oauthServer.js.map +1 -0
  138. package/dist/internal/pathUtils.d.ts +21 -4
  139. package/dist/internal/pathUtils.d.ts.map +1 -1
  140. package/dist/internal/pathUtils.js +24 -13
  141. package/dist/internal/pathUtils.js.map +1 -1
  142. package/dist/internal/tokenStorage.d.ts +75 -0
  143. package/dist/internal/tokenStorage.d.ts.map +1 -0
  144. package/dist/internal/tokenStorage.js +149 -0
  145. package/dist/internal/tokenStorage.js.map +1 -0
  146. package/dist/internal/userCache.d.ts +42 -0
  147. package/dist/internal/userCache.d.ts.map +1 -0
  148. package/dist/internal/userCache.js +51 -0
  149. package/dist/internal/userCache.js.map +1 -0
  150. package/dist/parsers/ConfluenceParser.d.ts +26 -0
  151. package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
  152. package/dist/parsers/ConfluenceParser.js +792 -0
  153. package/dist/parsers/ConfluenceParser.js.map +1 -0
  154. package/dist/parsers/MarkdownParser.d.ts +26 -0
  155. package/dist/parsers/MarkdownParser.d.ts.map +1 -0
  156. package/dist/parsers/MarkdownParser.js +873 -0
  157. package/dist/parsers/MarkdownParser.js.map +1 -0
  158. package/dist/parsers/index.d.ts +8 -0
  159. package/dist/parsers/index.d.ts.map +1 -0
  160. package/dist/parsers/index.js +8 -0
  161. package/dist/parsers/index.js.map +1 -0
  162. package/dist/schemas/ConfluenceSchema.d.ts +21 -0
  163. package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
  164. package/dist/schemas/ConfluenceSchema.js +38 -0
  165. package/dist/schemas/ConfluenceSchema.js.map +1 -0
  166. package/dist/schemas/ConversionSchema.d.ts +35 -0
  167. package/dist/schemas/ConversionSchema.d.ts.map +1 -0
  168. package/dist/schemas/ConversionSchema.js +208 -0
  169. package/dist/schemas/ConversionSchema.js.map +1 -0
  170. package/dist/schemas/MarkdownSchema.d.ts +21 -0
  171. package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
  172. package/dist/schemas/MarkdownSchema.js +38 -0
  173. package/dist/schemas/MarkdownSchema.js.map +1 -0
  174. package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
  175. package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
  176. package/dist/schemas/hast/HastFromHtml.js +107 -0
  177. package/dist/schemas/hast/HastFromHtml.js.map +1 -0
  178. package/dist/schemas/hast/HastSchema.d.ts +195 -0
  179. package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
  180. package/dist/schemas/hast/HastSchema.js +183 -0
  181. package/dist/schemas/hast/HastSchema.js.map +1 -0
  182. package/dist/schemas/hast/index.d.ts +9 -0
  183. package/dist/schemas/hast/index.d.ts.map +1 -0
  184. package/dist/schemas/hast/index.js +3 -0
  185. package/dist/schemas/hast/index.js.map +1 -0
  186. package/dist/schemas/index.d.ts +14 -0
  187. package/dist/schemas/index.d.ts.map +1 -0
  188. package/dist/schemas/index.js +16 -0
  189. package/dist/schemas/index.js.map +1 -0
  190. package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
  191. package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
  192. package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
  193. package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
  194. package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
  195. package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
  196. package/dist/schemas/mdast/MdastSchema.js +266 -0
  197. package/dist/schemas/mdast/MdastSchema.js.map +1 -0
  198. package/dist/schemas/mdast/index.d.ts +10 -0
  199. package/dist/schemas/mdast/index.d.ts.map +1 -0
  200. package/dist/schemas/mdast/index.js +4 -0
  201. package/dist/schemas/mdast/index.js.map +1 -0
  202. package/dist/schemas/mdast/mdastToString.d.ts +13 -0
  203. package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
  204. package/dist/schemas/mdast/mdastToString.js +85 -0
  205. package/dist/schemas/mdast/mdastToString.js.map +1 -0
  206. package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
  207. package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
  208. package/dist/schemas/nodes/block/BlockSchema.js +634 -0
  209. package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
  210. package/dist/schemas/nodes/block/index.d.ts +7 -0
  211. package/dist/schemas/nodes/block/index.d.ts.map +1 -0
  212. package/dist/schemas/nodes/block/index.js +7 -0
  213. package/dist/schemas/nodes/block/index.js.map +1 -0
  214. package/dist/schemas/nodes/index.d.ts +9 -0
  215. package/dist/schemas/nodes/index.d.ts.map +1 -0
  216. package/dist/schemas/nodes/index.js +12 -0
  217. package/dist/schemas/nodes/index.js.map +1 -0
  218. package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
  219. package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
  220. package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
  221. package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
  222. package/dist/schemas/nodes/inline/index.d.ts +7 -0
  223. package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
  224. package/dist/schemas/nodes/inline/index.js +7 -0
  225. package/dist/schemas/nodes/inline/index.js.map +1 -0
  226. package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
  227. package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
  228. package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
  229. package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
  230. package/dist/schemas/nodes/macro/index.d.ts +7 -0
  231. package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
  232. package/dist/schemas/nodes/macro/index.js +7 -0
  233. package/dist/schemas/nodes/macro/index.js.map +1 -0
  234. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
  235. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
  236. package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
  237. package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
  238. package/dist/schemas/preprocessing/index.d.ts +8 -0
  239. package/dist/schemas/preprocessing/index.d.ts.map +1 -0
  240. package/dist/schemas/preprocessing/index.js +2 -0
  241. package/dist/schemas/preprocessing/index.js.map +1 -0
  242. package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
  243. package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
  244. package/dist/serializers/ConfluenceSerializer.js +551 -0
  245. package/dist/serializers/ConfluenceSerializer.js.map +1 -0
  246. package/dist/serializers/MarkdownSerializer.d.ts +34 -0
  247. package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
  248. package/dist/serializers/MarkdownSerializer.js +355 -0
  249. package/dist/serializers/MarkdownSerializer.js.map +1 -0
  250. package/dist/serializers/index.d.ts +8 -0
  251. package/dist/serializers/index.d.ts.map +1 -0
  252. package/dist/serializers/index.js +8 -0
  253. package/dist/serializers/index.js.map +1 -0
  254. package/package.json +32 -21
  255. package/src/ConfluenceAuth.ts +581 -0
  256. package/src/ConfluenceClient.ts +230 -165
  257. package/src/ConfluenceConfig.ts +63 -7
  258. package/src/ConfluenceError.ts +110 -14
  259. package/src/GitError.ts +92 -0
  260. package/src/GitService.ts +859 -0
  261. package/src/LocalFileSystem.ts +179 -9
  262. package/src/MarkdownConverter.ts +126 -122
  263. package/src/SchemaConverterError.ts +108 -0
  264. package/src/Schemas.ts +223 -6
  265. package/src/SyncEngine.ts +745 -162
  266. package/src/ast/BlockNode.ts +425 -0
  267. package/src/ast/Document.ts +90 -0
  268. package/src/ast/InlineNode.ts +323 -0
  269. package/src/ast/MacroNode.ts +245 -0
  270. package/src/ast/index.ts +83 -0
  271. package/src/bin.ts +50 -249
  272. package/src/commands/auth.ts +117 -0
  273. package/src/commands/clone.ts +145 -0
  274. package/src/commands/delete.ts +57 -0
  275. package/src/commands/errorHandler.ts +32 -0
  276. package/src/commands/git.ts +114 -0
  277. package/src/commands/index.ts +10 -0
  278. package/src/commands/layers.ts +211 -0
  279. package/src/commands/new.ts +99 -0
  280. package/src/commands/pageTree.ts +40 -0
  281. package/src/commands/shared.ts +35 -0
  282. package/src/commands/sync.ts +129 -0
  283. package/src/index.ts +21 -1
  284. package/src/internal/NodeLayers.ts +21 -0
  285. package/src/internal/frontmatter.ts +21 -0
  286. package/src/internal/gitCommands.ts +229 -0
  287. package/src/internal/hashUtils.ts +65 -3
  288. package/src/internal/oauthServer.ts +199 -0
  289. package/src/internal/pathUtils.ts +34 -17
  290. package/src/internal/tokenStorage.ts +240 -0
  291. package/src/internal/userCache.ts +90 -0
  292. package/src/parsers/ConfluenceParser.ts +950 -0
  293. package/src/parsers/MarkdownParser.ts +1198 -0
  294. package/src/parsers/index.ts +8 -0
  295. package/src/schemas/ConfluenceSchema.ts +56 -0
  296. package/src/schemas/ConversionSchema.ts +318 -0
  297. package/src/schemas/MarkdownSchema.ts +56 -0
  298. package/src/schemas/hast/HastFromHtml.ts +153 -0
  299. package/src/schemas/hast/HastSchema.ts +274 -0
  300. package/src/schemas/hast/index.ts +35 -0
  301. package/src/schemas/index.ts +20 -0
  302. package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
  303. package/src/schemas/mdast/MdastSchema.ts +566 -0
  304. package/src/schemas/mdast/index.ts +59 -0
  305. package/src/schemas/mdast/mdastToString.ts +102 -0
  306. package/src/schemas/nodes/block/BlockSchema.ts +773 -0
  307. package/src/schemas/nodes/block/index.ts +13 -0
  308. package/src/schemas/nodes/index.ts +20 -0
  309. package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
  310. package/src/schemas/nodes/inline/index.ts +14 -0
  311. package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
  312. package/src/schemas/nodes/macro/index.ts +6 -0
  313. package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
  314. package/src/schemas/preprocessing/index.ts +8 -0
  315. package/src/serializers/ConfluenceSerializer.ts +717 -0
  316. package/src/serializers/MarkdownSerializer.ts +493 -0
  317. package/src/serializers/index.ts +8 -0
  318. package/test/GitService.test.ts +209 -0
  319. package/test/MarkdownConverter.test.ts +37 -3
  320. package/test/Schemas.test.ts +97 -2
  321. package/test/ast/BlockNode.test.ts +265 -0
  322. package/test/ast/Document.test.ts +126 -0
  323. package/test/ast/InlineNode.test.ts +161 -0
  324. package/test/fixtures/integration-test.html.fixture +103 -0
  325. package/test/fixtures/integration-test.md.expected +257 -0
  326. package/test/integration.test.ts +269 -0
  327. package/test/oauthServer.test.ts +50 -0
  328. package/test/parsers/ConfluenceParser.test.ts +283 -0
  329. package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
  330. package/test/schemas/ConversionSchema.test.ts +159 -0
  331. package/test/schemas/HastSchema.test.ts +138 -0
  332. package/test/schemas/MdastSchema.test.ts +145 -0
  333. package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
  334. package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
  335. package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
  336. 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: `![${img.alt || ""}](${img.url})`,
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