confluence-md-sync 0.2.1 → 0.4.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 +107 -9
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +84 -28
- package/dist/export/canonical.d.ts +38 -0
- package/dist/export/canonical.js +272 -0
- package/dist/export/export-page.d.ts +36 -0
- package/dist/export/export-page.js +41 -0
- package/dist/export/roundtrip.d.ts +30 -0
- package/dist/export/roundtrip.js +32 -0
- package/dist/export/to-markdown.d.ts +49 -0
- package/dist/export/to-markdown.js +1132 -0
- package/dist/export/xhtml.d.ts +52 -0
- package/dist/export/xhtml.js +230 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +8 -1
- package/dist/macros/builder.d.ts +7 -0
- package/dist/macros/builder.js +23 -2
- package/dist/macros/index.d.ts +2 -1
- package/dist/macros/index.js +2 -1
- package/dist/macros/plugins/core.js +3 -0
- package/dist/macros/plugins/table-filter.d.ts +8 -0
- package/dist/macros/plugins/table-filter.js +35 -3
- package/dist/macros/registry.d.ts +11 -0
- package/dist/macros/registry.js +45 -1
- package/dist/markdown/render.d.ts +30 -1
- package/dist/markdown/render.js +83 -13
- package/dist/publish/publish.d.ts +17 -0
- package/dist/publish/publish.js +43 -4
- package/dist/publish/remote.d.ts +25 -0
- package/dist/publish/remote.js +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Экспорт страницы Confluence в комплект «markdown + аттачи», пригодный
|
|
3
|
+
* для обратной публикации через publishPage (render-стиль 'attachment').
|
|
4
|
+
*/
|
|
5
|
+
import type { ConfluenceConfig } from '../client/config.js';
|
|
6
|
+
import type { MacroRegistry } from '../macros/registry.js';
|
|
7
|
+
import { type StorageToMarkdownResult } from './to-markdown.js';
|
|
8
|
+
export interface ExportPageOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Точный путь до итогового md-файла. Альтернатива `outDir`. Аттачи (если
|
|
11
|
+
* включены) кладутся в `attachments/` рядом с этим файлом.
|
|
12
|
+
*/
|
|
13
|
+
outFile?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Каталог для `page.md` и `attachments/`. Используется, если не задан
|
|
16
|
+
* `outFile`. Если не задан ни тот, ни другой — `./<pageId>`.
|
|
17
|
+
*/
|
|
18
|
+
outDir?: string;
|
|
19
|
+
/** Скачивать ли аттачи, на которые ссылается страница. Default: true. */
|
|
20
|
+
downloadAttachments?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 'faithful' (default) — round-trippable, с сырым HTML в fallback;
|
|
23
|
+
* 'readable' — чистый Markdown ценой оформления (round-trip не гарантирован).
|
|
24
|
+
*/
|
|
25
|
+
mode?: 'faithful' | 'readable';
|
|
26
|
+
registry?: MacroRegistry;
|
|
27
|
+
}
|
|
28
|
+
export interface ExportPageResult extends StorageToMarkdownResult {
|
|
29
|
+
pageId: string;
|
|
30
|
+
title: string;
|
|
31
|
+
version: number;
|
|
32
|
+
markdownPath: string;
|
|
33
|
+
/** Скачанные файлы: имя аттача → локальный путь. */
|
|
34
|
+
downloaded: Map<string, string>;
|
|
35
|
+
}
|
|
36
|
+
export declare function exportPage(pageId: string, opts: ExportPageOptions, cfg: ConfluenceConfig): Promise<ExportPageResult>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Экспорт страницы Confluence в комплект «markdown + аттачи», пригодный
|
|
3
|
+
* для обратной публикации через publishPage (render-стиль 'attachment').
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { ConfluenceClient } from '../client/client.js';
|
|
8
|
+
import { storageToMarkdown } from './to-markdown.js';
|
|
9
|
+
export async function exportPage(pageId, opts, cfg) {
|
|
10
|
+
const client = new ConfluenceClient(cfg);
|
|
11
|
+
const page = await client.getPageStorage(pageId);
|
|
12
|
+
const converted = storageToMarkdown(page.storage, { registry: opts.registry, mode: opts.mode });
|
|
13
|
+
const markdownPath = opts.outFile ?? join(opts.outDir ?? `./${pageId}`, 'page.md');
|
|
14
|
+
mkdirSync(dirname(markdownPath), { recursive: true });
|
|
15
|
+
writeFileSync(markdownPath, converted.markdown);
|
|
16
|
+
const downloaded = new Map();
|
|
17
|
+
if (opts.downloadAttachments !== false && converted.attachmentRefs.length > 0) {
|
|
18
|
+
const dir = join(dirname(markdownPath), 'attachments');
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
for (const name of converted.attachmentRefs) {
|
|
21
|
+
const [att] = await client.listAttachments(pageId, name);
|
|
22
|
+
if (att === undefined) {
|
|
23
|
+
console.warn(`[export] attachment '${name}' referenced by the page but not found`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const link = att._links?.download ?? `/download/attachments/${pageId}/${encodeURIComponent(name)}`;
|
|
27
|
+
const data = await client.downloadAttachment(link);
|
|
28
|
+
const path = join(dir, name);
|
|
29
|
+
writeFileSync(path, data);
|
|
30
|
+
downloaded.set(name, path);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
...converted,
|
|
35
|
+
pageId,
|
|
36
|
+
title: page.title,
|
|
37
|
+
version: page.version,
|
|
38
|
+
markdownPath,
|
|
39
|
+
downloaded,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round-trip-проверка: storage → markdown → storage′ и каноническое
|
|
3
|
+
* сравнение (см. canonical.ts — критерий «без потери разметки»).
|
|
4
|
+
*/
|
|
5
|
+
import type { ConfluenceConfig } from '../client/config.js';
|
|
6
|
+
import { type MacroRegistry } from '../macros/registry.js';
|
|
7
|
+
import { type StorageDiff } from './canonical.js';
|
|
8
|
+
import { type StorageToMarkdownResult } from './to-markdown.js';
|
|
9
|
+
export interface RoundTripResult extends StorageToMarkdownResult {
|
|
10
|
+
/** storage, восстановленный из markdown публикационным конвейером. */
|
|
11
|
+
regenerated: string;
|
|
12
|
+
equal: boolean;
|
|
13
|
+
diffs: StorageDiff[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Рендерит markdown из exportPage/storageToMarkdown обратно в storage тем
|
|
17
|
+
* же конвейером, что publishPage (attachment-стиль ссылок, без linkify).
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderExportedMarkdown(markdown: string, registry?: MacroRegistry): string;
|
|
20
|
+
/** Полный офлайн round-trip для готового storage-фрагмента. */
|
|
21
|
+
export declare function roundTripStorage(storage: string, opts?: {
|
|
22
|
+
registry?: MacroRegistry;
|
|
23
|
+
}): RoundTripResult;
|
|
24
|
+
/** Round-trip для живой страницы: тянет storage по id и проверяет офлайн. */
|
|
25
|
+
export declare function roundTripPage(pageId: string, cfg: ConfluenceConfig, opts?: {
|
|
26
|
+
registry?: MacroRegistry;
|
|
27
|
+
}): Promise<RoundTripResult & {
|
|
28
|
+
title: string;
|
|
29
|
+
version: number;
|
|
30
|
+
}>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round-trip-проверка: storage → markdown → storage′ и каноническое
|
|
3
|
+
* сравнение (см. canonical.ts — критерий «без потери разметки»).
|
|
4
|
+
*/
|
|
5
|
+
import { ConfluenceClient } from '../client/client.js';
|
|
6
|
+
import { processMacros } from '../macros/registry.js';
|
|
7
|
+
import { defaultMacroRegistry } from '../macros/index.js';
|
|
8
|
+
import { renderToStorage } from '../markdown/render.js';
|
|
9
|
+
import { compareStorage } from './canonical.js';
|
|
10
|
+
import { storageToMarkdown } from './to-markdown.js';
|
|
11
|
+
/**
|
|
12
|
+
* Рендерит markdown из exportPage/storageToMarkdown обратно в storage тем
|
|
13
|
+
* же конвейером, что publishPage (attachment-стиль ссылок, без linkify).
|
|
14
|
+
*/
|
|
15
|
+
export function renderExportedMarkdown(markdown, registry) {
|
|
16
|
+
const storage = renderToStorage(markdown, { images: new Map(), files: new Map() }, { imageStyle: 'attachment', fileStyle: 'attachment', linkify: false });
|
|
17
|
+
return processMacros(storage, registry ?? defaultMacroRegistry).toString();
|
|
18
|
+
}
|
|
19
|
+
/** Полный офлайн round-trip для готового storage-фрагмента. */
|
|
20
|
+
export function roundTripStorage(storage, opts = {}) {
|
|
21
|
+
const converted = storageToMarkdown(storage, opts);
|
|
22
|
+
const regenerated = renderExportedMarkdown(converted.markdown, opts.registry);
|
|
23
|
+
const { equal, diffs } = compareStorage(storage, regenerated);
|
|
24
|
+
return { ...converted, regenerated, equal, diffs };
|
|
25
|
+
}
|
|
26
|
+
/** Round-trip для живой страницы: тянет storage по id и проверяет офлайн. */
|
|
27
|
+
export async function roundTripPage(pageId, cfg, opts = {}) {
|
|
28
|
+
const client = new ConfluenceClient(cfg);
|
|
29
|
+
const page = await client.getPageStorage(pageId);
|
|
30
|
+
const result = roundTripStorage(page.storage, opts);
|
|
31
|
+
return { ...result, title: page.title, version: page.version };
|
|
32
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Конвертация Confluence storage → Markdown. Два режима:
|
|
3
|
+
*
|
|
4
|
+
* FAITHFUL (default) — гарантия round-trip. Трёхуровневая политика:
|
|
5
|
+
* 1. чистый Markdown — заголовки, абзацы, списки, простые таблицы,
|
|
6
|
+
* ссылки, картинки-аттачи ({{img:...}}), page-ссылки ({{page:...}});
|
|
7
|
+
* 2. маркеры макросов <!-- MACRO:start/end --> — для макросов, чей
|
|
8
|
+
* рендер восстанавливает исходный XHTML (проверяется на месте:
|
|
9
|
+
* маркер прогоняется через render-конвейер и сравнивается канонически);
|
|
10
|
+
* 3. дословный XHTML — «как есть»: без ac:/ri:-тегов — сырым HTML,
|
|
11
|
+
* с ними — fenced-блоком ```confluence-storage.
|
|
12
|
+
* Потери исключены по построению; но (3) даёт сырой HTML, который многие
|
|
13
|
+
* md-редакторы показывают уродливо.
|
|
14
|
+
*
|
|
15
|
+
* READABLE — чистый Markdown ценой оформления. Round-trip НЕ гарантируется:
|
|
16
|
+
* теряются цвета/стили спанов, div-обёртки, точная геометрия объединённых
|
|
17
|
+
* ячеек; сохраняется смысловая нагрузка (текст, структура). Сложные
|
|
18
|
+
* таблицы разворачиваются в GFM (colspan/rowspan → сетка с заполнением,
|
|
19
|
+
* блочное содержимое ячеек — во flatten через <br>). Сырой HTML-блок не
|
|
20
|
+
* выдаётся никогда.
|
|
21
|
+
*/
|
|
22
|
+
import { type MacroRegistry } from '../macros/registry.js';
|
|
23
|
+
export interface StorageToMarkdownOptions {
|
|
24
|
+
/** Реестр для проверки маркеров макросов (default: встроенный). */
|
|
25
|
+
registry?: MacroRegistry;
|
|
26
|
+
/**
|
|
27
|
+
* 'faithful' (default) — round-trippable, но с сырым HTML в fallback;
|
|
28
|
+
* 'readable' — чистый Markdown ценой оформления, без round-trip.
|
|
29
|
+
*/
|
|
30
|
+
mode?: 'faithful' | 'readable';
|
|
31
|
+
}
|
|
32
|
+
export interface StorageToMarkdownResult {
|
|
33
|
+
markdown: string;
|
|
34
|
+
/** Имена аттачей, на которые ссылаются {{img:...}}. */
|
|
35
|
+
images: string[];
|
|
36
|
+
/** Имена аттачей, на которые ссылаются {{file:...}}. */
|
|
37
|
+
files: string[];
|
|
38
|
+
/** Все имена аттачей, упомянутые где-либо (включая fenced-блоки). */
|
|
39
|
+
attachmentRefs: string[];
|
|
40
|
+
stats: {
|
|
41
|
+
markers: number;
|
|
42
|
+
fenced: number;
|
|
43
|
+
rawHtml: number;
|
|
44
|
+
/** readable-режим: сколько узлов конвертировано с потерей оформления. */
|
|
45
|
+
lossy: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Конвертирует storage-фрагмент страницы в Markdown. */
|
|
49
|
+
export declare function storageToMarkdown(storage: string, opts?: StorageToMarkdownOptions): StorageToMarkdownResult;
|