@knpkv/confluence-to-markdown 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +58 -14
- package/dist/AdfPlaceholders.d.ts +42 -0
- package/dist/AdfPlaceholders.d.ts.map +1 -0
- package/dist/AdfPlaceholders.js +547 -0
- package/dist/AdfPlaceholders.js.map +1 -0
- package/dist/AdfSchemaValidator.d.ts +37 -0
- package/dist/AdfSchemaValidator.d.ts.map +1 -0
- package/dist/AdfSchemaValidator.js +37 -0
- package/dist/AdfSchemaValidator.js.map +1 -0
- package/dist/AdfWalker.d.ts +39 -0
- package/dist/AdfWalker.d.ts.map +1 -0
- package/dist/AdfWalker.js +527 -0
- package/dist/AdfWalker.js.map +1 -0
- package/dist/AtlaskitTransformers.d.ts +35 -0
- package/dist/AtlaskitTransformers.d.ts.map +1 -0
- package/dist/AtlaskitTransformers.js +48 -0
- package/dist/AtlaskitTransformers.js.map +1 -0
- package/dist/Brand.d.ts +6 -6
- package/dist/Brand.d.ts.map +1 -1
- package/dist/Brand.js +8 -6
- package/dist/Brand.js.map +1 -1
- package/dist/ConfluenceAuth.d.ts +4 -4
- package/dist/ConfluenceAuth.d.ts.map +1 -1
- package/dist/ConfluenceAuth.js +15 -27
- package/dist/ConfluenceAuth.js.map +1 -1
- package/dist/ConfluenceClient.d.ts +4 -4
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +21 -14
- package/dist/ConfluenceClient.js.map +1 -1
- package/dist/ConfluenceConfig.d.ts +3 -3
- package/dist/ConfluenceConfig.d.ts.map +1 -1
- package/dist/ConfluenceConfig.js +13 -11
- package/dist/ConfluenceConfig.js.map +1 -1
- package/dist/ConfluenceError.d.ts +56 -4
- package/dist/ConfluenceError.d.ts.map +1 -1
- package/dist/ConfluenceError.js +30 -1
- package/dist/ConfluenceError.js.map +1 -1
- package/dist/GitService.d.ts +11 -3
- package/dist/GitService.d.ts.map +1 -1
- package/dist/GitService.js +19 -27
- package/dist/GitService.js.map +1 -1
- package/dist/LocalFileSystem.d.ts +3 -3
- package/dist/LocalFileSystem.d.ts.map +1 -1
- package/dist/LocalFileSystem.js +6 -6
- package/dist/LocalFileSystem.js.map +1 -1
- package/dist/MarkdownConverter.d.ts +16 -65
- package/dist/MarkdownConverter.d.ts.map +1 -1
- package/dist/MarkdownConverter.js +64 -85
- package/dist/MarkdownConverter.js.map +1 -1
- package/dist/Schemas.d.ts +128 -141
- package/dist/Schemas.d.ts.map +1 -1
- package/dist/Schemas.js +21 -23
- package/dist/Schemas.js.map +1 -1
- package/dist/SyncEngine.d.ts +8 -5
- package/dist/SyncEngine.d.ts.map +1 -1
- package/dist/SyncEngine.js +189 -113
- package/dist/SyncEngine.js.map +1 -1
- package/dist/bin.js +23 -35
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +2 -14
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +11 -16
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/clone.d.ts +4 -6
- package/dist/commands/clone.d.ts.map +1 -1
- package/dist/commands/clone.js +34 -32
- package/dist/commands/clone.js.map +1 -1
- package/dist/commands/delete.d.ts +2 -10
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +5 -4
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/errorHandler.d.ts +2 -1
- package/dist/commands/errorHandler.d.ts.map +1 -1
- package/dist/commands/errorHandler.js +22 -15
- package/dist/commands/errorHandler.js.map +1 -1
- package/dist/commands/fetch.d.ts +27 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +48 -0
- package/dist/commands/fetch.js.map +1 -0
- package/dist/commands/git.d.ts +7 -10
- package/dist/commands/git.d.ts.map +1 -1
- package/dist/commands/git.js +6 -6
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/layers.d.ts +10 -9
- package/dist/commands/layers.d.ts.map +1 -1
- package/dist/commands/layers.js +41 -30
- package/dist/commands/layers.js.map +1 -1
- package/dist/commands/new.d.ts +2 -6
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +5 -4
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/pageInput.d.ts +19 -0
- package/dist/commands/pageInput.d.ts.map +1 -0
- package/dist/commands/pageInput.js +68 -0
- package/dist/commands/pageInput.js.map +1 -0
- package/dist/commands/root.d.ts +8 -0
- package/dist/commands/root.d.ts.map +1 -0
- package/dist/commands/root.js +29 -0
- package/dist/commands/root.js.map +1 -0
- package/dist/commands/sync.d.ts +6 -9
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -6
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.d.ts +3 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -13
- package/dist/index.js.map +1 -1
- package/dist/internal/NodeLayers.d.ts.map +1 -1
- package/dist/internal/NodeLayers.js +1 -2
- package/dist/internal/NodeLayers.js.map +1 -1
- package/dist/internal/adfMetadata.d.ts +30 -0
- package/dist/internal/adfMetadata.d.ts.map +1 -0
- package/dist/internal/adfMetadata.js +126 -0
- package/dist/internal/adfMetadata.js.map +1 -0
- package/dist/internal/cleanMarkdown.d.ts +5 -0
- package/dist/internal/cleanMarkdown.d.ts.map +1 -0
- package/dist/internal/cleanMarkdown.js +13 -0
- package/dist/internal/cleanMarkdown.js.map +1 -0
- package/dist/internal/frontmatter.d.ts.map +1 -1
- package/dist/internal/frontmatter.js +41 -8
- package/dist/internal/frontmatter.js.map +1 -1
- package/dist/internal/gitCommands.d.ts +9 -3
- package/dist/internal/gitCommands.d.ts.map +1 -1
- package/dist/internal/gitCommands.js +18 -9
- package/dist/internal/gitCommands.js.map +1 -1
- package/dist/internal/hashUtils.d.ts +1 -1
- package/dist/internal/hashUtils.d.ts.map +1 -1
- package/dist/internal/hashUtils.js +1 -1
- package/dist/internal/hashUtils.js.map +1 -1
- package/dist/internal/oauthServer.d.ts +10 -5
- package/dist/internal/oauthServer.d.ts.map +1 -1
- package/dist/internal/oauthServer.js +19 -40
- package/dist/internal/oauthServer.js.map +1 -1
- package/dist/internal/pathUtils.d.ts +1 -1
- package/dist/internal/pathUtils.d.ts.map +1 -1
- package/dist/internal/pathUtils.js +1 -1
- package/dist/internal/pathUtils.js.map +1 -1
- package/dist/internal/process.d.ts +15 -0
- package/dist/internal/process.d.ts.map +1 -0
- package/dist/internal/process.js +10 -0
- package/dist/internal/process.js.map +1 -0
- package/dist/internal/stdio.d.ts +6 -0
- package/dist/internal/stdio.d.ts.map +1 -0
- package/dist/internal/stdio.js +15 -0
- package/dist/internal/stdio.js.map +1 -0
- package/dist/internal/tokenStorage.d.ts +3 -13
- package/dist/internal/tokenStorage.d.ts.map +1 -1
- package/dist/internal/tokenStorage.js +26 -24
- package/dist/internal/tokenStorage.js.map +1 -1
- package/dist/internal/userCache.d.ts +1 -1
- package/dist/internal/userCache.d.ts.map +1 -1
- package/dist/internal/userCache.js +1 -1
- package/dist/internal/userCache.js.map +1 -1
- package/package.json +29 -20
- package/skills/confluence/SKILL.md +143 -0
- package/skills/confluence/agents/openai.yaml +4 -0
- package/src/AdfPlaceholders.ts +563 -0
- package/src/AdfSchemaValidator.ts +65 -0
- package/src/AdfWalker.ts +591 -0
- package/src/AtlaskitTransformers.ts +70 -0
- package/src/Brand.ts +11 -16
- package/src/ConfluenceAuth.ts +22 -30
- package/src/ConfluenceClient.ts +28 -24
- package/src/ConfluenceConfig.ts +14 -14
- package/src/ConfluenceError.ts +65 -3
- package/src/GitService.ts +39 -49
- package/src/LocalFileSystem.ts +7 -9
- package/src/MarkdownConverter.ts +108 -143
- package/src/Schemas.ts +17 -16
- package/src/SyncEngine.ts +272 -127
- package/src/atlaskit-adf-schema.d.ts +3 -0
- package/src/bin.ts +30 -56
- package/src/commands/auth.ts +21 -18
- package/src/commands/clone.ts +46 -38
- package/src/commands/delete.ts +5 -4
- package/src/commands/errorHandler.ts +25 -18
- package/src/commands/fetch.ts +90 -0
- package/src/commands/git.ts +6 -6
- package/src/commands/index.ts +1 -0
- package/src/commands/layers.ts +64 -37
- package/src/commands/new.ts +5 -4
- package/src/commands/pageInput.ts +103 -0
- package/src/commands/root.ts +59 -0
- package/src/commands/sync.ts +7 -6
- package/src/index.ts +3 -18
- package/src/internal/NodeLayers.ts +1 -2
- package/src/internal/adfMetadata.ts +145 -0
- package/src/internal/cleanMarkdown.ts +15 -0
- package/src/internal/frontmatter.ts +45 -8
- package/src/internal/gitCommands.ts +23 -17
- package/src/internal/hashUtils.ts +2 -2
- package/src/internal/oauthServer.ts +84 -105
- package/src/internal/pathUtils.ts +1 -1
- package/src/internal/process.ts +15 -0
- package/src/internal/stdio.ts +22 -0
- package/src/internal/tokenStorage.ts +39 -29
- package/src/internal/userCache.ts +2 -2
- package/test/AdfPlaceholders.test.ts +508 -0
- package/test/AdfSchemaValidator.test.ts +34 -0
- package/test/AdfWalker.test.ts +676 -0
- package/test/AtlaskitTransformers.test.ts +25 -0
- package/test/Brand.test.ts +11 -11
- package/test/GitService.test.ts +6 -2
- package/test/MarkdownConverter.test.ts +121 -105
- package/test/RoundTrip.test.ts +521 -0
- package/test/Schemas.test.ts +40 -40
- package/test/adfMetadata.test.ts +110 -0
- package/test/cleanMarkdown.test.ts +36 -0
- package/test/commandHarness.test.ts +79 -0
- package/test/commandHarness.ts +147 -0
- package/test/fetch.test.ts +61 -0
- package/test/frontmatter.test.ts +41 -0
- package/test/integration.test.ts +569 -156
- package/test/layers.test.ts +12 -0
- package/test/oauthServer.test.ts +4 -5
- package/test/pageInput.test.ts +83 -0
- package/test/tokenStorage.test.ts +17 -17
- package/dist/SchemaConverterError.d.ts +0 -108
- package/dist/SchemaConverterError.d.ts.map +0 -1
- package/dist/SchemaConverterError.js +0 -84
- package/dist/SchemaConverterError.js.map +0 -1
- package/dist/ast/BlockNode.d.ts +0 -453
- package/dist/ast/BlockNode.d.ts.map +0 -1
- package/dist/ast/BlockNode.js +0 -310
- package/dist/ast/BlockNode.js.map +0 -1
- package/dist/ast/Document.d.ts +0 -216
- package/dist/ast/Document.d.ts.map +0 -1
- package/dist/ast/Document.js +0 -69
- package/dist/ast/Document.js.map +0 -1
- package/dist/ast/InlineNode.d.ts +0 -477
- package/dist/ast/InlineNode.d.ts.map +0 -1
- package/dist/ast/InlineNode.js +0 -263
- package/dist/ast/InlineNode.js.map +0 -1
- package/dist/ast/MacroNode.d.ts +0 -267
- package/dist/ast/MacroNode.d.ts.map +0 -1
- package/dist/ast/MacroNode.js +0 -164
- package/dist/ast/MacroNode.js.map +0 -1
- package/dist/ast/index.d.ts +0 -10
- package/dist/ast/index.d.ts.map +0 -1
- package/dist/ast/index.js +0 -14
- package/dist/ast/index.js.map +0 -1
- package/dist/parsers/ConfluenceParser.d.ts +0 -26
- package/dist/parsers/ConfluenceParser.d.ts.map +0 -1
- package/dist/parsers/ConfluenceParser.js +0 -797
- package/dist/parsers/ConfluenceParser.js.map +0 -1
- package/dist/parsers/MarkdownParser.d.ts +0 -26
- package/dist/parsers/MarkdownParser.d.ts.map +0 -1
- package/dist/parsers/MarkdownParser.js +0 -982
- package/dist/parsers/MarkdownParser.js.map +0 -1
- package/dist/parsers/index.d.ts +0 -8
- package/dist/parsers/index.d.ts.map +0 -1
- package/dist/parsers/index.js +0 -8
- package/dist/parsers/index.js.map +0 -1
- package/dist/schemas/ConfluenceSchema.d.ts +0 -21
- package/dist/schemas/ConfluenceSchema.d.ts.map +0 -1
- package/dist/schemas/ConfluenceSchema.js +0 -38
- package/dist/schemas/ConfluenceSchema.js.map +0 -1
- package/dist/schemas/ConversionSchema.d.ts +0 -35
- package/dist/schemas/ConversionSchema.d.ts.map +0 -1
- package/dist/schemas/ConversionSchema.js +0 -208
- package/dist/schemas/ConversionSchema.js.map +0 -1
- package/dist/schemas/MarkdownSchema.d.ts +0 -21
- package/dist/schemas/MarkdownSchema.d.ts.map +0 -1
- package/dist/schemas/MarkdownSchema.js +0 -38
- package/dist/schemas/MarkdownSchema.js.map +0 -1
- package/dist/schemas/hast/HastFromHtml.d.ts +0 -27
- package/dist/schemas/hast/HastFromHtml.d.ts.map +0 -1
- package/dist/schemas/hast/HastFromHtml.js +0 -107
- package/dist/schemas/hast/HastFromHtml.js.map +0 -1
- package/dist/schemas/hast/HastSchema.d.ts +0 -195
- package/dist/schemas/hast/HastSchema.d.ts.map +0 -1
- package/dist/schemas/hast/HastSchema.js +0 -183
- package/dist/schemas/hast/HastSchema.js.map +0 -1
- package/dist/schemas/hast/index.d.ts +0 -9
- package/dist/schemas/hast/index.d.ts.map +0 -1
- package/dist/schemas/hast/index.js +0 -3
- package/dist/schemas/hast/index.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -14
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -16
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts +0 -30
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +0 -1
- package/dist/schemas/mdast/MdastFromMarkdown.js +0 -79
- package/dist/schemas/mdast/MdastFromMarkdown.js.map +0 -1
- package/dist/schemas/mdast/MdastSchema.d.ts +0 -385
- package/dist/schemas/mdast/MdastSchema.d.ts.map +0 -1
- package/dist/schemas/mdast/MdastSchema.js +0 -266
- package/dist/schemas/mdast/MdastSchema.js.map +0 -1
- package/dist/schemas/mdast/index.d.ts +0 -10
- package/dist/schemas/mdast/index.d.ts.map +0 -1
- package/dist/schemas/mdast/index.js +0 -4
- package/dist/schemas/mdast/index.js.map +0 -1
- package/dist/schemas/mdast/mdastToString.d.ts +0 -13
- package/dist/schemas/mdast/mdastToString.d.ts.map +0 -1
- package/dist/schemas/mdast/mdastToString.js +0 -85
- package/dist/schemas/mdast/mdastToString.js.map +0 -1
- package/dist/schemas/nodes/block/BlockSchema.d.ts +0 -43
- package/dist/schemas/nodes/block/BlockSchema.d.ts.map +0 -1
- package/dist/schemas/nodes/block/BlockSchema.js +0 -634
- package/dist/schemas/nodes/block/BlockSchema.js.map +0 -1
- package/dist/schemas/nodes/block/index.d.ts +0 -7
- package/dist/schemas/nodes/block/index.d.ts.map +0 -1
- package/dist/schemas/nodes/block/index.js +0 -7
- package/dist/schemas/nodes/block/index.js.map +0 -1
- package/dist/schemas/nodes/index.d.ts +0 -9
- package/dist/schemas/nodes/index.d.ts.map +0 -1
- package/dist/schemas/nodes/index.js +0 -12
- package/dist/schemas/nodes/index.js.map +0 -1
- package/dist/schemas/nodes/inline/InlineSchema.d.ts +0 -48
- package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +0 -1
- package/dist/schemas/nodes/inline/InlineSchema.js +0 -436
- package/dist/schemas/nodes/inline/InlineSchema.js.map +0 -1
- package/dist/schemas/nodes/inline/index.d.ts +0 -7
- package/dist/schemas/nodes/inline/index.d.ts.map +0 -1
- package/dist/schemas/nodes/inline/index.js +0 -7
- package/dist/schemas/nodes/inline/index.js.map +0 -1
- package/dist/schemas/nodes/macro/MacroSchema.d.ts +0 -27
- package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +0 -1
- package/dist/schemas/nodes/macro/MacroSchema.js +0 -162
- package/dist/schemas/nodes/macro/MacroSchema.js.map +0 -1
- package/dist/schemas/nodes/macro/index.d.ts +0 -7
- package/dist/schemas/nodes/macro/index.d.ts.map +0 -1
- package/dist/schemas/nodes/macro/index.js +0 -7
- package/dist/schemas/nodes/macro/index.js.map +0 -1
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +0 -24
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +0 -1
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js +0 -359
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +0 -1
- package/dist/schemas/preprocessing/index.d.ts +0 -8
- package/dist/schemas/preprocessing/index.d.ts.map +0 -1
- package/dist/schemas/preprocessing/index.js +0 -2
- package/dist/schemas/preprocessing/index.js.map +0 -1
- package/dist/serializers/ConfluenceSerializer.d.ts +0 -30
- package/dist/serializers/ConfluenceSerializer.d.ts.map +0 -1
- package/dist/serializers/ConfluenceSerializer.js +0 -560
- package/dist/serializers/ConfluenceSerializer.js.map +0 -1
- package/dist/serializers/MarkdownSerializer.d.ts +0 -34
- package/dist/serializers/MarkdownSerializer.d.ts.map +0 -1
- package/dist/serializers/MarkdownSerializer.js +0 -395
- package/dist/serializers/MarkdownSerializer.js.map +0 -1
- package/dist/serializers/index.d.ts +0 -8
- package/dist/serializers/index.d.ts.map +0 -1
- package/dist/serializers/index.js +0 -8
- package/dist/serializers/index.js.map +0 -1
- package/src/SchemaConverterError.ts +0 -108
- package/src/ast/BlockNode.ts +0 -469
- package/src/ast/Document.ts +0 -90
- package/src/ast/InlineNode.ts +0 -323
- package/src/ast/MacroNode.ts +0 -245
- package/src/ast/index.ts +0 -83
- package/src/parsers/ConfluenceParser.ts +0 -956
- package/src/parsers/MarkdownParser.ts +0 -1338
- package/src/parsers/index.ts +0 -8
- package/src/schemas/ConfluenceSchema.ts +0 -56
- package/src/schemas/ConversionSchema.ts +0 -318
- package/src/schemas/MarkdownSchema.ts +0 -56
- package/src/schemas/hast/HastFromHtml.ts +0 -153
- package/src/schemas/hast/HastSchema.ts +0 -274
- package/src/schemas/hast/index.ts +0 -35
- package/src/schemas/index.ts +0 -20
- package/src/schemas/mdast/MdastFromMarkdown.ts +0 -118
- package/src/schemas/mdast/MdastSchema.ts +0 -566
- package/src/schemas/mdast/index.ts +0 -59
- package/src/schemas/mdast/mdastToString.ts +0 -102
- package/src/schemas/nodes/block/BlockSchema.ts +0 -773
- package/src/schemas/nodes/block/index.ts +0 -13
- package/src/schemas/nodes/index.ts +0 -20
- package/src/schemas/nodes/inline/InlineSchema.ts +0 -523
- package/src/schemas/nodes/inline/index.ts +0 -14
- package/src/schemas/nodes/macro/MacroSchema.ts +0 -226
- package/src/schemas/nodes/macro/index.ts +0 -6
- package/src/schemas/preprocessing/ConfluencePreprocessor.ts +0 -455
- package/src/schemas/preprocessing/index.ts +0 -8
- package/src/serializers/ConfluenceSerializer.ts +0 -737
- package/src/serializers/MarkdownSerializer.ts +0 -543
- package/src/serializers/index.ts +0 -8
- package/test/ast/BlockNode.test.ts +0 -265
- package/test/ast/Document.test.ts +0 -126
- package/test/ast/InlineNode.test.ts +0 -161
- package/test/fixtures/integration-test.html.fixture +0 -103
- package/test/fixtures/integration-test.md.expected +0 -257
- package/test/parsers/ConfluenceParser.test.ts +0 -452
- package/test/schemas/ConfluencePreprocessor.test.ts +0 -180
- package/test/schemas/ConversionSchema.test.ts +0 -159
- package/test/schemas/HastSchema.test.ts +0 -138
- package/test/schemas/MdastSchema.test.ts +0 -145
- package/test/schemas/nodes/block/BlockSchema.test.ts +0 -173
- package/test/schemas/nodes/inline/InlineSchema.test.ts +0 -198
- package/test/schemas/nodes/macro/MacroSchema.test.ts +0 -142
|
@@ -1,452 +0,0 @@
|
|
|
1
|
-
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"
|
|
2
|
-
import * as NodePath from "@effect/platform-node/NodePath"
|
|
3
|
-
import * as FileSystem from "@effect/platform/FileSystem"
|
|
4
|
-
import * as Path from "@effect/platform/Path"
|
|
5
|
-
import { describe, expect, it } from "@effect/vitest"
|
|
6
|
-
import * as Effect from "effect/Effect"
|
|
7
|
-
import * as Layer from "effect/Layer"
|
|
8
|
-
import { parseConfluenceHtml } from "../../src/parsers/ConfluenceParser.js"
|
|
9
|
-
import { parseMarkdown } from "../../src/parsers/MarkdownParser.js"
|
|
10
|
-
import { serializeToConfluence } from "../../src/serializers/ConfluenceSerializer.js"
|
|
11
|
-
import { serializeToMarkdown } from "../../src/serializers/MarkdownSerializer.js"
|
|
12
|
-
|
|
13
|
-
const PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)
|
|
14
|
-
|
|
15
|
-
const getFixturesDir = Effect.gen(function*() {
|
|
16
|
-
const path = yield* Path.Path
|
|
17
|
-
return path.join(import.meta.dirname, "../fixtures")
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const readFixture = (filename: string) =>
|
|
21
|
-
Effect.gen(function*() {
|
|
22
|
-
const fs = yield* FileSystem.FileSystem
|
|
23
|
-
const path = yield* Path.Path
|
|
24
|
-
const fixturesDir = yield* getFixturesDir
|
|
25
|
-
return yield* fs.readFileString(path.join(fixturesDir, filename))
|
|
26
|
-
}).pipe(Effect.provide(PlatformLive))
|
|
27
|
-
|
|
28
|
-
describe("ConfluenceParser", () => {
|
|
29
|
-
describe("parseConfluenceHtml", () => {
|
|
30
|
-
it.effect("parses basic headings", () =>
|
|
31
|
-
Effect.gen(function*() {
|
|
32
|
-
const html = "<h1>Title</h1><h2>Subtitle</h2>"
|
|
33
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
34
|
-
expect(doc.children.length).toBe(2)
|
|
35
|
-
expect(doc.children[0]?._tag).toBe("Heading")
|
|
36
|
-
}))
|
|
37
|
-
|
|
38
|
-
it.effect("parses paragraphs", () =>
|
|
39
|
-
Effect.gen(function*() {
|
|
40
|
-
const html = "<p>Hello world</p>"
|
|
41
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
42
|
-
expect(doc.children.length).toBe(1)
|
|
43
|
-
expect(doc.children[0]?._tag).toBe("Paragraph")
|
|
44
|
-
}))
|
|
45
|
-
|
|
46
|
-
it.effect("parses task lists", () =>
|
|
47
|
-
Effect.gen(function*() {
|
|
48
|
-
const html = `<ac:task-list ac:task-list-id="test">
|
|
49
|
-
<ac:task>
|
|
50
|
-
<ac:task-id>1</ac:task-id>
|
|
51
|
-
<ac:task-uuid>uuid-1</ac:task-uuid>
|
|
52
|
-
<ac:task-status>incomplete</ac:task-status>
|
|
53
|
-
<ac:task-body><span>Task 1</span></ac:task-body>
|
|
54
|
-
</ac:task>
|
|
55
|
-
<ac:task>
|
|
56
|
-
<ac:task-id>2</ac:task-id>
|
|
57
|
-
<ac:task-uuid>uuid-2</ac:task-uuid>
|
|
58
|
-
<ac:task-status>complete</ac:task-status>
|
|
59
|
-
<ac:task-body><span>Task 2</span></ac:task-body>
|
|
60
|
-
</ac:task>
|
|
61
|
-
</ac:task-list>`
|
|
62
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
63
|
-
expect(doc.children.length).toBe(1)
|
|
64
|
-
const taskList = doc.children[0]
|
|
65
|
-
expect(taskList?._tag).toBe("TaskList")
|
|
66
|
-
if (taskList?._tag === "TaskList") {
|
|
67
|
-
expect(taskList.children.length).toBe(2)
|
|
68
|
-
expect(taskList.children[0]?.status).toBe("incomplete")
|
|
69
|
-
expect(taskList.children[0]?.body[0]?._tag).toBe("Text")
|
|
70
|
-
expect(taskList.children[1]?.status).toBe("complete")
|
|
71
|
-
}
|
|
72
|
-
}))
|
|
73
|
-
|
|
74
|
-
it.effect("parses images with attachments", () =>
|
|
75
|
-
Effect.gen(function*() {
|
|
76
|
-
const html = `<ac:image ac:align="center" ac:width="250" ac:alt="logo">
|
|
77
|
-
<ri:attachment ri:filename="logo.svg"/>
|
|
78
|
-
</ac:image>`
|
|
79
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
80
|
-
expect(doc.children.length).toBe(1)
|
|
81
|
-
const image = doc.children[0]
|
|
82
|
-
expect(image?._tag).toBe("Image")
|
|
83
|
-
if (image?._tag === "Image") {
|
|
84
|
-
expect(image.attachment?.filename).toBe("logo.svg")
|
|
85
|
-
expect(image.align).toBe("center")
|
|
86
|
-
expect(image.width).toBe(250)
|
|
87
|
-
expect(image.alt).toBe("logo")
|
|
88
|
-
}
|
|
89
|
-
}))
|
|
90
|
-
|
|
91
|
-
it.effect("parses emoticons", () =>
|
|
92
|
-
Effect.gen(function*() {
|
|
93
|
-
const html =
|
|
94
|
-
`<p><ac:emoticon ac:emoji-shortname=":grinning:" ac:emoji-id="1f600" ac:emoji-fallback="smile"/></p>`
|
|
95
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
96
|
-
expect(doc.children.length).toBe(1)
|
|
97
|
-
const para = doc.children[0]
|
|
98
|
-
expect(para?._tag).toBe("Paragraph")
|
|
99
|
-
if (para?._tag === "Paragraph") {
|
|
100
|
-
const emoticon = para.children[0]
|
|
101
|
-
expect(emoticon?._tag).toBe("Emoticon")
|
|
102
|
-
if (emoticon?._tag === "Emoticon") {
|
|
103
|
-
expect(emoticon.shortname).toBe(":grinning:")
|
|
104
|
-
expect(emoticon.emojiId).toBe("1f600")
|
|
105
|
-
expect(emoticon.fallback).toBe("smile")
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}))
|
|
109
|
-
|
|
110
|
-
it.effect("parses user mentions", () =>
|
|
111
|
-
Effect.gen(function*() {
|
|
112
|
-
const html = `<p><ac:link><ri:user ri:account-id="557058:abc123"/></ac:link></p>`
|
|
113
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
114
|
-
expect(doc.children.length).toBe(1)
|
|
115
|
-
const para = doc.children[0]
|
|
116
|
-
if (para?._tag === "Paragraph") {
|
|
117
|
-
const mention = para.children[0]
|
|
118
|
-
expect(mention?._tag).toBe("UserMention")
|
|
119
|
-
if (mention?._tag === "UserMention") {
|
|
120
|
-
expect(mention.accountId).toBe("557058:abc123")
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}))
|
|
124
|
-
|
|
125
|
-
it.effect("parses colored text", () =>
|
|
126
|
-
Effect.gen(function*() {
|
|
127
|
-
const html = `<p><span style="color: rgb(255,0,0);">Red text</span></p>`
|
|
128
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
129
|
-
const para = doc.children[0]
|
|
130
|
-
if (para?._tag === "Paragraph") {
|
|
131
|
-
const colored = para.children[0]
|
|
132
|
-
expect(colored?._tag).toBe("ColoredText")
|
|
133
|
-
if (colored?._tag === "ColoredText") {
|
|
134
|
-
expect(colored.color).toBe("rgb(255,0,0)")
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}))
|
|
138
|
-
|
|
139
|
-
it.effect("parses highlighted text", () =>
|
|
140
|
-
Effect.gen(function*() {
|
|
141
|
-
const html = `<p><span style="background-color: rgb(255,255,0);">Highlighted</span></p>`
|
|
142
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
143
|
-
const para = doc.children[0]
|
|
144
|
-
if (para?._tag === "Paragraph") {
|
|
145
|
-
const highlight = para.children[0]
|
|
146
|
-
expect(highlight?._tag).toBe("Highlight")
|
|
147
|
-
if (highlight?._tag === "Highlight") {
|
|
148
|
-
expect(highlight.backgroundColor).toBe("rgb(255,255,0)")
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}))
|
|
152
|
-
|
|
153
|
-
it.effect("parses paragraph with alignment", () =>
|
|
154
|
-
Effect.gen(function*() {
|
|
155
|
-
const html = `<p style="text-align: center;">Centered</p>`
|
|
156
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
157
|
-
const para = doc.children[0]
|
|
158
|
-
expect(para?._tag).toBe("Paragraph")
|
|
159
|
-
if (para?._tag === "Paragraph") {
|
|
160
|
-
expect(para.alignment).toBe("center")
|
|
161
|
-
}
|
|
162
|
-
}))
|
|
163
|
-
|
|
164
|
-
it.effect("parses paragraph with indent", () =>
|
|
165
|
-
Effect.gen(function*() {
|
|
166
|
-
const html = `<p style="margin-left: 30px;">Indented</p>`
|
|
167
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
168
|
-
const para = doc.children[0]
|
|
169
|
-
expect(para?._tag === "Paragraph").toBe(true)
|
|
170
|
-
if (para?._tag === "Paragraph") {
|
|
171
|
-
expect(para.indent).toBe(30)
|
|
172
|
-
}
|
|
173
|
-
}))
|
|
174
|
-
|
|
175
|
-
it.effect("parses underline, subscript, superscript", () =>
|
|
176
|
-
Effect.gen(function*() {
|
|
177
|
-
const html = `<p><u>Underline</u> <sub>Sub</sub> <sup>Sup</sup></p>`
|
|
178
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
179
|
-
const para = doc.children[0]
|
|
180
|
-
if (para?._tag === "Paragraph") {
|
|
181
|
-
const [u, , sub, , sup] = para.children
|
|
182
|
-
expect(u?._tag).toBe("Underline")
|
|
183
|
-
expect(sub?._tag).toBe("Subscript")
|
|
184
|
-
expect(sup?._tag).toBe("Superscript")
|
|
185
|
-
}
|
|
186
|
-
}))
|
|
187
|
-
|
|
188
|
-
it.effect("parses tables with proper cell content", () =>
|
|
189
|
-
Effect.gen(function*() {
|
|
190
|
-
const html = `<table>
|
|
191
|
-
<thead><tr><th><p>Header 1</p></th><th><p>Header 2</p></th></tr></thead>
|
|
192
|
-
<tbody><tr><td><p>Cell 1</p></td><td><p>Cell 2</p></td></tr></tbody>
|
|
193
|
-
</table>`
|
|
194
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
195
|
-
const table = doc.children[0]
|
|
196
|
-
expect(table?._tag).toBe("Table")
|
|
197
|
-
if (table?._tag === "Table") {
|
|
198
|
-
// Header should have 2 cells
|
|
199
|
-
expect(table.header?.cells.length).toBe(2)
|
|
200
|
-
// First header cell should have text "Header 1"
|
|
201
|
-
const headerCell = table.header?.cells[0]
|
|
202
|
-
if (headerCell) {
|
|
203
|
-
expect(headerCell.children[0]?._tag).toBe("Text")
|
|
204
|
-
if (headerCell.children[0]?._tag === "Text") {
|
|
205
|
-
expect(headerCell.children[0].value).toBe("Header 1")
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// Body rows
|
|
209
|
-
expect(table.rows.length).toBe(1)
|
|
210
|
-
const bodyCell = table.rows[0]?.cells[0]
|
|
211
|
-
if (bodyCell) {
|
|
212
|
-
expect(bodyCell.children[0]?._tag).toBe("Text")
|
|
213
|
-
if (bodyCell.children[0]?._tag === "Text") {
|
|
214
|
-
expect(bodyCell.children[0].value).toBe("Cell 1")
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}))
|
|
219
|
-
|
|
220
|
-
it.effect("parses nested unordered list as nested List AST", () =>
|
|
221
|
-
Effect.gen(function*() {
|
|
222
|
-
const html = `<ul><li><p>Outer</p><ul><li><p>Inner A</p></li><li><p>Inner B</p></li></ul></li></ul>`
|
|
223
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
224
|
-
const outerList = doc.children[0]
|
|
225
|
-
expect(outerList?._tag).toBe("List")
|
|
226
|
-
if (outerList?._tag === "List") {
|
|
227
|
-
expect(outerList.children.length).toBe(1)
|
|
228
|
-
const outerItem = outerList.children[0]
|
|
229
|
-
expect(outerItem?.children.length).toBe(2)
|
|
230
|
-
// First child is the "Outer" paragraph
|
|
231
|
-
expect(outerItem?.children[0]?._tag).toBe("Paragraph")
|
|
232
|
-
// Second child is the nested List (no longer UnsupportedBlock)
|
|
233
|
-
const nested = outerItem?.children[1]
|
|
234
|
-
expect(nested?._tag).toBe("List")
|
|
235
|
-
if (nested?._tag === "List") {
|
|
236
|
-
expect(nested.children.length).toBe(2)
|
|
237
|
-
const firstInner = nested.children[0]
|
|
238
|
-
expect(firstInner?.children[0]?._tag).toBe("Paragraph")
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}))
|
|
242
|
-
|
|
243
|
-
it.effect("serializes nested list as indented markdown bullets", () =>
|
|
244
|
-
Effect.gen(function*() {
|
|
245
|
-
const html = `<ul><li><p>Outer</p><ul><li><p>Inner A</p></li><li><p>Inner B</p></li></ul></li></ul>`
|
|
246
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
247
|
-
const md = yield* serializeToMarkdown(doc, { includeRawSource: false })
|
|
248
|
-
expect(md).toContain("- Outer")
|
|
249
|
-
expect(md).toContain(" - Inner A")
|
|
250
|
-
expect(md).toContain(" - Inner B")
|
|
251
|
-
expect(md).not.toContain("<ul")
|
|
252
|
-
expect(md).not.toContain("local-id")
|
|
253
|
-
}))
|
|
254
|
-
|
|
255
|
-
it.effect("unwraps table cell with leading empty <p> placeholder", () =>
|
|
256
|
-
Effect.gen(function*() {
|
|
257
|
-
// Confluence editor frequently emits an empty <p/> alongside the real content.
|
|
258
|
-
const html = `<table><tbody>
|
|
259
|
-
<tr><td><p local-id="empty"/><p><strong>Must</strong></p></td><td><p>Text</p></td></tr>
|
|
260
|
-
</tbody></table>`
|
|
261
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
262
|
-
const table = doc.children[0]
|
|
263
|
-
expect(table?._tag).toBe("Table")
|
|
264
|
-
if (table?._tag === "Table") {
|
|
265
|
-
const cell = table.rows[0]?.cells[0]
|
|
266
|
-
expect(cell?.children.length).toBe(1)
|
|
267
|
-
const strong = cell?.children[0]
|
|
268
|
-
expect(strong?._tag).toBe("Strong")
|
|
269
|
-
}
|
|
270
|
-
}))
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
describe("integration test fixture", () => {
|
|
274
|
-
it.effect("parses and serializes integration test fixture", () =>
|
|
275
|
-
Effect.gen(function*() {
|
|
276
|
-
const html = yield* readFixture("integration-test.html.fixture")
|
|
277
|
-
const doc = yield* parseConfluenceHtml(html)
|
|
278
|
-
|
|
279
|
-
// Doc should have children - layout markers plus actual content
|
|
280
|
-
expect(doc.children.length).toBeGreaterThan(0)
|
|
281
|
-
|
|
282
|
-
// Serialize to markdown - content should be readable (not URL-encoded)
|
|
283
|
-
const markdown = yield* serializeToMarkdown(doc)
|
|
284
|
-
// Layout markers should be present as comments
|
|
285
|
-
expect(markdown).toContain("cf:layout-start")
|
|
286
|
-
expect(markdown).toContain("cf:section:")
|
|
287
|
-
// Content should be readable markdown, not URL-encoded
|
|
288
|
-
expect(markdown).toContain("# Heading 1")
|
|
289
|
-
expect(markdown).toContain("## Heading 2")
|
|
290
|
-
|
|
291
|
-
// Serialize to confluence - should reconstruct layout structure
|
|
292
|
-
const confluenceHtml = yield* serializeToConfluence(doc)
|
|
293
|
-
expect(confluenceHtml).toContain("<ac:layout>")
|
|
294
|
-
expect(confluenceHtml).toContain("<ac:layout-section")
|
|
295
|
-
expect(confluenceHtml).toContain("<ac:layout-cell>")
|
|
296
|
-
// Note: ac:task-list may have attributes, so search for the tag name
|
|
297
|
-
expect(confluenceHtml).toContain("ac:task-list")
|
|
298
|
-
expect(confluenceHtml).toContain("<ac:task-status>incomplete</ac:task-status>")
|
|
299
|
-
expect(confluenceHtml).toContain("<ac:task-status>complete</ac:task-status>")
|
|
300
|
-
expect(confluenceHtml).toContain("ac:emoticon")
|
|
301
|
-
expect(confluenceHtml).toContain("ri:account-id")
|
|
302
|
-
}))
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
describe("roundtrip HTML -> MD -> HTML", () => {
|
|
306
|
-
it.effect("roundtrips integration-test.html with 1-to-1 preservation", () =>
|
|
307
|
-
Effect.gen(function*() {
|
|
308
|
-
const originalHtml = yield* readFixture("integration-test.html.fixture")
|
|
309
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
310
|
-
const md = yield* serializeToMarkdown(doc1)
|
|
311
|
-
const doc2 = yield* parseMarkdown(md)
|
|
312
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
313
|
-
|
|
314
|
-
// 1-to-1 roundtrip: finalHtml should EXACTLY match originalHtml
|
|
315
|
-
expect(finalHtml).toBe(originalHtml)
|
|
316
|
-
}))
|
|
317
|
-
|
|
318
|
-
it.effect("renders UserMention as visible markdown link and roundtrips", () =>
|
|
319
|
-
Effect.gen(function*() {
|
|
320
|
-
const originalHtml = `<p><ac:link><ri:user ri:account-id="557058:abc123"/></ac:link></p>`
|
|
321
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
322
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
323
|
-
expect(md).toContain("[@557058:abc123](#cf-user:")
|
|
324
|
-
expect(md).not.toContain("<!--cf:user:")
|
|
325
|
-
const doc2 = yield* parseMarkdown(md)
|
|
326
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
327
|
-
expect(finalHtml).toContain("ri:account-id=\"557058:abc123\"")
|
|
328
|
-
}))
|
|
329
|
-
|
|
330
|
-
it.effect("renders inline StatusMacro as visible link and roundtrips", () =>
|
|
331
|
-
Effect.gen(function*() {
|
|
332
|
-
const originalHtml =
|
|
333
|
-
`<p><ac:structured-macro ac:name="status"><ac:parameter ac:name="title">READY</ac:parameter><ac:parameter ac:name="colour">Blue</ac:parameter></ac:structured-macro></p>`
|
|
334
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
335
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
336
|
-
expect(md).toContain("[READY](#cf-status:Blue)")
|
|
337
|
-
expect(md).not.toContain("<!--cf:status:")
|
|
338
|
-
const doc2 = yield* parseMarkdown(md)
|
|
339
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
340
|
-
expect(finalHtml).toContain("ac:name=\"status\"")
|
|
341
|
-
expect(finalHtml).toContain("READY")
|
|
342
|
-
expect(finalHtml).toContain("Blue")
|
|
343
|
-
}))
|
|
344
|
-
|
|
345
|
-
it.effect("decodes multi-word status title for visible link text", () =>
|
|
346
|
-
Effect.gen(function*() {
|
|
347
|
-
const originalHtml =
|
|
348
|
-
`<p><ac:structured-macro ac:name="status"><ac:parameter ac:name="title">In Progress</ac:parameter><ac:parameter ac:name="colour">Yellow</ac:parameter></ac:structured-macro></p>`
|
|
349
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
350
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
351
|
-
// Display text is decoded; URL fragment carries the encoded value.
|
|
352
|
-
expect(md).toContain("[In Progress](#cf-status:Yellow)")
|
|
353
|
-
expect(md).not.toContain("In%20Progress")
|
|
354
|
-
const finalHtml = yield* serializeToConfluence(yield* parseMarkdown(md))
|
|
355
|
-
expect(finalHtml).toContain("In Progress")
|
|
356
|
-
}))
|
|
357
|
-
|
|
358
|
-
it.effect("preserves real <thead> with empty header cells (no synthetic-header drop)", () =>
|
|
359
|
-
Effect.gen(function*() {
|
|
360
|
-
// A legitimate empty <thead> in the source must round-trip intact —
|
|
361
|
-
// it must NOT be confused with the synthetic empty header the
|
|
362
|
-
// serializer emits for <tbody>-only tables.
|
|
363
|
-
const originalHtml =
|
|
364
|
-
`<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td>a</td><td>b</td></tr></tbody></table>`
|
|
365
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
366
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
367
|
-
expect(md).not.toContain("cf:synth-thead")
|
|
368
|
-
const doc2 = yield* parseMarkdown(md)
|
|
369
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
370
|
-
expect(finalHtml).toBe(originalHtml)
|
|
371
|
-
}))
|
|
372
|
-
|
|
373
|
-
it.effect("renders view-file macro as a link to the attachment", () =>
|
|
374
|
-
Effect.gen(function*() {
|
|
375
|
-
const originalHtml =
|
|
376
|
-
`<p><ac:structured-macro ac:name="view-file"><ac:parameter ac:name="name"><ri:attachment ri:filename="guidelines.md"/></ac:parameter></ac:structured-macro></p>`
|
|
377
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
378
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
379
|
-
expect(md).toContain("[guidelines.md](attachment:guidelines.md)")
|
|
380
|
-
const doc2 = yield* parseMarkdown(md)
|
|
381
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
382
|
-
expect(finalHtml).toContain("ac:name=\"view-file\"")
|
|
383
|
-
expect(finalHtml).toContain("ri:filename=\"guidelines.md\"")
|
|
384
|
-
}))
|
|
385
|
-
|
|
386
|
-
it.effect("roundtrips nested unordered list HTML -> markdown -> HTML", () =>
|
|
387
|
-
Effect.gen(function*() {
|
|
388
|
-
const originalHtml = `<ul><li><p>Outer</p><ul><li><p>Inner A</p></li><li><p>Inner B</p></li></ul></li></ul>`
|
|
389
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
390
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
391
|
-
const doc2 = yield* parseMarkdown(md)
|
|
392
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
393
|
-
expect(finalHtml).toBe(originalHtml)
|
|
394
|
-
}))
|
|
395
|
-
|
|
396
|
-
it.effect("emits markdown header divider for headerless table and roundtrips", () =>
|
|
397
|
-
Effect.gen(function*() {
|
|
398
|
-
// Tables without <thead> need a synthetic header in markdown to render.
|
|
399
|
-
const originalHtml =
|
|
400
|
-
`<table><tbody><tr><td>Cell A1</td><td>Cell A2</td></tr><tr><td>Cell B1</td><td>Cell B2</td></tr></tbody></table>`
|
|
401
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
402
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
403
|
-
// Synthetic empty header + divider so md viewers render it as a table.
|
|
404
|
-
expect(md).toMatch(/\|\s*\|\s*\|\n\| --- \| --- \|/)
|
|
405
|
-
expect(md).toContain("| Cell A1 | Cell A2 |")
|
|
406
|
-
expect(md).toContain("| Cell B1 | Cell B2 |")
|
|
407
|
-
// Roundtrip: the synthetic empty header is dropped on parse, so the
|
|
408
|
-
// Confluence output omits <thead> just like the source.
|
|
409
|
-
const doc2 = yield* parseMarkdown(md)
|
|
410
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
411
|
-
expect(finalHtml).toBe(originalHtml)
|
|
412
|
-
}))
|
|
413
|
-
|
|
414
|
-
it.effect("renders ExpandMacro as <details>/<summary> and roundtrips", () =>
|
|
415
|
-
Effect.gen(function*() {
|
|
416
|
-
const originalHtml =
|
|
417
|
-
`<ac:structured-macro ac:name="expand"><ac:parameter ac:name="title">Glossary term</ac:parameter><ac:rich-text-body><p>Body content here.</p></ac:rich-text-body></ac:structured-macro>`
|
|
418
|
-
const doc1 = yield* parseConfluenceHtml(originalHtml)
|
|
419
|
-
const md = yield* serializeToMarkdown(doc1, { includeRawSource: false })
|
|
420
|
-
expect(md).toContain("<details>")
|
|
421
|
-
expect(md).toContain("<summary>Glossary term</summary>")
|
|
422
|
-
expect(md).toContain("Body content here.")
|
|
423
|
-
expect(md).toContain("</details>")
|
|
424
|
-
// No more opaque cf:expand comment.
|
|
425
|
-
expect(md).not.toContain("<!--cf:expand:")
|
|
426
|
-
|
|
427
|
-
const doc2 = yield* parseMarkdown(md)
|
|
428
|
-
expect(doc2.children[0]?._tag).toBe("ExpandMacro")
|
|
429
|
-
const finalHtml = yield* serializeToConfluence(doc2)
|
|
430
|
-
expect(finalHtml).toContain("ac:name=\"expand\"")
|
|
431
|
-
expect(finalHtml).toContain("Glossary term")
|
|
432
|
-
expect(finalHtml).toContain("Body content here.")
|
|
433
|
-
}))
|
|
434
|
-
|
|
435
|
-
it.effect("roundtrips TOC macro", () =>
|
|
436
|
-
Effect.gen(function*() {
|
|
437
|
-
const html =
|
|
438
|
-
`<ac:structured-macro ac:name="toc"><ac:parameter ac:name="minLevel">2</ac:parameter></ac:structured-macro>`
|
|
439
|
-
const doc1 = yield* parseConfluenceHtml(html)
|
|
440
|
-
expect(doc1.children[0]?._tag).toBe("TocMacro")
|
|
441
|
-
|
|
442
|
-
const md = yield* serializeToMarkdown(doc1)
|
|
443
|
-
expect(md).toContain("[[toc]]")
|
|
444
|
-
|
|
445
|
-
const doc2 = yield* parseMarkdown(md)
|
|
446
|
-
expect(doc2.children[0]?._tag).toBe("TocMacro")
|
|
447
|
-
|
|
448
|
-
const html2 = yield* serializeToConfluence(doc2)
|
|
449
|
-
expect(html2).toContain("<ac:structured-macro ac:name=\"toc\">")
|
|
450
|
-
}))
|
|
451
|
-
})
|
|
452
|
-
})
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
-
import * as Effect from "effect/Effect"
|
|
3
|
-
import * as Either from "effect/Either"
|
|
4
|
-
import * as Schema from "effect/Schema"
|
|
5
|
-
import { PreprocessedHtmlFromConfluence, PreprocessedHtmlSchema } from "../../src/schemas/preprocessing/index.js"
|
|
6
|
-
|
|
7
|
-
describe("PreprocessedHtmlSchema", () => {
|
|
8
|
-
it("validates branded string", () => {
|
|
9
|
-
const result = Schema.decodeUnknownEither(PreprocessedHtmlSchema)("<p>Hello</p>")
|
|
10
|
-
expect(Either.isRight(result)).toBe(true)
|
|
11
|
-
})
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe("PreprocessedHtmlFromConfluence", () => {
|
|
15
|
-
describe("size limit", () => {
|
|
16
|
-
it.effect("rejects HTML exceeding size limit", () =>
|
|
17
|
-
Effect.gen(function*() {
|
|
18
|
-
const largeHtml = "x".repeat(1024 * 1024 + 1)
|
|
19
|
-
const result = yield* Effect.either(Schema.decode(PreprocessedHtmlFromConfluence)(largeHtml))
|
|
20
|
-
expect(Either.isLeft(result)).toBe(true)
|
|
21
|
-
}))
|
|
22
|
-
|
|
23
|
-
it.effect("accepts HTML within size limit", () =>
|
|
24
|
-
Effect.gen(function*() {
|
|
25
|
-
const html = "<p>Hello</p>"
|
|
26
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
27
|
-
expect(result).toContain("Hello")
|
|
28
|
-
}))
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
describe("structured macro expansion", () => {
|
|
32
|
-
it.effect("expands code macro", () =>
|
|
33
|
-
Effect.gen(function*() {
|
|
34
|
-
// Single line format that matches the regex pattern
|
|
35
|
-
const html =
|
|
36
|
-
`<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">typescript</ac:parameter><ac:plain-text-body><![CDATA[const x = 1]]></ac:plain-text-body></ac:structured-macro>`
|
|
37
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
38
|
-
expect(result).toContain("data-macro=\"code\"")
|
|
39
|
-
expect(result).toContain("data-language=\"typescript\"")
|
|
40
|
-
expect(result).toContain("const x = 1")
|
|
41
|
-
}))
|
|
42
|
-
|
|
43
|
-
it.effect("expands info panel macro", () =>
|
|
44
|
-
Effect.gen(function*() {
|
|
45
|
-
const html = `<ac:structured-macro ac:name="info">
|
|
46
|
-
<ac:parameter ac:name="title">Note</ac:parameter>
|
|
47
|
-
<ac:rich-text-body><p>Content here</p></ac:rich-text-body>
|
|
48
|
-
</ac:structured-macro>`
|
|
49
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
50
|
-
expect(result).toContain("data-macro=\"info\"")
|
|
51
|
-
expect(result).toContain("data-title=\"Note\"")
|
|
52
|
-
expect(result).toContain("<p>Content here</p>")
|
|
53
|
-
}))
|
|
54
|
-
|
|
55
|
-
it.effect("expands expand macro", () =>
|
|
56
|
-
Effect.gen(function*() {
|
|
57
|
-
const html = `<ac:structured-macro ac:name="expand">
|
|
58
|
-
<ac:parameter ac:name="title">Click to expand</ac:parameter>
|
|
59
|
-
<ac:rich-text-body><p>Hidden content</p></ac:rich-text-body>
|
|
60
|
-
</ac:structured-macro>`
|
|
61
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
62
|
-
expect(result).toContain("<details")
|
|
63
|
-
expect(result).toContain("<summary>Click to expand</summary>")
|
|
64
|
-
expect(result).toContain("<p>Hidden content</p>")
|
|
65
|
-
}))
|
|
66
|
-
|
|
67
|
-
it.effect("expands TOC macro", () =>
|
|
68
|
-
Effect.gen(function*() {
|
|
69
|
-
const html = `<ac:structured-macro ac:name="toc">
|
|
70
|
-
<ac:parameter ac:name="minLevel">2</ac:parameter>
|
|
71
|
-
<ac:parameter ac:name="maxLevel">4</ac:parameter>
|
|
72
|
-
</ac:structured-macro>`
|
|
73
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
74
|
-
expect(result).toContain("data-macro=\"toc\"")
|
|
75
|
-
expect(result).toContain("data-min=\"2\"")
|
|
76
|
-
expect(result).toContain("data-max=\"4\"")
|
|
77
|
-
}))
|
|
78
|
-
|
|
79
|
-
it.effect("expands status macro", () =>
|
|
80
|
-
Effect.gen(function*() {
|
|
81
|
-
const html = `<ac:structured-macro ac:name="status">
|
|
82
|
-
<ac:parameter ac:name="colour">Green</ac:parameter>
|
|
83
|
-
<ac:parameter ac:name="title">Done</ac:parameter>
|
|
84
|
-
</ac:structured-macro>`
|
|
85
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
86
|
-
expect(result).toContain("data-macro=\"status\"")
|
|
87
|
-
expect(result).toContain("data-color=\"Green\"")
|
|
88
|
-
expect(result).toContain("Done")
|
|
89
|
-
}))
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
describe("task list preprocessing", () => {
|
|
93
|
-
it.effect("converts task list to ul", () =>
|
|
94
|
-
Effect.gen(function*() {
|
|
95
|
-
const html = `<ac:task-list>
|
|
96
|
-
<ac:task>
|
|
97
|
-
<ac:task-id>1</ac:task-id>
|
|
98
|
-
<ac:task-status>incomplete</ac:task-status>
|
|
99
|
-
<ac:task-body>Task one</ac:task-body>
|
|
100
|
-
</ac:task>
|
|
101
|
-
</ac:task-list>`
|
|
102
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
103
|
-
expect(result).toContain("<ul data-macro=\"task-list\">")
|
|
104
|
-
expect(result).toContain("data-task-id=\"1\"")
|
|
105
|
-
expect(result).toContain("data-task-status=\"incomplete\"")
|
|
106
|
-
expect(result).toContain("Task one")
|
|
107
|
-
}))
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
describe("image preprocessing", () => {
|
|
111
|
-
it.effect("converts attachment images", () =>
|
|
112
|
-
Effect.gen(function*() {
|
|
113
|
-
const html = `<ac:image ac:align="center" ac:width="250">
|
|
114
|
-
<ri:attachment ri:filename="test.png"/>
|
|
115
|
-
</ac:image>`
|
|
116
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
117
|
-
expect(result).toContain("data-attachment=\"test.png\"")
|
|
118
|
-
expect(result).toContain("data-align=\"center\"")
|
|
119
|
-
expect(result).toContain("data-width=\"250\"")
|
|
120
|
-
}))
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
describe("emoticon preprocessing", () => {
|
|
124
|
-
it.effect("converts emoticons", () =>
|
|
125
|
-
Effect.gen(function*() {
|
|
126
|
-
const html = `<ac:emoticon ac:emoji-shortname=":grinning:" ac:emoji-id="1f600" ac:emoji-fallback="😀"/>`
|
|
127
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
128
|
-
expect(result).toContain("data-emoji=\":grinning:\"")
|
|
129
|
-
expect(result).toContain("data-emoji-id=\"1f600\"")
|
|
130
|
-
expect(result).toContain("😀")
|
|
131
|
-
}))
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
describe("user mention preprocessing", () => {
|
|
135
|
-
it.effect("converts user mentions", () =>
|
|
136
|
-
Effect.gen(function*() {
|
|
137
|
-
const html = `<ac:link><ri:user ri:account-id="557058:abc123"/></ac:link>`
|
|
138
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
139
|
-
expect(result).toContain("data-user-mention=\"557058:abc123\"")
|
|
140
|
-
}))
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
describe("namespace stripping", () => {
|
|
144
|
-
it.effect("removes ac: and ri: tags", () =>
|
|
145
|
-
Effect.gen(function*() {
|
|
146
|
-
const html = `<p>Text<ac:parameter ac:name="foo">bar</ac:parameter></p>`
|
|
147
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
148
|
-
expect(result).not.toContain("ac:parameter")
|
|
149
|
-
expect(result).toContain("<p>Text</p>")
|
|
150
|
-
}))
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe("layout preprocessing", () => {
|
|
154
|
-
it.effect("adds layout markers", () =>
|
|
155
|
-
Effect.gen(function*() {
|
|
156
|
-
const html = `<ac:layout>
|
|
157
|
-
<ac:layout-section ac:type="two_equal">
|
|
158
|
-
<ac:layout-cell><p>Cell 1</p></ac:layout-cell>
|
|
159
|
-
<ac:layout-cell><p>Cell 2</p></ac:layout-cell>
|
|
160
|
-
</ac:layout-section>
|
|
161
|
-
</ac:layout>`
|
|
162
|
-
const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
163
|
-
expect(result).toContain("<!--cf:layout-start-->")
|
|
164
|
-
expect(result).toContain("<!--cf:layout-end-->")
|
|
165
|
-
expect(result).toContain("<!--cf:section:0;")
|
|
166
|
-
expect(result).toContain("<p>Cell 1</p>")
|
|
167
|
-
expect(result).toContain("<p>Cell 2</p>")
|
|
168
|
-
}))
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
describe("encode (identity)", () => {
|
|
172
|
-
it.effect("encode returns same string", () =>
|
|
173
|
-
Effect.gen(function*() {
|
|
174
|
-
const html = "<p>Hello</p>"
|
|
175
|
-
const preprocessed = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
|
|
176
|
-
const encoded = yield* Schema.encode(PreprocessedHtmlFromConfluence)(preprocessed)
|
|
177
|
-
expect(encoded).toBe(preprocessed)
|
|
178
|
-
}))
|
|
179
|
-
})
|
|
180
|
-
})
|