@knpkv/confluence-to-markdown 0.2.0 → 0.4.1

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 +60 -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 +356 -0
  7. package/dist/ConfluenceAuth.js.map +1 -0
  8. package/dist/ConfluenceClient.d.ts +26 -2
  9. package/dist/ConfluenceClient.d.ts.map +1 -1
  10. package/dist/ConfluenceClient.js +98 -92
  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 +89 -6
  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 +27 -16
  255. package/src/ConfluenceAuth.ts +571 -0
  256. package/src/ConfluenceClient.ts +188 -156
  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,792 @@
1
+ /**
2
+ * Parser for Confluence storage format (HTML) to AST.
3
+ *
4
+ * @module
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as Schema from "effect/Schema";
8
+ import rehypeParse from "rehype-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
+ import { PreprocessedHtmlFromConfluence } from "../schemas/preprocessing/index.js";
16
+ /**
17
+ * Parse Confluence storage format HTML to Document AST.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { parseConfluenceHtml } from "@knpkv/confluence-to-markdown/parsers/ConfluenceParser"
22
+ * import { Effect } from "effect"
23
+ *
24
+ * Effect.gen(function* () {
25
+ * const doc = yield* parseConfluenceHtml("<h1>Title</h1><p>Content</p>")
26
+ * console.log(doc.children.length) // 2
27
+ * })
28
+ * ```
29
+ *
30
+ * @category Parsers
31
+ */
32
+ export const parseConfluenceHtml = (html) => Effect.gen(function* () {
33
+ // Pre-process Confluence macros (includes size validation)
34
+ const preprocessed = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html).pipe(Effect.mapError((error) => new ParseError({
35
+ source: "confluence",
36
+ message: `Preprocessing error: ${error.message}`,
37
+ rawContent: html.slice(0, 200)
38
+ })));
39
+ // Parse HTML to hast
40
+ const hast = yield* Effect.try({
41
+ try: () => unified().use(rehypeParse, { fragment: true }).parse(preprocessed),
42
+ catch: (error) => new ParseError({
43
+ source: "confluence",
44
+ message: `HTML parse error: ${error instanceof Error ? error.message : String(error)}`,
45
+ rawContent: html.slice(0, 200)
46
+ })
47
+ });
48
+ // Convert hast to AST
49
+ const children = yield* hastToDocumentNodes(hast);
50
+ // Store original HTML for 1-to-1 roundtrip
51
+ return makeDocument(children, html);
52
+ });
53
+ /**
54
+ * Convert hast Root to document nodes.
55
+ */
56
+ const hastToDocumentNodes = (root) => Effect.gen(function* () {
57
+ const nodes = [];
58
+ for (const child of root.children) {
59
+ if (child.type === "element") {
60
+ const el = child;
61
+ // Check for marker div containing a cf: comment
62
+ if (el.tagName === "div" && el.properties?.["dataCfMarker"] !== undefined) {
63
+ const commentChild = el.children.find((c) => c.type === "comment");
64
+ if (commentChild) {
65
+ const comment = commentChild.value;
66
+ const node = yield* parseCommentNode(comment);
67
+ if (node !== null)
68
+ nodes.push(node);
69
+ }
70
+ }
71
+ else {
72
+ const node = yield* hastElementToNode(el);
73
+ if (node !== null)
74
+ nodes.push(node);
75
+ }
76
+ }
77
+ else if (child.type === "comment") {
78
+ // Handle cf: comment-encoded elements at root level
79
+ const comment = child.value;
80
+ const node = yield* parseCommentNode(comment);
81
+ if (node !== null)
82
+ nodes.push(node);
83
+ }
84
+ }
85
+ return nodes;
86
+ });
87
+ /**
88
+ * Parse comment-encoded elements (decision lists, layout markers, etc).
89
+ */
90
+ const parseCommentNode = (comment) => Effect.gen(function* () {
91
+ // Decision list: cf:decision:localId;state;content|localId;state;content
92
+ const decisionMatch = comment.match(/^cf:decision:(.*)$/);
93
+ if (decisionMatch) {
94
+ const itemsStr = decisionMatch[1] ?? "";
95
+ // Return as UnsupportedBlock with the encoded comment for roundtrip
96
+ return new UnsupportedBlock({
97
+ rawHtml: `<!--cf:decision:${itemsStr}-->`,
98
+ source: "confluence"
99
+ });
100
+ }
101
+ // Layout markers - these are structural markers, preserve for roundtrip
102
+ // cf:layout-start, cf:layout-end
103
+ if (comment === "cf:layout-start" || comment === "cf:layout-end") {
104
+ return new UnsupportedBlock({
105
+ rawHtml: `<!--${comment}-->`,
106
+ source: "confluence"
107
+ });
108
+ }
109
+ // cf:section:index;type;breakoutMode;breakoutWidth;cellCount
110
+ const sectionMatch = comment.match(/^cf:section:(\d+);([^;]*);([^;]*);([^;]*);(\d+)$/);
111
+ if (sectionMatch) {
112
+ return new UnsupportedBlock({
113
+ rawHtml: `<!--${comment}-->`,
114
+ source: "confluence"
115
+ });
116
+ }
117
+ // cf:section-end:index
118
+ if (comment.startsWith("cf:section-end:")) {
119
+ return new UnsupportedBlock({
120
+ rawHtml: `<!--${comment}-->`,
121
+ source: "confluence"
122
+ });
123
+ }
124
+ // cf:cell:sectionIndex;cellIndex
125
+ const cellMatch = comment.match(/^cf:cell:(\d+);(\d+)$/);
126
+ if (cellMatch) {
127
+ return new UnsupportedBlock({
128
+ rawHtml: `<!--${comment}-->`,
129
+ source: "confluence"
130
+ });
131
+ }
132
+ return null;
133
+ });
134
+ /**
135
+ * Convert hast Element to BlockNode or MacroNode.
136
+ */
137
+ const hastElementToNode = (element) => Effect.gen(function* () {
138
+ const tagName = element.tagName.toLowerCase();
139
+ // Heading
140
+ if (/^h[1-6]$/.test(tagName)) {
141
+ const levelStr = tagName[1];
142
+ if (!levelStr)
143
+ return null;
144
+ const level = parseInt(levelStr);
145
+ const children = yield* hastChildrenToInline(element.children);
146
+ return new Heading({ level, children });
147
+ }
148
+ // Paragraph (with optional alignment and indent)
149
+ if (tagName === "p") {
150
+ const children = yield* hastChildrenToInline(element.children);
151
+ const style = element.properties?.style;
152
+ let alignment;
153
+ let indent;
154
+ if (style) {
155
+ const alignMatch = style.match(/text-align:\s*(left|center|right)/);
156
+ if (alignMatch?.[1]) {
157
+ alignment = alignMatch[1];
158
+ }
159
+ const marginMatch = style.match(/margin-left:\s*(\d+(?:\.\d+)?)\s*px/);
160
+ if (marginMatch?.[1]) {
161
+ indent = parseFloat(marginMatch[1]);
162
+ }
163
+ }
164
+ if (alignment !== undefined || indent !== undefined) {
165
+ return new Paragraph({
166
+ children,
167
+ ...(alignment !== undefined ? { alignment } : {}),
168
+ ...(indent !== undefined ? { indent } : {})
169
+ });
170
+ }
171
+ return new Paragraph({ children });
172
+ }
173
+ // Code block
174
+ if (tagName === "pre") {
175
+ const codeEl = element.children.find((c) => c.type === "element" && c.tagName === "code");
176
+ const code = codeEl ? getTextContent(codeEl) : getTextContent(element);
177
+ const language = element.properties?.["dataLanguage"] || undefined;
178
+ return new CodeBlock({ code, language });
179
+ }
180
+ // Thematic break
181
+ if (tagName === "hr") {
182
+ return new ThematicBreak({});
183
+ }
184
+ // Image (supports both URL and Confluence attachments from preprocessed data)
185
+ if (tagName === "img") {
186
+ const src = element.properties?.src;
187
+ const dataAttachment = element.properties?.["dataAttachment"];
188
+ const dataAlign = element.properties?.["dataAlign"];
189
+ const dataWidth = element.properties?.["dataWidth"];
190
+ const alt = element.properties?.alt || undefined;
191
+ // Confluence attachment (preprocessed)
192
+ if (dataAttachment) {
193
+ return new Image({
194
+ attachment: { filename: dataAttachment },
195
+ alt,
196
+ ...(dataAlign ? { align: dataAlign } : {}),
197
+ ...(dataWidth ? { width: parseInt(dataWidth) } : {})
198
+ });
199
+ }
200
+ // URL-based image
201
+ if (!src)
202
+ return null;
203
+ return new Image({
204
+ src,
205
+ alt,
206
+ title: element.properties?.title || undefined
207
+ });
208
+ }
209
+ // Table
210
+ if (tagName === "table") {
211
+ return yield* parseTable(element);
212
+ }
213
+ // Task list (from preprocessed data)
214
+ if (tagName === "ul" && element.properties?.["dataMacro"] === "task-list") {
215
+ return yield* parseTaskList(element);
216
+ }
217
+ // Lists
218
+ if (tagName === "ul" || tagName === "ol") {
219
+ return yield* parseList(element, tagName === "ol");
220
+ }
221
+ // Block quote
222
+ if (tagName === "blockquote") {
223
+ const children = yield* hastChildrenToSimpleBlocks(element.children);
224
+ return { _tag: "BlockQuote", version: 1, children };
225
+ }
226
+ // Macro divs
227
+ if (tagName === "div" && element.properties?.["dataMacro"]) {
228
+ const macro = element.properties["dataMacro"];
229
+ if (PanelTypes.includes(macro)) {
230
+ const children = yield* hastChildrenToSimpleBlocks(element.children);
231
+ return {
232
+ _tag: "InfoPanel",
233
+ version: 1,
234
+ panelType: macro,
235
+ title: element.properties["dataTitle"] || undefined,
236
+ children
237
+ };
238
+ }
239
+ }
240
+ // Expand/details
241
+ if (tagName === "details") {
242
+ const summary = element.children.find((c) => c.type === "element" && c.tagName === "summary");
243
+ const title = summary ? getTextContent(summary) : undefined;
244
+ const contentChildren = element.children.filter((c) => !(c.type === "element" && c.tagName === "summary"));
245
+ const children = yield* hastChildrenToSimpleBlocks(contentChildren);
246
+ return {
247
+ _tag: "ExpandMacro",
248
+ version: 1,
249
+ title,
250
+ children
251
+ };
252
+ }
253
+ // TOC
254
+ if (tagName === "nav" && element.properties?.["dataMacro"] === "toc") {
255
+ const minStr = element.properties["dataMin"];
256
+ const maxStr = element.properties["dataMax"];
257
+ return {
258
+ _tag: "TocMacro",
259
+ version: 1,
260
+ minLevel: minStr ? parseInt(minStr) : undefined,
261
+ maxLevel: maxStr ? parseInt(maxStr) : undefined
262
+ };
263
+ }
264
+ // Unsupported macro
265
+ if (element.properties?.["dataUnsupportedMacro"]) {
266
+ return new UnsupportedBlock({
267
+ rawHtml: hastToHtml(element),
268
+ source: "confluence"
269
+ });
270
+ }
271
+ // Generic div - recurse into children
272
+ if (tagName === "div" || tagName === "section" || tagName === "article") {
273
+ const children = [];
274
+ for (const child of element.children) {
275
+ if (child.type === "element") {
276
+ const node = yield* hastElementToNode(child);
277
+ if (node !== null)
278
+ children.push(node);
279
+ }
280
+ }
281
+ if (children.length >= 1) {
282
+ const first = children[0];
283
+ return first !== undefined ? first : null;
284
+ }
285
+ return null;
286
+ }
287
+ // Ignore common layout elements
288
+ if (["br", "html", "head", "body"].includes(tagName)) {
289
+ return null;
290
+ }
291
+ // Inline elements at block level - wrap in paragraph
292
+ if (["a", "strong", "em", "b", "i", "u", "code", "del", "sub", "sup", "span"].includes(tagName)) {
293
+ const inlineNode = yield* hastElementToInline(element);
294
+ if (inlineNode) {
295
+ return new Paragraph({ children: [inlineNode] });
296
+ }
297
+ return null;
298
+ }
299
+ // Unknown block element
300
+ return new UnsupportedBlock({
301
+ rawHtml: hastToHtml(element),
302
+ source: "confluence"
303
+ });
304
+ });
305
+ /**
306
+ * Convert hast children to inline nodes.
307
+ */
308
+ const hastChildrenToInline = (children) => Effect.gen(function* () {
309
+ const nodes = [];
310
+ for (const child of children) {
311
+ if (child.type === "text") {
312
+ const textNode = child;
313
+ if (textNode.value.trim() || nodes.length > 0) {
314
+ nodes.push(new Text({ value: textNode.value }));
315
+ }
316
+ }
317
+ else if (child.type === "element") {
318
+ const node = yield* hastElementToInline(child);
319
+ if (node !== null)
320
+ nodes.push(node);
321
+ }
322
+ }
323
+ return nodes;
324
+ });
325
+ /**
326
+ * Convert hast Element to InlineNode.
327
+ */
328
+ const hastElementToInline = (element) => Effect.gen(function* () {
329
+ const tagName = element.tagName.toLowerCase();
330
+ // Strong/bold
331
+ if (tagName === "strong" || tagName === "b") {
332
+ const children = yield* hastChildrenToBaseInline(element.children);
333
+ return new Strong({ children });
334
+ }
335
+ // Emphasis/italic
336
+ if (tagName === "em" || tagName === "i") {
337
+ const children = yield* hastChildrenToBaseInline(element.children);
338
+ return new Emphasis({ children });
339
+ }
340
+ // Underline
341
+ if (tagName === "u") {
342
+ const children = yield* hastChildrenToBaseInline(element.children);
343
+ return new Underline({ children });
344
+ }
345
+ // Strikethrough
346
+ if (tagName === "del" || tagName === "s") {
347
+ const children = yield* hastChildrenToBaseInline(element.children);
348
+ return new Strikethrough({ children });
349
+ }
350
+ // Subscript
351
+ if (tagName === "sub") {
352
+ const children = yield* hastChildrenToBaseInline(element.children);
353
+ return new Subscript({ children });
354
+ }
355
+ // Superscript
356
+ if (tagName === "sup") {
357
+ const children = yield* hastChildrenToBaseInline(element.children);
358
+ return new Superscript({ children });
359
+ }
360
+ // Inline code
361
+ if (tagName === "code") {
362
+ return new InlineCode({ value: getTextContent(element) });
363
+ }
364
+ // Smart link (Jira, Confluence search, etc.) - preserve datasource for roundtrip
365
+ if (tagName === "a" && element.properties?.["dataDatasource"]) {
366
+ const href = element.properties?.href;
367
+ const appearance = element.properties?.["dataCardAppearance"] || "inline";
368
+ const datasource = element.properties["dataDatasource"];
369
+ return new UnsupportedInline({
370
+ raw: `<!--cf:smartlink:${encodeURIComponent(href ?? "")};${encodeURIComponent(appearance)};${encodeURIComponent(datasource)}-->`,
371
+ source: "confluence"
372
+ });
373
+ }
374
+ // Link
375
+ if (tagName === "a") {
376
+ const href = element.properties?.href;
377
+ if (!href)
378
+ return null;
379
+ const children = yield* hastChildrenToBaseInline(element.children);
380
+ return new Link({
381
+ href,
382
+ title: element.properties?.title || undefined,
383
+ children
384
+ });
385
+ }
386
+ // Line break
387
+ if (tagName === "br") {
388
+ return new LineBreak({});
389
+ }
390
+ // Date/time (rehype converts datetime attr to camelCase dateTime)
391
+ if (tagName === "time") {
392
+ const datetime = element.properties?.dateTime || "";
393
+ return new DateTime({ datetime });
394
+ }
395
+ // Emoticon (preprocessed from ac:emoticon)
396
+ if (tagName === "span" && element.properties?.["dataEmoji"]) {
397
+ const shortname = element.properties["dataEmoji"] || "";
398
+ const emojiId = element.properties["dataEmojiId"] || "";
399
+ const fallback = getTextContent(element);
400
+ return new Emoticon({ shortname, emojiId, fallback });
401
+ }
402
+ // User mention (preprocessed from ac:link > ri:user)
403
+ if (tagName === "span" && element.properties?.["dataUserMention"]) {
404
+ const accountId = element.properties["dataUserMention"] || "";
405
+ return new UserMention({ accountId });
406
+ }
407
+ // Confluence link with link-body (preprocessed from ac:link > ac:link-body)
408
+ if (tagName === "span" && element.properties?.["dataConfluenceLink"] !== undefined) {
409
+ const linkText = getTextContent(element);
410
+ return new UnsupportedInline({
411
+ raw: `<!--cf:link:${encodeURIComponent(linkText)}-->`,
412
+ source: "confluence"
413
+ });
414
+ }
415
+ // Status macro (inline) - use comment encoding for roundtrip
416
+ if (tagName === "span" && element.properties?.["dataMacro"] === "status") {
417
+ const color = element.properties["dataColor"] || "";
418
+ const title = getTextContent(element);
419
+ return new UnsupportedInline({
420
+ raw: `<!--cf:status:${encodeURIComponent(title)};${encodeURIComponent(color)}-->`,
421
+ source: "confluence"
422
+ });
423
+ }
424
+ // TOC macro in inline context (e.g., inside table cell) - use comment encoding
425
+ // Use ; as separator (not | which breaks markdown tables)
426
+ if (tagName === "nav" && element.properties?.["dataMacro"] === "toc") {
427
+ const minStr = element.properties["dataMin"];
428
+ const maxStr = element.properties["dataMax"];
429
+ return new UnsupportedInline({
430
+ raw: `<!--cf:toc:${minStr ?? ""};${maxStr ?? ""}-->`,
431
+ source: "confluence"
432
+ });
433
+ }
434
+ // Colored text (span with color style)
435
+ if (tagName === "span") {
436
+ const style = element.properties?.style;
437
+ if (style) {
438
+ const colorMatch = style.match(/(?:^|;)\s*color:\s*([^;]+)/);
439
+ const bgMatch = style.match(/(?:^|;)\s*background-color:\s*([^;]+)/);
440
+ if (colorMatch?.[1]) {
441
+ const children = yield* hastChildrenToBaseInline(element.children);
442
+ return new ColoredText({ color: colorMatch[1].trim(), children });
443
+ }
444
+ if (bgMatch?.[1]) {
445
+ const children = yield* hastChildrenToBaseInline(element.children);
446
+ return new Highlight({ backgroundColor: bgMatch[1].trim(), children });
447
+ }
448
+ }
449
+ // Nested inline elements - extract content
450
+ const children = yield* hastChildrenToInline(element.children);
451
+ if (children.length === 1) {
452
+ const first = children[0];
453
+ return first !== undefined ? first : null;
454
+ }
455
+ return null;
456
+ }
457
+ // Images can be inline too
458
+ if (tagName === "img") {
459
+ return new UnsupportedInline({
460
+ raw: hastToHtml(element),
461
+ source: "confluence"
462
+ });
463
+ }
464
+ // Unknown inline element
465
+ return new UnsupportedInline({
466
+ raw: hastToHtml(element),
467
+ source: "confluence"
468
+ });
469
+ });
470
+ /**
471
+ * Convert hast children to base inline nodes (for Strong/Emphasis/Link children).
472
+ */
473
+ const hastChildrenToBaseInline = (children) => Effect.gen(function* () {
474
+ const nodes = [];
475
+ for (const child of children) {
476
+ if (child.type === "text") {
477
+ const textNode = child;
478
+ nodes.push(new Text({ value: textNode.value }));
479
+ }
480
+ else if (child.type === "element") {
481
+ const el = child;
482
+ const tagName = el.tagName.toLowerCase();
483
+ if (tagName === "code") {
484
+ nodes.push(new InlineCode({ value: getTextContent(el) }));
485
+ }
486
+ else if (tagName === "br") {
487
+ nodes.push(new LineBreak({}));
488
+ }
489
+ else {
490
+ nodes.push(new UnsupportedInline({ raw: hastToHtml(el), source: "confluence" }));
491
+ }
492
+ }
493
+ }
494
+ return nodes;
495
+ });
496
+ /**
497
+ * Convert hast children to simple block nodes (non-recursive).
498
+ */
499
+ const hastChildrenToSimpleBlocks = (children) => Effect.gen(function* () {
500
+ const blocks = [];
501
+ for (const child of children) {
502
+ if (child.type === "element") {
503
+ const el = child;
504
+ const tagName = el.tagName.toLowerCase();
505
+ if (/^h[1-6]$/.test(tagName)) {
506
+ const levelStr = tagName[1];
507
+ if (levelStr) {
508
+ const level = parseInt(levelStr);
509
+ const inlineChildren = yield* hastChildrenToInline(el.children);
510
+ blocks.push(new Heading({ level, children: inlineChildren }));
511
+ }
512
+ }
513
+ else if (tagName === "p") {
514
+ const inlineChildren = yield* hastChildrenToInline(el.children);
515
+ blocks.push(new Paragraph({ children: inlineChildren }));
516
+ }
517
+ else if (tagName === "pre") {
518
+ const codeEl = el.children.find((c) => c.type === "element" && c.tagName === "code");
519
+ const code = codeEl ? getTextContent(codeEl) : getTextContent(el);
520
+ blocks.push(new CodeBlock({ code }));
521
+ }
522
+ else if (tagName === "hr") {
523
+ blocks.push(new ThematicBreak({}));
524
+ }
525
+ else if (tagName === "img") {
526
+ const src = el.properties?.src;
527
+ if (src)
528
+ blocks.push(new Image({ src }));
529
+ }
530
+ else if (tagName === "table") {
531
+ blocks.push(yield* parseTable(el));
532
+ }
533
+ else {
534
+ blocks.push(new UnsupportedBlock({ rawHtml: hastToHtml(el), source: "confluence" }));
535
+ }
536
+ }
537
+ }
538
+ return blocks;
539
+ });
540
+ /**
541
+ * Parse table element.
542
+ */
543
+ const parseTable = (element) => Effect.gen(function* () {
544
+ let header;
545
+ const rows = [];
546
+ for (const child of element.children) {
547
+ if (child.type !== "element")
548
+ continue;
549
+ const el = child;
550
+ if (el.tagName === "thead") {
551
+ const tr = el.children.find((c) => c.type === "element" && c.tagName === "tr");
552
+ if (tr) {
553
+ header = yield* parseTableRow(tr, true);
554
+ }
555
+ }
556
+ else if (el.tagName === "tbody") {
557
+ for (const row of el.children) {
558
+ if (row.type === "element" && row.tagName === "tr") {
559
+ const tr = row;
560
+ // Check if this row has all <th> cells - treat as header if no header yet
561
+ const allTh = tr.children
562
+ .filter((c) => c.type === "element")
563
+ .every((c) => c.tagName === "th");
564
+ if (allTh && !header && rows.length === 0) {
565
+ header = yield* parseTableRow(tr, true);
566
+ }
567
+ else {
568
+ rows.push(yield* parseTableRow(tr, false));
569
+ }
570
+ }
571
+ }
572
+ }
573
+ else if (el.tagName === "tr") {
574
+ rows.push(yield* parseTableRow(el, false));
575
+ }
576
+ }
577
+ return new Table({ header, rows });
578
+ });
579
+ /**
580
+ * Parse table row.
581
+ */
582
+ const parseTableRow = (element, isHeader) => Effect.gen(function* () {
583
+ const cells = [];
584
+ for (const child of element.children) {
585
+ if (child.type === "element") {
586
+ const el = child;
587
+ if (el.tagName === "td" || el.tagName === "th") {
588
+ const cellIsHeader = isHeader || el.tagName === "th";
589
+ // Unwrap single <p> elements inside cells
590
+ const children = yield* parseCellContent(el.children);
591
+ cells.push(new TableCell({ isHeader: cellIsHeader, children }));
592
+ }
593
+ }
594
+ }
595
+ return new TableRow({ cells });
596
+ });
597
+ /**
598
+ * Parse cell content, unwrapping single <p> elements.
599
+ */
600
+ const parseCellContent = (children) => Effect.gen(function* () {
601
+ // Find actual element children (skip whitespace text)
602
+ const elementChildren = children.filter((c) => {
603
+ if (c.type === "element")
604
+ return true;
605
+ if (c.type === "text" && c.value.trim())
606
+ return true;
607
+ return false;
608
+ });
609
+ // If single <p> element, unwrap it
610
+ if (elementChildren.length === 1) {
611
+ const first = elementChildren[0];
612
+ if (first && first.type === "element" && first.tagName === "p") {
613
+ return yield* hastChildrenToInline(first.children);
614
+ }
615
+ }
616
+ // Otherwise parse normally
617
+ return yield* hastChildrenToInline(children);
618
+ });
619
+ /**
620
+ * Parse task list element (preprocessed from ac:task-list).
621
+ */
622
+ const parseTaskList = (element) => Effect.gen(function* () {
623
+ const items = [];
624
+ for (const child of element.children) {
625
+ if (child.type === "element" && child.tagName === "li") {
626
+ const li = child;
627
+ const id = li.properties?.["dataTaskId"] || "";
628
+ const uuid = li.properties?.["dataTaskUuid"] || "";
629
+ const status = li.properties?.["dataTaskStatus"] === "complete"
630
+ ? "complete"
631
+ : "incomplete";
632
+ const body = yield* hastChildrenToInline(li.children);
633
+ items.push({
634
+ _tag: "TaskItem",
635
+ id,
636
+ uuid,
637
+ status,
638
+ body
639
+ });
640
+ }
641
+ }
642
+ return {
643
+ _tag: "TaskList",
644
+ version: 1,
645
+ children: items
646
+ };
647
+ });
648
+ /**
649
+ * Parse list element.
650
+ */
651
+ const parseList = (element, ordered) => Effect.gen(function* () {
652
+ const items = [];
653
+ const startProp = element.properties?.start;
654
+ const start = ordered && startProp ? parseInt(String(startProp)) : undefined;
655
+ for (const child of element.children) {
656
+ if (child.type === "element" && child.tagName === "li") {
657
+ const li = child;
658
+ const children = yield* parseListItemContent(li.children);
659
+ // Check for task list items
660
+ const checkbox = li.children.find((c) => c.type === "element" &&
661
+ c.tagName === "input" &&
662
+ c.properties?.type === "checkbox");
663
+ const checked = checkbox ? (checkbox.properties?.checked === true) : undefined;
664
+ if (checked !== undefined) {
665
+ items.push({ _tag: "ListItem", checked, children });
666
+ }
667
+ else {
668
+ items.push({ _tag: "ListItem", children });
669
+ }
670
+ }
671
+ }
672
+ if (start !== undefined) {
673
+ return { _tag: "List", version: 1, ordered, start, children: items };
674
+ }
675
+ return { _tag: "List", version: 1, ordered, children: items };
676
+ });
677
+ /**
678
+ * Parse list item content, handling nested lists and unwrapping single <p>.
679
+ * Also handles direct text/inline content without wrapper elements.
680
+ */
681
+ const parseListItemContent = (children) => Effect.gen(function* () {
682
+ const blocks = [];
683
+ // Check if there's any direct text/inline content (not wrapped in <p>)
684
+ const hasDirectInlineContent = children.some((child) => {
685
+ if (child.type === "text") {
686
+ return child.value.trim() !== "";
687
+ }
688
+ if (child.type === "element") {
689
+ const tagName = child.tagName.toLowerCase();
690
+ // Inline elements that should be wrapped in a paragraph
691
+ return ["a", "strong", "em", "b", "i", "u", "code", "span", "del", "sub", "sup"].includes(tagName);
692
+ }
693
+ return false;
694
+ });
695
+ // If there's direct inline content, wrap it all in a paragraph
696
+ if (hasDirectInlineContent) {
697
+ const inlineChildren = yield* hastChildrenToInline(children);
698
+ if (inlineChildren.length > 0) {
699
+ blocks.push(new Paragraph({ children: inlineChildren }));
700
+ }
701
+ // Also check for nested lists after the inline content
702
+ for (const child of children) {
703
+ if (child.type === "element") {
704
+ const el = child;
705
+ const tagName = el.tagName.toLowerCase();
706
+ if (tagName === "ul" || tagName === "ol") {
707
+ blocks.push(new UnsupportedBlock({ rawHtml: hastToHtml(el), source: "confluence" }));
708
+ }
709
+ }
710
+ }
711
+ return blocks;
712
+ }
713
+ for (const child of children) {
714
+ if (child.type !== "element")
715
+ continue;
716
+ const el = child;
717
+ const tagName = el.tagName.toLowerCase();
718
+ // Single <p> inside list item - extract inline content as paragraph
719
+ if (tagName === "p") {
720
+ const inlineChildren = yield* hastChildrenToInline(el.children);
721
+ blocks.push(new Paragraph({ children: inlineChildren }));
722
+ } // Nested lists - convert to paragraph with raw HTML for now (will be handled later)
723
+ else if (tagName === "ul" || tagName === "ol") {
724
+ // For nested lists, preserve as unsupported for now
725
+ blocks.push(new UnsupportedBlock({ rawHtml: hastToHtml(el), source: "confluence" }));
726
+ } // Other block elements
727
+ else if (tagName === "pre") {
728
+ const codeEl = el.children.find((c) => c.type === "element" && c.tagName === "code");
729
+ const code = codeEl ? getTextContent(codeEl) : getTextContent(el);
730
+ blocks.push(new CodeBlock({ code }));
731
+ }
732
+ else if (tagName === "hr") {
733
+ blocks.push(new ThematicBreak({}));
734
+ }
735
+ else if (tagName === "img") {
736
+ const src = el.properties?.src;
737
+ if (src)
738
+ blocks.push(new Image({ src }));
739
+ }
740
+ else if (tagName === "table") {
741
+ blocks.push(yield* parseTable(el));
742
+ }
743
+ else if (/^h[1-6]$/.test(tagName)) {
744
+ const levelStr = tagName[1];
745
+ if (levelStr) {
746
+ const level = parseInt(levelStr);
747
+ const inlineChildren = yield* hastChildrenToInline(el.children);
748
+ blocks.push(new Heading({ level, children: inlineChildren }));
749
+ }
750
+ }
751
+ }
752
+ return blocks;
753
+ });
754
+ /**
755
+ * Get text content from hast node.
756
+ */
757
+ const getTextContent = (element) => {
758
+ let text = "";
759
+ for (const child of element.children) {
760
+ if (child.type === "text") {
761
+ text += child.value;
762
+ }
763
+ else if (child.type === "element") {
764
+ text += getTextContent(child);
765
+ }
766
+ }
767
+ return text;
768
+ };
769
+ /**
770
+ * Convert hast element back to HTML string (for unsupported elements).
771
+ */
772
+ const hastToHtml = (element) => {
773
+ const props = Object.entries(element.properties || {})
774
+ .map(([k, v]) => {
775
+ const attrName = k.replace(/([A-Z])/g, "-$1").toLowerCase();
776
+ return `${attrName}="${String(v)}"`;
777
+ })
778
+ .join(" ");
779
+ const openTag = props ? `<${element.tagName} ${props}>` : `<${element.tagName}>`;
780
+ const closeTag = `</${element.tagName}>`;
781
+ const content = element.children
782
+ .map((c) => {
783
+ if (c.type === "text")
784
+ return c.value;
785
+ if (c.type === "element")
786
+ return hastToHtml(c);
787
+ return "";
788
+ })
789
+ .join("");
790
+ return `${openTag}${content}${closeTag}`;
791
+ };
792
+ //# sourceMappingURL=ConfluenceParser.js.map