Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The best Markdown to Confluence converter. Direct AST-to-XML conversion with no
- **Full GFM support** — Tables, task lists, strikethrough, autolinks
- **Mermaid diagrams** — Rendered to PNG with ELK layout engine
- **Admonitions** — `[!NOTE]`, `[!WARNING]`, `[!TIP]` → Confluence info panels
- **Raw passthrough** — ` ```confluence ` blocks emit storage XML verbatim (toc, status, …)
- **Smart sync** — MD5-based change detection, only uploads when content changes
- **Local images** — Automatically uploaded as attachments
- **Frontmatter** — Control page ID, title, and labels via YAML
Expand Down Expand Up @@ -154,6 +155,21 @@ flowchart TD

Mermaid diagrams are rendered to PNG with ELK layout engine for better handling of complex flowcharts and subgraphs.

### Raw Confluence Passthrough

For Confluence macros that have no Markdown equivalent (table of contents, status lozenges, expand/details panels, …), a ` ```confluence ` fenced block is passed through to the page's storage format **verbatim** — not escaped, not wrapped in a code macro.

````markdown
```confluence
<ac:structured-macro ac:name="toc">
<ac:parameter ac:name="minLevel">2</ac:parameter>
<ac:parameter ac:name="maxLevel">2</ac:parameter>
</ac:structured-macro>
```
````

This is a block-level escape hatch — the whole block is raw storage XML. Confluence validates it at publish time.

## License

[MIT](LICENSE) © Vladimir Urushev
6 changes: 6 additions & 0 deletions src/elements/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ registerConverter<Code>('code', (node, context) => {
return `<ac:image ac:align="center" ac:layout="center" ac:width="800" ac:thumbnail="true"><ri:attachment ri:filename="${filename}"/></ac:image>`
}

// Raw Confluence storage-format passthrough. Emitted verbatim — no escaping, no code
// macro — as an escape hatch for macros with no markdown form (toc, details, status, …).
if (lang === 'confluence') {
return code
}

const params: Record<string, string> = {}
if (lang) {
params.language = mapLanguage(lang)
Expand Down
35 changes: 35 additions & 0 deletions test/converter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,41 @@ describe('Code blocks', () => {
})
})

describe('Confluence passthrough', () => {
it('emits a confluence fence verbatim and unescaped', () => {
const result = md2confluence('```confluence\n<ac:structured-macro ac:name="toc"/>\n```')
expect(result).toContain('<ac:structured-macro ac:name="toc"/>')
expect(result).not.toContain('&lt;')
expect(result).not.toContain('ac:name="code"')
})

it('preserves special characters and nested macros byte-for-byte', () => {
const body =
'<ac:structured-macro ac:name="status"><ac:parameter ac:name="colour">Green</ac:parameter><ac:parameter ac:name="title">public</ac:parameter></ac:structured-macro>'
const result = md2confluence(`\`\`\`confluence\n<p>a & b "c" ${body}</p>\n\`\`\``)
expect(result).toContain(`<p>a & b "c" ${body}</p>`)
expect(result).not.toContain('&amp;')
expect(result).not.toContain('&quot;')
})

it('preserves newlines in a multi-line fence body', () => {
const result = md2confluence('```confluence\n<table>\n <tr><td>x</td></tr>\n</table>\n```')
expect(result).toBe('<table>\n <tr><td>x</td></tr>\n</table>')
})

it('does not affect real language code blocks', () => {
const result = md2confluence('```js\nconst x = 1\n```')
expect(result).toContain('<ac:structured-macro ac:name="code">')
expect(result).toContain('<ac:parameter ac:name="language">javascript</ac:parameter>')
})

it('matches the language exactly — uppercase falls through to a code block', () => {
const result = md2confluence('```Confluence\n<ac:structured-macro ac:name="toc"/>\n```')
expect(result).toContain('<ac:structured-macro ac:name="code">')
expect(result).toContain('<![CDATA[<ac:structured-macro ac:name="toc"/>]]>')
})
})

describe('Lists', () => {
it('converts unordered lists', () => {
const result = md2confluence('- Item 1\n- Item 2')
Expand Down