@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,226 @@
1
+ /**
2
+ * Transform schemas for macro nodes (Hast <-> AST).
3
+ *
4
+ * Provides transforms for Confluence-specific macro elements like info panels,
5
+ * expand sections, TOC, and status badges.
6
+ *
7
+ * @module
8
+ */
9
+ import * as Effect from "effect/Effect"
10
+ import type * as ParseResult from "effect/ParseResult"
11
+ import type {
12
+ BlockNode,
13
+ CodeBlock,
14
+ Heading,
15
+ Image,
16
+ Paragraph,
17
+ Table,
18
+ ThematicBreak,
19
+ UnsupportedBlock
20
+ } from "../../../ast/BlockNode.js"
21
+ import {
22
+ type CodeMacro,
23
+ type ExpandMacro,
24
+ type InfoPanel,
25
+ type MacroNode,
26
+ PanelTypes,
27
+ type StatusMacro,
28
+ type TocMacro
29
+ } from "../../../ast/MacroNode.js"
30
+ import type { HastElement, HastNode } from "../../hast/index.js"
31
+ import { getTextContent, isHastElement, makeHastElement, makeHastText } from "../../hast/index.js"
32
+ import type { MdastBlockContent } from "../../mdast/index.js"
33
+ import { makeMdastCode, makeMdastParagraph, makeMdastText, mdastToString } from "../../mdast/index.js"
34
+ import { blockNodeToHast, blockNodeToMdast } from "../block/index.js"
35
+
36
+ type SimpleBlock = Heading | Paragraph | CodeBlock | ThematicBreak | Image | Table | UnsupportedBlock
37
+
38
+ /**
39
+ * Convert HAST element to AST macro node.
40
+ */
41
+ export const macroNodeFromHastElement = (
42
+ element: HastElement,
43
+ parseBlockChildren: (
44
+ children: ReadonlyArray<HastNode>
45
+ ) => Effect.Effect<ReadonlyArray<BlockNode>, ParseResult.ParseError>
46
+ ): Effect.Effect<MacroNode | null, ParseResult.ParseError> =>
47
+ Effect.gen(function*() {
48
+ const tagName = element.tagName.toLowerCase()
49
+
50
+ // Info/warning/note panels
51
+ if (tagName === "div" && element.properties?.["dataMacro"]) {
52
+ const macro = element.properties["dataMacro"] as string
53
+ if ((PanelTypes as ReadonlyArray<string>).includes(macro)) {
54
+ const children = yield* parseBlockChildren(element.children)
55
+ // Cast to SimpleBlock[] - at runtime only simple blocks are parsed for panel children
56
+ return {
57
+ _tag: "InfoPanel" as const,
58
+ version: 1,
59
+ panelType: macro as (typeof PanelTypes)[number],
60
+ title: (element.properties["dataTitle"] as string) || undefined,
61
+ children: children as ReadonlyArray<SimpleBlock>
62
+ } satisfies InfoPanel
63
+ }
64
+ }
65
+
66
+ // Expand/details
67
+ if (tagName === "details") {
68
+ const summary = element.children.find(
69
+ (c): c is HastElement => isHastElement(c) && c.tagName === "summary"
70
+ )
71
+ const title = summary ? getTextContent(summary) : undefined
72
+ const contentChildren = element.children.filter(
73
+ (c) => !(isHastElement(c) && c.tagName === "summary")
74
+ )
75
+ const children = yield* parseBlockChildren(contentChildren)
76
+ // Cast to SimpleBlock[] - at runtime only simple blocks are parsed for expand children
77
+ return {
78
+ _tag: "ExpandMacro" as const,
79
+ version: 1,
80
+ title,
81
+ children: children as ReadonlyArray<SimpleBlock>
82
+ } satisfies ExpandMacro
83
+ }
84
+
85
+ // TOC
86
+ if (tagName === "nav" && element.properties?.["dataMacro"] === "toc") {
87
+ const minStr = element.properties["dataMin"] as string | undefined
88
+ const maxStr = element.properties["dataMax"] as string | undefined
89
+ return {
90
+ _tag: "TocMacro" as const,
91
+ version: 1,
92
+ minLevel: minStr ? parseInt(minStr) : undefined,
93
+ maxLevel: maxStr ? parseInt(maxStr) : undefined
94
+ } satisfies TocMacro
95
+ }
96
+
97
+ // Code macro (from preprocessed data)
98
+ if (tagName === "pre" && element.properties?.["dataMacro"] === "code") {
99
+ const codeEl = element.children.find(
100
+ (c): c is HastElement => isHastElement(c) && c.tagName === "code"
101
+ )
102
+ const code = codeEl ? getTextContent(codeEl) : getTextContent(element)
103
+ const language = (element.properties["dataLanguage"] as string) || undefined
104
+ return {
105
+ _tag: "CodeMacro" as const,
106
+ version: 1,
107
+ language,
108
+ code,
109
+ lineNumbers: false,
110
+ collapse: false
111
+ } satisfies CodeMacro
112
+ }
113
+
114
+ // Status macro
115
+ if (tagName === "span" && element.properties?.["dataMacro"] === "status") {
116
+ const color = (element.properties["dataColor"] as string) || "Grey"
117
+ const text = getTextContent(element)
118
+ return {
119
+ _tag: "StatusMacro" as const,
120
+ version: 1,
121
+ text,
122
+ color: normalizeStatusColor(color)
123
+ } satisfies StatusMacro
124
+ }
125
+
126
+ return null
127
+ })
128
+
129
+ /**
130
+ * Normalize status color to allowed values.
131
+ */
132
+ const normalizeStatusColor = (color: string): "Grey" | "Red" | "Yellow" | "Green" | "Blue" => {
133
+ const normalized = color.toLowerCase()
134
+ switch (normalized) {
135
+ case "red":
136
+ return "Red"
137
+ case "yellow":
138
+ return "Yellow"
139
+ case "green":
140
+ return "Green"
141
+ case "blue":
142
+ return "Blue"
143
+ default:
144
+ return "Grey"
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Convert AST macro node to HAST element.
150
+ */
151
+ export const macroNodeToHast = (node: MacroNode): HastElement => {
152
+ switch (node._tag) {
153
+ case "InfoPanel":
154
+ return makeHastElement(
155
+ "div",
156
+ {
157
+ dataMacro: node.panelType,
158
+ ...(node.title ? { dataTitle: node.title } : {})
159
+ },
160
+ node.children.map(blockNodeToHast)
161
+ )
162
+ case "ExpandMacro":
163
+ return makeHastElement(
164
+ "details",
165
+ { dataMacro: "expand" },
166
+ [
167
+ ...(node.title ? [makeHastElement("summary", {}, [makeHastText(node.title)])] : []),
168
+ ...node.children.map(blockNodeToHast)
169
+ ]
170
+ )
171
+ case "TocMacro":
172
+ return makeHastElement("nav", {
173
+ dataMacro: "toc",
174
+ ...(node.minLevel !== undefined ? { dataMin: String(node.minLevel) } : {}),
175
+ ...(node.maxLevel !== undefined ? { dataMax: String(node.maxLevel) } : {})
176
+ })
177
+ case "CodeMacro":
178
+ return makeHastElement(
179
+ "pre",
180
+ {
181
+ dataMacro: "code",
182
+ ...(node.language ? { dataLanguage: node.language } : {})
183
+ },
184
+ [makeHastElement("code", {}, [makeHastText(node.code)])]
185
+ )
186
+ case "StatusMacro":
187
+ return makeHastElement(
188
+ "span",
189
+ { dataMacro: "status", dataColor: node.color },
190
+ [makeHastText(node.text)]
191
+ )
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Convert AST macro node to MDAST block content.
197
+ */
198
+ export const macroNodeToMdast = (node: MacroNode): MdastBlockContent => {
199
+ switch (node._tag) {
200
+ case "InfoPanel":
201
+ // Render as container syntax with children
202
+ return {
203
+ type: "html",
204
+ value: `:::${node.panelType}${node.title ? ` ${node.title}` : ""}\n${
205
+ node.children.map((c) => mdastToString(blockNodeToMdast(c))).join("\n")
206
+ }\n:::`
207
+ }
208
+ case "ExpandMacro":
209
+ return {
210
+ type: "html",
211
+ value: `<details>\n<summary>${node.title ?? ""}</summary>\n${
212
+ node.children.map((c) => mdastToString(blockNodeToMdast(c))).join("\n")
213
+ }\n</details>`
214
+ }
215
+ case "TocMacro":
216
+ return {
217
+ type: "html",
218
+ value: "[[toc]]"
219
+ }
220
+ case "CodeMacro":
221
+ return makeMdastCode(node.code, node.language)
222
+ case "StatusMacro":
223
+ // Render as badge-like text
224
+ return makeMdastParagraph([makeMdastText(`[${node.text}]`)])
225
+ }
226
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Macro node transform schemas.
3
+ *
4
+ * @module
5
+ */
6
+ export { macroNodeFromHastElement, macroNodeToHast, macroNodeToMdast } from "./MacroSchema.js"
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Schema-based Confluence HTML preprocessing.
3
+ *
4
+ * Transforms raw Confluence storage format into parseable HTML by expanding
5
+ * macros, converting task lists, and normalizing Confluence-specific markup.
6
+ *
7
+ * @module
8
+ */
9
+ import type * as Brand from "effect/Brand"
10
+ import * as Effect from "effect/Effect"
11
+ import { pipe } from "effect/Function"
12
+ import * as ParseResult from "effect/ParseResult"
13
+ import * as Schema from "effect/Schema"
14
+ import { PanelTypes } from "../../ast/MacroNode.js"
15
+
16
+ /** Maximum HTML input size (1MB) to prevent ReDoS attacks */
17
+ const MAX_HTML_SIZE = 1024 * 1024
18
+
19
+ /**
20
+ * Branded type for preprocessed Confluence HTML.
21
+ *
22
+ * @category Types
23
+ */
24
+ export type PreprocessedHtml = string & Brand.Brand<"PreprocessedHtml">
25
+
26
+ /**
27
+ * Schema for preprocessed HTML brand.
28
+ *
29
+ * @category Schemas
30
+ */
31
+ export const PreprocessedHtmlSchema = Schema.String.pipe(
32
+ Schema.brand("PreprocessedHtml")
33
+ )
34
+
35
+ /**
36
+ * Transform raw Confluence HTML to preprocessed HTML.
37
+ *
38
+ * Applies the following transformations:
39
+ * - Layout section extraction with markers
40
+ * - Structured macro expansion (code, info panels, expand, TOC, status)
41
+ * - Task list normalization
42
+ * - Image attachment processing
43
+ * - Emoticon conversion
44
+ * - User mention extraction
45
+ * - ADF extension handling
46
+ * - Namespace stripping (ac:, ri: tags)
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * import { PreprocessedHtmlFromConfluence } from "@knpkv/confluence-to-markdown/schemas/preprocessing"
51
+ * import * as Schema from "effect/Schema"
52
+ * import { Effect } from "effect"
53
+ *
54
+ * const program = Effect.gen(function* () {
55
+ * const html = yield* Schema.decode(PreprocessedHtmlFromConfluence)(
56
+ * '<ac:structured-macro ac:name="info"><ac:rich-text-body>Content</ac:rich-text-body></ac:structured-macro>'
57
+ * )
58
+ * // html contains: <div data-macro="info">Content</div>
59
+ * })
60
+ * ```
61
+ *
62
+ * @category Schemas
63
+ */
64
+ const makePreprocessedHtml = Schema.decodeSync(PreprocessedHtmlSchema)
65
+
66
+ export const PreprocessedHtmlFromConfluence = Schema.transformOrFail(
67
+ Schema.String,
68
+ PreprocessedHtmlSchema,
69
+ {
70
+ strict: true,
71
+ decode: (html, _options, ast) =>
72
+ Effect.gen(function*() {
73
+ if (html.length > MAX_HTML_SIZE) {
74
+ return yield* Effect.fail(
75
+ new ParseResult.Type(
76
+ ast,
77
+ html,
78
+ `HTML input too large: ${html.length} bytes (max ${MAX_HTML_SIZE})`
79
+ )
80
+ )
81
+ }
82
+ return pipe(html, preprocessConfluenceHtml, makePreprocessedHtml)
83
+ }),
84
+ encode: (preprocessed) =>
85
+ // Identity - branded string is already a string
86
+ Effect.succeed(preprocessed)
87
+ }
88
+ )
89
+
90
+ /**
91
+ * Main preprocessing pipeline.
92
+ */
93
+ const preprocessConfluenceHtml = (html: string): string => {
94
+ let result = html
95
+
96
+ // 1. Process layouts FIRST - before any other preprocessing
97
+ result = preprocessLayouts(result)
98
+
99
+ // 2. Process structured macros iteratively
100
+ result = processStructuredMacros(result)
101
+
102
+ // 3. Process task lists BEFORE stripping ac: tags
103
+ result = preprocessTaskLists(result)
104
+
105
+ // 4. Process images with attachments
106
+ result = preprocessImages(result)
107
+
108
+ // 5. Process emoticons
109
+ result = preprocessEmoticons(result)
110
+
111
+ // 6. Process user mentions
112
+ result = preprocessUserMentions(result)
113
+
114
+ // 7. Process Confluence links with link-body
115
+ result = preprocessConfluenceLinks(result)
116
+
117
+ // 8. Process ADF extensions (decision lists)
118
+ result = preprocessAdfExtensions(result)
119
+
120
+ // 9. Strip remaining ac/ri namespace tags
121
+ result = stripNamespaces(result)
122
+
123
+ return result
124
+ }
125
+
126
+ /**
127
+ * Process Confluence layouts.
128
+ * Inserts markers for roundtrip preservation.
129
+ */
130
+ const preprocessLayouts = (html: string): string => {
131
+ return html.replace(
132
+ /<ac:layout>([\s\S]*?)<\/ac:layout>/gi,
133
+ (_, layoutContent) => {
134
+ let result = "<div data-cf-marker><!--cf:layout-start--></div>"
135
+ let sectionIndex = 0
136
+
137
+ const sectionRegex = /<ac:layout-section([^>]*)>([\s\S]*?)<\/ac:layout-section>/gi
138
+ let sectionMatch
139
+ while ((sectionMatch = sectionRegex.exec(layoutContent)) !== null) {
140
+ const sectionAttrs = sectionMatch[1] ?? ""
141
+ const sectionContent = sectionMatch[2] ?? ""
142
+
143
+ const typeMatch = sectionAttrs.match(/ac:type="([^"]*)"/)
144
+ const sectionType = typeMatch?.[1] ?? "fixed-width"
145
+
146
+ const breakoutModeMatch = sectionAttrs.match(/ac:breakout-mode="([^"]*)"/)
147
+ const breakoutWidthMatch = sectionAttrs.match(/ac:breakout-width="([^"]*)"/)
148
+ const breakoutMode = breakoutModeMatch?.[1] ?? ""
149
+ const breakoutWidth = breakoutWidthMatch?.[1] ?? ""
150
+
151
+ const cellContents: Array<string> = []
152
+ const cellRegex = /<ac:layout-cell[^>]*>([\s\S]*?)<\/ac:layout-cell>/gi
153
+ let cellMatch
154
+ while ((cellMatch = cellRegex.exec(sectionContent)) !== null) {
155
+ cellContents.push(cellMatch[1] ?? "")
156
+ }
157
+
158
+ result += `<div data-cf-marker><!--cf:section:${sectionIndex};${encodeURIComponent(sectionType)};${
159
+ encodeURIComponent(breakoutMode)
160
+ };${encodeURIComponent(breakoutWidth)};${cellContents.length}--></div>`
161
+
162
+ cellContents.forEach((cellContent, cellIndex) => {
163
+ result += `<div data-cf-marker><!--cf:cell:${sectionIndex};${cellIndex}--></div>${cellContent}`
164
+ })
165
+
166
+ result += `<div data-cf-marker><!--cf:section-end:${sectionIndex}--></div>`
167
+ sectionIndex++
168
+ }
169
+
170
+ result += "<div data-cf-marker><!--cf:layout-end--></div>"
171
+ return result
172
+ }
173
+ )
174
+ }
175
+
176
+ /**
177
+ * Process structured macros iteratively.
178
+ */
179
+ const processStructuredMacros = (html: string): string => {
180
+ let result = html
181
+ let iterations = 0
182
+ const maxIterations = 100
183
+
184
+ while (iterations < maxIterations) {
185
+ const macroStart = result.indexOf("<ac:structured-macro")
186
+ if (macroStart === -1) break
187
+
188
+ // First, find the end of the opening tag to check if self-closing
189
+ const openingTagEnd = result.indexOf(">", macroStart)
190
+ if (openingTagEnd === -1) break
191
+
192
+ // Check if self-closing (ends with />)
193
+ const isSelfClosing = result[openingTagEnd - 1] === "/"
194
+
195
+ let endPos: number
196
+ if (isSelfClosing) {
197
+ // Self-closing macro: <ac:structured-macro ... />
198
+ endPos = openingTagEnd + 1
199
+ } else {
200
+ // Regular macro with body: find matching closing tag
201
+ let depth = 1
202
+ let pos = openingTagEnd + 1
203
+ endPos = -1
204
+
205
+ while (pos < result.length && depth > 0) {
206
+ if (result.slice(pos, pos + 20) === "<ac:structured-macro") {
207
+ // Check if this nested opening is also self-closing
208
+ const nestedEnd = result.indexOf(">", pos)
209
+ if (nestedEnd !== -1 && result[nestedEnd - 1] === "/") {
210
+ // Self-closing nested macro - don't change depth
211
+ pos = nestedEnd + 1
212
+ } else {
213
+ depth++
214
+ pos += 20
215
+ }
216
+ } else if (result.slice(pos, pos + 21) === "</ac:structured-macro") {
217
+ depth--
218
+ if (depth === 0) {
219
+ endPos = result.indexOf(">", pos) + 1
220
+ }
221
+ pos += 21
222
+ } else {
223
+ pos++
224
+ }
225
+ }
226
+
227
+ if (endPos === -1) break
228
+ }
229
+
230
+ const macroContent = result.slice(macroStart, endPos)
231
+ const replacement = processSingleMacro(macroContent)
232
+ result = result.slice(0, macroStart) + replacement + result.slice(endPos)
233
+ iterations++
234
+ }
235
+
236
+ return result
237
+ }
238
+
239
+ /**
240
+ * Process a single Confluence macro to HTML.
241
+ */
242
+ const processSingleMacro = (macroContent: string): string => {
243
+ const nameMatch = macroContent.match(/ac:name="([^"]+)"/)
244
+ const macroName = nameMatch?.[1] ?? ""
245
+
246
+ // Plain-text body (for code macros)
247
+ const plainBodyStart = macroContent.indexOf("<ac:plain-text-body><![CDATA[")
248
+ const plainBodyEnd = macroContent.indexOf("]]></ac:plain-text-body>")
249
+ if (plainBodyStart !== -1 && plainBodyEnd !== -1) {
250
+ const content = macroContent.slice(plainBodyStart + 29, plainBodyEnd)
251
+ const langMatch = macroContent.match(/ac:name="code".*?<ac:parameter[^>]*ac:name="language"[^>]*>([^<]+)/)
252
+ const language = langMatch?.[1] ?? ""
253
+ return `<pre data-macro="code" data-language="${language}"><code>${escapeHtml(content)}</code></pre>`
254
+ }
255
+
256
+ // Rich-text body
257
+ const richBodyStart = macroContent.indexOf("<ac:rich-text-body>")
258
+ const richBodyEnd = macroContent.indexOf("</ac:rich-text-body>")
259
+ if (richBodyStart !== -1 && richBodyEnd !== -1) {
260
+ const content = macroContent.slice(richBodyStart + 19, richBodyEnd)
261
+
262
+ // Info/warning/note panels
263
+ if ((PanelTypes as ReadonlyArray<string>).includes(macroName)) {
264
+ const titleMatch = macroContent.match(/<ac:parameter[^>]*ac:name="title"[^>]*>([^<]+)/)
265
+ const title = titleMatch?.[1] ?? ""
266
+ return `<div data-macro="${macroName}" data-title="${escapeHtml(title)}">${content}</div>`
267
+ }
268
+
269
+ // Expand macro
270
+ if (macroName === "expand") {
271
+ const titleMatch = macroContent.match(/<ac:parameter[^>]*ac:name="title"[^>]*>([^<]+)/)
272
+ const title = titleMatch?.[1] ?? ""
273
+ return `<details data-macro="expand"><summary>${escapeHtml(title)}</summary>${content}</details>`
274
+ }
275
+
276
+ return content
277
+ }
278
+
279
+ // TOC macro
280
+ if (macroName === "toc") {
281
+ const minMatch = macroContent.match(/<ac:parameter[^>]*ac:name="minLevel"[^>]*>(\d+)/)
282
+ const maxMatch = macroContent.match(/<ac:parameter[^>]*ac:name="maxLevel"[^>]*>(\d+)/)
283
+ return `<nav data-macro="toc" data-min="${minMatch?.[1] ?? ""}" data-max="${maxMatch?.[1] ?? ""}"></nav>`
284
+ }
285
+
286
+ // Status macro
287
+ if (macroName === "status") {
288
+ const colorMatch = macroContent.match(/<ac:parameter[^>]*ac:name="colour"[^>]*>([^<]+)/)
289
+ const titleMatch = macroContent.match(/<ac:parameter[^>]*ac:name="title"[^>]*>([^<]+)/)
290
+ return `<span data-macro="status" data-color="${colorMatch?.[1] ?? ""}">${escapeHtml(titleMatch?.[1] ?? "")}</span>`
291
+ }
292
+
293
+ // Unknown macro - preserve as unsupported
294
+ return `<div data-unsupported-macro="${macroName}">${macroContent}</div>`
295
+ }
296
+
297
+ /**
298
+ * Preprocess task lists.
299
+ */
300
+ const preprocessTaskLists = (html: string): string => {
301
+ let result = html
302
+ const taskRegex = /<ac:task>([\s\S]*?)<\/ac:task>/gi
303
+ result = result.replace(taskRegex, (_, taskContent) => {
304
+ const idMatch = taskContent.match(/<ac:task-id>([^<]*)<\/ac:task-id>/)
305
+ const uuidMatch = taskContent.match(/<ac:task-uuid>([^<]*)<\/ac:task-uuid>/)
306
+ const statusMatch = taskContent.match(/<ac:task-status>([^<]*)<\/ac:task-status>/)
307
+ const bodyMatch = taskContent.match(/<ac:task-body>([\s\S]*?)<\/ac:task-body>/)
308
+
309
+ const id = idMatch?.[1] ?? ""
310
+ const uuid = uuidMatch?.[1] ?? ""
311
+ const status = statusMatch?.[1] ?? "incomplete"
312
+ const body = bodyMatch?.[1] ?? ""
313
+ const cleanBody = body.replace(/<[^>]+>/g, "").trim()
314
+ return `<li data-task-id="${id}" data-task-uuid="${uuid}" data-task-status="${status}">${cleanBody}</li>`
315
+ })
316
+ result = result.replace(/<ac:task-list[^>]*>/gi, "<ul data-macro=\"task-list\">")
317
+ result = result.replace(/<\/ac:task-list>/gi, "</ul>")
318
+ return result
319
+ }
320
+
321
+ /**
322
+ * Preprocess images with attachments.
323
+ */
324
+ const preprocessImages = (html: string): string => {
325
+ return html.replace(
326
+ /<ac:image([^>]*)>[\s\S]*?<ri:attachment([^>]*)\/>[\s\S]*?<\/ac:image>/gi,
327
+ (_, imageAttrs, attachmentAttrs) => {
328
+ const filename = attachmentAttrs.match(/ri:filename="([^"]*)"/)?.[1] ?? ""
329
+ const align = imageAttrs.match(/ac:align="([^"]*)"/)?.[1] ?? ""
330
+ const width = imageAttrs.match(/ac:width="([^"]*)"/)?.[1] ?? ""
331
+ const alt = imageAttrs.match(/ac:alt="([^"]*)"/)?.[1] ?? ""
332
+ const attrs = [
333
+ `data-attachment="${escapeHtml(filename)}"`,
334
+ align && `data-align="${align}"`,
335
+ width && `data-width="${width}"`,
336
+ alt && `alt="${escapeHtml(alt)}"`
337
+ ].filter(Boolean).join(" ")
338
+ return `<img ${attrs}>`
339
+ }
340
+ )
341
+ }
342
+
343
+ /**
344
+ * Preprocess emoticons.
345
+ */
346
+ const preprocessEmoticons = (html: string): string => {
347
+ return html.replace(
348
+ /<ac:emoticon([^>]*)\/?>/gi,
349
+ (_, attrs) => {
350
+ const shortname = attrs.match(/ac:emoji-shortname="([^"]*)"/)?.[1] ?? ""
351
+ const emojiId = attrs.match(/ac:emoji-id="([^"]*)"/)?.[1] ?? ""
352
+ const fallback = attrs.match(/ac:emoji-fallback="([^"]*)"/)?.[1] ?? ""
353
+ return `<span data-emoji="${escapeHtml(shortname)}" data-emoji-id="${emojiId}">${fallback}</span>`
354
+ }
355
+ )
356
+ }
357
+
358
+ /**
359
+ * Preprocess user mentions.
360
+ */
361
+ const preprocessUserMentions = (html: string): string => {
362
+ return html.replace(
363
+ /<ac:link>\s*<ri:user([^>]*)\/?>\s*<\/ac:link>/gi,
364
+ (_, attrs) => {
365
+ const accountId = attrs.match(/ri:account-id="([^"]*)"/)?.[1] ?? ""
366
+ return `<span data-user-mention="${escapeHtml(accountId)}"></span>`
367
+ }
368
+ )
369
+ }
370
+
371
+ /**
372
+ * Preprocess Confluence links with link-body.
373
+ * <ac:link><ac:link-body>Link text</ac:link-body></ac:link>
374
+ * -> <span data-confluence-link>Link text</span>
375
+ */
376
+ const preprocessConfluenceLinks = (html: string): string => {
377
+ return html.replace(
378
+ /<ac:link>\s*<ac:link-body>([\s\S]*?)<\/ac:link-body>\s*<\/ac:link>/gi,
379
+ (_, linkText) => {
380
+ return `<span data-confluence-link>${linkText}</span>`
381
+ }
382
+ )
383
+ }
384
+
385
+ /**
386
+ * Preprocess ADF extensions (decision lists).
387
+ */
388
+ const preprocessAdfExtensions = (html: string): string => {
389
+ return html.replace(
390
+ /<ac:adf-extension>([\s\S]*?)<\/ac:adf-extension>/gi,
391
+ (_, content) => {
392
+ if (content.includes("type=\"decision-list\"")) {
393
+ const items: Array<{ localId: string; state: string; content: string }> = []
394
+ const itemRegex = /<ac:adf-node\s+type="decision-item">([\s\S]*?)<\/ac:adf-node>/gi
395
+ let itemMatch
396
+ while ((itemMatch = itemRegex.exec(content)) !== null) {
397
+ const itemContent = itemMatch[1] ?? ""
398
+ const localIdMatch = itemContent.match(/<ac:adf-attribute\s+key="local-id">([^<]*)<\/ac:adf-attribute>/)
399
+ const stateMatch = itemContent.match(/<ac:adf-attribute\s+key="state">([^<]*)<\/ac:adf-attribute>/)
400
+ const textMatch = itemContent.match(/<ac:adf-content>([^<]*)<\/ac:adf-content>/)
401
+ items.push({
402
+ localId: localIdMatch?.[1] ?? "",
403
+ state: stateMatch?.[1] ?? "UNDECIDED",
404
+ content: textMatch?.[1] ?? ""
405
+ })
406
+ }
407
+ if (items.length > 0) {
408
+ const encoded = items.map((item) =>
409
+ `${encodeURIComponent(item.localId)};${encodeURIComponent(item.state)};${encodeURIComponent(item.content)}`
410
+ ).join("|")
411
+ return `<!--cf:decision:${encoded}-->`
412
+ }
413
+ }
414
+ // Check if it's an ADF panel (note, info, warning, etc.)
415
+ if (content.includes("type=\"panel\"")) {
416
+ const panelTypeMatch = content.match(/<ac:adf-attribute\s+key="panel-type">([^<]*)<\/ac:adf-attribute>/)
417
+ const panelType = panelTypeMatch?.[1] ?? "info"
418
+ const contentMatch = content.match(/<ac:adf-content>([\s\S]*?)<\/ac:adf-content>/)
419
+ const innerContent = contentMatch?.[1] ?? ""
420
+ return `<div data-macro="${panelType}" data-title="">${innerContent}</div>`
421
+ }
422
+ const fallbackMatch = content.match(/<ac:adf-fallback>([\s\S]*?)<\/ac:adf-fallback>/)
423
+ if (fallbackMatch) {
424
+ return fallbackMatch[1] ?? ""
425
+ }
426
+ return ""
427
+ }
428
+ )
429
+ }
430
+
431
+ /**
432
+ * Strip remaining ac/ri namespace tags.
433
+ */
434
+ const stripNamespaces = (html: string): string => {
435
+ return html
436
+ .replace(/<ac:parameter[^>]{0,1000}>[^<]{0,10000}<\/ac:parameter>/gi, "")
437
+ .replace(/<\/?ac:[a-z-]{1,50}[^>]{0,1000}>/gi, "")
438
+ .replace(/<\/?ri:[a-z-]{1,50}[^>]{0,1000}\/?>/gi, "")
439
+ }
440
+
441
+ const escapeHtml = (str: string): string =>
442
+ str
443
+ .replace(/&/g, "&amp;")
444
+ .replace(/</g, "&lt;")
445
+ .replace(/>/g, "&gt;")
446
+ .replace(/"/g, "&quot;")
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Confluence HTML preprocessing schemas.
3
+ *
4
+ * @module
5
+ */
6
+ export type { PreprocessedHtml } from "./ConfluencePreprocessor.js"
7
+
8
+ export { PreprocessedHtmlFromConfluence, PreprocessedHtmlSchema } from "./ConfluencePreprocessor.js"