@wonseok-han/confluence-sync 1.4.0 → 1.5.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/README.md CHANGED
@@ -130,6 +130,7 @@ docs/ → [CONFLUENCE_PARENT_ID 또는 공간 최상위]
130
130
 
131
131
  - 페이지 제목은 **frontmatter `title` → 첫 `# 제목` → 파일명** 순으로 정해지고, 본문은 storage format 으로 변환됩니다.
132
132
  - 코드블록 → **code 매크로**, 표·리스트·헤딩은 그대로.
133
+ - `mermaid` 코드블록 뒤에는 **Mermaid diagram** 매크로를 자동 삽입합니다. Confluence에 [Atlassian Labs Mermaid Diagrams Viewer](https://marketplace.atlassian.com/apps/1232887/mermaid-diagrams-viewer)가 설치되어 있어야 하며, 원본 코드도 함께 표시됩니다. 자동 연결은 등장 순서를 사용하므로 페이지의 모든 Mermaid 코드는 `mermaid` 코드블록으로 작성하세요.
133
134
  - **내부 `.md` 링크**와 **`[[wikilink]]`** → Confluence 페이지 링크로 자동 변환.
134
135
  - **섹션 링크** `[§1 개요](#1-개요)` · `[§6](./04-구현체.md#6-zkp)` · `[[문서#헤딩 텍스트]]` → Confluence 앵커 링크. [아래 참조](#섹션-링크-앵커)
135
136
  - **로컬 이미지**와 **`![[embed]]`** → 페이지 첨부로 업로드 후 참조 (같은 파일명은 새 버전으로 갱신). 외부 URL 이미지는 그대로.
package/dist/markdown.js CHANGED
@@ -11,6 +11,7 @@ import { createHash } from 'node:crypto';
11
11
  import MarkdownIt from 'markdown-it';
12
12
  import { resolveWikilinks } from './obsidian.js';
13
13
  import { decodeAnchor, anchorMacro } from './anchors.js';
14
+ import { mermaidMacro } from './mermaid.js';
14
15
  export const escapeXml = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
15
16
  export const hashOf = (s) => createHash('sha256').update(s).digest('hex');
16
17
  /** markdown-it 이 퍼센트 인코딩한 링크/이미지 경로를 실제 경로로 디코드(실패 시 원본). */
@@ -25,7 +26,7 @@ const decodePath = (s) => {
25
26
  const md = new MarkdownIt({ html: true, linkify: true, breaks: false });
26
27
  const emptyCtx = () => ({
27
28
  fileDir: '', titleIndex: {}, anchorIndex: {}, selfRel: '', selfTitle: '',
28
- anchorNames: new Set(), bodySlugs: [], headingSeq: 0, anchorsPlaced: 0,
29
+ anchorNames: new Set(), bodySlugs: [], headingSeq: 0, mermaidSeq: 0, anchorsPlaced: 0,
29
30
  images: [], internalLinks: 0, anchorLinks: 0, deadAnchors: [], linkStack: [], linkedTitles: new Set(),
30
31
  });
31
32
  // 렌더 규칙은 md 인스턴스에 한 번만 등록되므로, 현재 변환 컨텍스트를 모듈 변수로 공유한다.
@@ -76,7 +77,8 @@ md.renderer.rules.heading_open = (tokens, idx, opts, _env, self) => {
76
77
  };
77
78
  md.renderer.rules.fence = (tokens, idx) => {
78
79
  const t = tokens[idx];
79
- const lang = (t.info || '').trim().split(/\s+/)[0];
80
+ const infoLang = (t.info || '').trim().split(/\s+/)[0];
81
+ const lang = infoLang.toLowerCase() === 'mermaid' ? 'mermaid' : infoLang;
80
82
  // 별도 호스팅한 PDF.js 등 웹 뷰어를 Confluence iFrame 매크로로 표시.
81
83
  if (lang === 'confluence-iframe') {
82
84
  const url = t.content.trim();
@@ -85,8 +87,9 @@ md.renderer.rules.fence = (tokens, idx) => {
85
87
  return `<ac:structured-macro ac:name="iframe"><ac:parameter ac:name="url">${escapeXml(url)}</ac:parameter><ac:parameter ac:name="width">100%</ac:parameter><ac:parameter ac:name="height">900</ac:parameter></ac:structured-macro>\n`;
86
88
  }
87
89
  const safe = t.content.split(']]>').join(']]]]><![CDATA[>');
88
- const langParam = lang ? `<ac:parameter ac:name="language">${lang}</ac:parameter>` : '';
89
- return `<ac:structured-macro ac:name="code">${langParam}<ac:plain-text-body><![CDATA[${safe}]]></ac:plain-text-body></ac:structured-macro>\n`;
90
+ const langParam = lang ? `<ac:parameter ac:name="language">${escapeXml(lang)}</ac:parameter>` : '';
91
+ const code = `<ac:structured-macro ac:name="code">${langParam}<ac:plain-text-body><![CDATA[${safe}]]></ac:plain-text-body></ac:structured-macro>\n`;
92
+ return code + (lang === 'mermaid' ? mermaidMacro(ctx.selfRel, ctx.mermaidSeq++) : '');
90
93
  };
91
94
  md.renderer.rules.link_open = (tokens, idx, opts, _env, self) => {
92
95
  const href = tokens[idx].attrGet('href') || '';
@@ -0,0 +1,13 @@
1
+ import { createHash } from 'node:crypto';
2
+ // Atlassian Labs Mermaid Diagrams Viewer (Marketplace production deployment).
3
+ // Confirmed against the installed macro's editor/clipboard attributes.
4
+ export const MERMAID_EXTENSION_KEY = '23392b90-4271-4239-98ca-a3e96c663cbb/63d4d207-ac2f-4273-865c-0240d37f044a/static/mermaid-diagram';
5
+ /** Auto-detect pairs Mermaid code blocks and viewer macros in document order. */
6
+ export function mermaidMacro(document, ordinal) {
7
+ // Random IDs would change the storage hash on every push. Include the ordinal
8
+ // so repeated identical diagrams still have distinct macro instances.
9
+ const hex = createHash('sha256').update(`csync:mermaid:${document}:${ordinal}`).digest('hex');
10
+ const id = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
11
+ const node = `<ac:adf-node type="extension"><ac:adf-attribute key="extension-type">com.atlassian.ecosystem</ac:adf-attribute><ac:adf-attribute key="extension-key">${MERMAID_EXTENSION_KEY}</ac:adf-attribute><ac:adf-attribute key="parameters"><ac:adf-parameter key="local-id">${id}</ac:adf-parameter><ac:adf-parameter key="extension-id">ari:cloud:ecosystem::extension/${MERMAID_EXTENSION_KEY}</ac:adf-parameter><ac:adf-parameter key="extension-title">Mermaid diagram</ac:adf-parameter></ac:adf-attribute><ac:adf-attribute key="text">Mermaid diagram</ac:adf-attribute><ac:adf-attribute key="layout">default</ac:adf-attribute><ac:adf-attribute key="local-id">${id}</ac:adf-attribute></ac:adf-node>`;
12
+ return `<ac:adf-extension>${node}<ac:adf-fallback>${node}</ac:adf-fallback></ac:adf-extension>\n`;
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wonseok-han/confluence-sync",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "description": "Confluence Cloud ↔ Markdown 양방향 동기화 CLI — 마크다운을 Confluence 페이지로 발행(push)하고, Confluence 페이지·폴더·스페이스를 마크다운으로 가져옵니다(pull).",
6
6
  "keywords": [