confluence-md-sync 0.8.10 → 0.9.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.
- package/dist/client/client.d.ts +1 -0
- package/dist/client/client.js +7 -2
- package/dist/export/export-page.js +9 -3
- package/dist/export/to-markdown.d.ts +2 -0
- package/dist/export/to-markdown.js +31 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/macros/index.d.ts +2 -0
- package/dist/macros/index.js +5 -1
- package/dist/macros/plugins/drawio.d.ts +6 -0
- package/dist/macros/plugins/drawio.js +24 -0
- package/dist/markdown/drawio.d.ts +15 -0
- package/dist/markdown/drawio.js +28 -0
- package/dist/markdown/native.js +14 -4
- package/dist/markdown/validate.js +5 -0
- package/dist/publish/publish.js +2 -0
- package/package.json +1 -1
package/dist/client/client.d.ts
CHANGED
package/dist/client/client.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
3
|
import { authHeader } from './config.js';
|
|
4
|
+
function attachmentMime(name, bytes) {
|
|
5
|
+
if (/^\s*(?:<\?xml[^>]*>\s*)?<(mxfile|mxGraphModel)\b/.test(bytes.subarray(0, 4096).toString('utf8')))
|
|
6
|
+
return 'application/vnd.jgraph.mxfile';
|
|
7
|
+
return /\.png$/i.test(name) ? 'image/png' : 'application/octet-stream';
|
|
8
|
+
}
|
|
4
9
|
export class ConfluenceApiError extends Error {
|
|
5
10
|
status;
|
|
6
11
|
statusText;
|
|
@@ -230,7 +235,7 @@ export class ConfluenceClient {
|
|
|
230
235
|
const filename = basename(filePath);
|
|
231
236
|
const buf = readFileSync(filePath);
|
|
232
237
|
const form = new FormData();
|
|
233
|
-
form.append('file', new Blob([buf]), filename);
|
|
238
|
+
form.append('file', new Blob([buf], { type: attachmentMime(filename, buf) }), filename);
|
|
234
239
|
form.append('minorEdit', 'true');
|
|
235
240
|
if (comment)
|
|
236
241
|
form.append('comment', comment);
|
|
@@ -261,7 +266,7 @@ export class ConfluenceClient {
|
|
|
261
266
|
const filename = basename(filePath);
|
|
262
267
|
const buf = readFileSync(filePath);
|
|
263
268
|
const form = new FormData();
|
|
264
|
-
form.append('file', new Blob([buf]), filename);
|
|
269
|
+
form.append('file', new Blob([buf], { type: attachmentMime(filename, buf) }), filename);
|
|
265
270
|
form.append('minorEdit', 'true');
|
|
266
271
|
if (comment)
|
|
267
272
|
form.append('comment', comment);
|
|
@@ -6,10 +6,12 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import { ConfluenceClient } from '../client/client.js';
|
|
8
8
|
import { storageToMarkdown } from './to-markdown.js';
|
|
9
|
+
import { drawioReferences, drawioAttachmentNames } from '../markdown/drawio.js';
|
|
9
10
|
export async function exportPage(pageId, opts, cfg) {
|
|
10
11
|
const client = new ConfluenceClient(cfg);
|
|
11
12
|
const page = await client.getPageStorage(pageId);
|
|
12
13
|
const converted = storageToMarkdown(page.storage, {
|
|
14
|
+
drawioNames: drawioAttachmentNames(await client.listAttachments(pageId)),
|
|
13
15
|
registry: opts.registry,
|
|
14
16
|
mode: opts.mode,
|
|
15
17
|
attachments: opts.attachments,
|
|
@@ -23,13 +25,17 @@ export async function exportPage(pageId, opts, cfg) {
|
|
|
23
25
|
const dir = join(dirname(markdownPath), 'attachments');
|
|
24
26
|
mkdirSync(dir, { recursive: true });
|
|
25
27
|
for (const name of converted.attachmentRefs) {
|
|
26
|
-
const
|
|
28
|
+
const diagram = drawioReferences(converted.markdown).find(d => d.name === name || d.preview === name);
|
|
29
|
+
const sourcePage = diagram?.pageId || pageId;
|
|
30
|
+
const [att] = await client.listAttachments(sourcePage, name);
|
|
27
31
|
if (att === undefined) {
|
|
28
32
|
console.warn(`[export] attachment '${name}' referenced by the page but not found`);
|
|
29
33
|
continue;
|
|
30
34
|
}
|
|
31
|
-
const link = att._links?.download ?? `/download/attachments/${
|
|
32
|
-
|
|
35
|
+
const link = new URL(att._links?.download ?? `/download/attachments/${sourcePage}/${encodeURIComponent(name)}`, cfg.baseUrl);
|
|
36
|
+
if (diagram?.name === name && diagram.revision)
|
|
37
|
+
link.searchParams.set('version', diagram.revision);
|
|
38
|
+
const data = await client.downloadAttachment(link.href);
|
|
33
39
|
const path = join(dir, name);
|
|
34
40
|
writeFileSync(path, data);
|
|
35
41
|
downloaded.set(name, path);
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { type MacroRegistry } from '../macros/registry.js';
|
|
23
23
|
export interface StorageToMarkdownOptions {
|
|
24
|
+
/** Known XML attachments; needed for image-mode diagrams without a .drawio suffix. */
|
|
25
|
+
drawioNames?: string[];
|
|
24
26
|
/** Реестр для проверки маркеров макросов (default: встроенный). */
|
|
25
27
|
registry?: MacroRegistry;
|
|
26
28
|
/**
|
|
@@ -39,6 +39,7 @@ export function storageToMarkdown(storage, opts = {}) {
|
|
|
39
39
|
localFiles: local,
|
|
40
40
|
tablesAsRecords: records,
|
|
41
41
|
localPrefix: opts.localPrefix ?? 'attachments/',
|
|
42
|
+
drawioNames: opts.drawioNames ?? [],
|
|
42
43
|
});
|
|
43
44
|
const markdown = conv.blocksToMd(parseStorage(storage));
|
|
44
45
|
const attachmentRefs = new Set([...conv.images, ...conv.files]);
|
|
@@ -78,6 +79,7 @@ const UNWRAP_BLOCK = new Set([
|
|
|
78
79
|
const SAFE_URL_RE = /^[A-Za-z0-9\-._~:/?#@!$&'*+,;=%]+$/;
|
|
79
80
|
class Converter {
|
|
80
81
|
registry;
|
|
82
|
+
drawioNames;
|
|
81
83
|
images = new Set();
|
|
82
84
|
files = new Set();
|
|
83
85
|
stats = { markers: 0, fenced: 0, rawHtml: 0, lossy: 0, native: 0, normalized: 0 };
|
|
@@ -87,6 +89,7 @@ class Converter {
|
|
|
87
89
|
localPrefix;
|
|
88
90
|
constructor(registry, opts) {
|
|
89
91
|
this.registry = registry;
|
|
92
|
+
this.drawioNames = new Set(opts.drawioNames);
|
|
90
93
|
this.readable = opts.readable;
|
|
91
94
|
this.localFiles = opts.localFiles;
|
|
92
95
|
this.tablesAsRecords = opts.tablesAsRecords;
|
|
@@ -347,6 +350,22 @@ class Converter {
|
|
|
347
350
|
const pmap = new Map(params.map((p) => [p.name, p.value]));
|
|
348
351
|
const oneline = (v) => v !== undefined && !/[\n|{}]/.test(v) && !v.includes('}}');
|
|
349
352
|
let candidate = null;
|
|
353
|
+
if ((name === 'drawio' || name === 'inc-drawio') && richBody === null) {
|
|
354
|
+
const filename = pmap.get('diagramName');
|
|
355
|
+
if (!filename || !oneline(filename) || /[/\\]/.test(filename))
|
|
356
|
+
return null;
|
|
357
|
+
if (params.some(p => !oneline(p.value) || /[|{}\n=]/.test(p.name)))
|
|
358
|
+
return null;
|
|
359
|
+
const attrs = params.filter(p => p.name !== 'diagramName').map(p => `|${p.name}=${p.value}`).join('');
|
|
360
|
+
candidate = `{{drawio:${filename}${attrs}}}`;
|
|
361
|
+
if (name === 'inc-drawio')
|
|
362
|
+
this.stats.normalized++;
|
|
363
|
+
else if (!this.readable && !this.verifyMacroMarker(el, candidate))
|
|
364
|
+
return null;
|
|
365
|
+
this.files.add(filename);
|
|
366
|
+
this.images.add(`${filename}.png`);
|
|
367
|
+
return candidate;
|
|
368
|
+
}
|
|
350
369
|
const tag = Converter.NATIVE_ADMONITION_TAG[name];
|
|
351
370
|
if (tag && richBody !== null) {
|
|
352
371
|
// Панель: параметры — только title.
|
|
@@ -1216,6 +1235,18 @@ class Converter {
|
|
|
1216
1235
|
if (kids.length !== 1)
|
|
1217
1236
|
throw new Unrepresentable();
|
|
1218
1237
|
const ref = kids[0];
|
|
1238
|
+
const filename = getAttr(ref, 'ri:filename') ?? '';
|
|
1239
|
+
const stem = filename.replace(/\.png$/i, '');
|
|
1240
|
+
const drawioName = getAttr(el, 'ac:alt')?.match(/^drawio:(.+)$/)?.[1]
|
|
1241
|
+
?? (stem !== filename && (this.drawioNames.has(stem) || /\.drawio$/i.test(stem)) ? stem : undefined);
|
|
1242
|
+
if (drawioName && ref.name === 'ri:attachment' && !/[{}|\n/\\]/.test(drawioName)) {
|
|
1243
|
+
const preview = getAttr(ref, 'ri:filename');
|
|
1244
|
+
if (preview && !/[{}|\n/\\]/.test(preview)) {
|
|
1245
|
+
this.files.add(drawioName);
|
|
1246
|
+
this.images.add(preview);
|
|
1247
|
+
return `{{drawio:${drawioName}|format=image${preview !== `${drawioName}.png` ? `|preview=${preview}` : ''}}}`;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1219
1250
|
// attachments:'local' — картинка как HTML <img> (сохраняет размеры).
|
|
1220
1251
|
// ri:attachment → локальный путь; ri:url → внешний URL как есть.
|
|
1221
1252
|
if (this.localFiles && (ref.name === 'ri:attachment' || ref.name === 'ri:url')) {
|
|
@@ -1264,7 +1295,6 @@ class Converter {
|
|
|
1264
1295
|
if (!ref.attrs.every(([k]) => k === 'ri:filename' || k === 'ri:version-at-save')) {
|
|
1265
1296
|
throw new Unrepresentable();
|
|
1266
1297
|
}
|
|
1267
|
-
const filename = getAttr(ref, 'ri:filename') ?? '';
|
|
1268
1298
|
const attrs = [];
|
|
1269
1299
|
for (const [k] of el.attrs) {
|
|
1270
1300
|
if (!k.startsWith('ac:'))
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { authHeader, loadConfigFromEnv, type ConfluenceAuthType, type Confluence
|
|
|
3
3
|
export { Markdown } from './markdown/markdown.js';
|
|
4
4
|
export { renderToStorage, extractPlaceholders, parsePlaceholder, renameImagePlaceholders, MissingAttachmentUrlError, PLACEHOLDER_RE, type AttachmentUrls, type ExtractedPlaceholders, type PlaceholderRef, type RenderStorageOptions, } from './markdown/render.js';
|
|
5
5
|
export { validateMarkdown, MarkdownValidationError, type ValidateOptions } from './markdown/validate.js';
|
|
6
|
+
export { drawioReferences, drawioAttachmentNames, type DrawioReference } from './markdown/drawio.js';
|
|
6
7
|
export { nativeToMarkers, nativeMacroList, NATIVE_ADMONITIONS, NATIVE_DIRECTIVES, NATIVE_PLACEHOLDERS, } from './markdown/native.js';
|
|
7
8
|
export * from './macros/index.js';
|
|
8
9
|
export { convertBpmn, convertBpmnFolder, isBpmnFile, bpmnOutputName, BPMN_FILE_RE, type BpmnConversion, type BpmnImageFormat, type ConvertBpmnFolderOptions, } from './bpmn/convert.js';
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { authHeader, loadConfigFromEnv, } from './client/config.js';
|
|
|
5
5
|
export { Markdown } from './markdown/markdown.js';
|
|
6
6
|
export { renderToStorage, extractPlaceholders, parsePlaceholder, renameImagePlaceholders, MissingAttachmentUrlError, PLACEHOLDER_RE, } from './markdown/render.js';
|
|
7
7
|
export { validateMarkdown, MarkdownValidationError } from './markdown/validate.js';
|
|
8
|
+
export { drawioReferences, drawioAttachmentNames } from './markdown/drawio.js';
|
|
8
9
|
export { nativeToMarkers, nativeMacroList, NATIVE_ADMONITIONS, NATIVE_DIRECTIVES, NATIVE_PLACEHOLDERS, } from './markdown/native.js';
|
|
9
10
|
// Macros (pluggable)
|
|
10
11
|
export * from './macros/index.js';
|
package/dist/macros/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { coreMacrosPlugin, extractPlainText } from './plugins/core.js';
|
|
|
6
6
|
export { tableFilterPlugin, TABLE_FILTER_DEFAULTS } from './plugins/table-filter.js';
|
|
7
7
|
export { csvTablePlugin, CSV_TABLE_DEFAULTS } from './plugins/csv-table.js';
|
|
8
8
|
export { portfolioForJiraPlanPlugin } from './plugins/portfolio-for-jira-plan.js';
|
|
9
|
+
export { drawioPlugin } from './plugins/drawio.js';
|
|
9
10
|
import { MacroRegistry } from './registry.js';
|
|
10
11
|
import { anchor, children, codeBlock, excerpt, excerptInclude, expand, includePage, jiraIssue, panel, status, toc } from './plugins/core.js';
|
|
11
12
|
import { tableExcerpt, tableExcerptInclude, tableFilter, tableJoiner } from './plugins/table-filter.js';
|
|
@@ -48,4 +49,5 @@ export declare const macros: {
|
|
|
48
49
|
tableJoiner: typeof tableJoiner;
|
|
49
50
|
csvTable: typeof csvTable;
|
|
50
51
|
portfolioForJiraPlan: typeof portfolioForJiraPlan;
|
|
52
|
+
drawio: (name: string, opts?: Record<string, string | undefined>) => import("../index.js").Markdown;
|
|
51
53
|
};
|
package/dist/macros/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { coreMacrosPlugin, extractPlainText } from './plugins/core.js';
|
|
|
6
6
|
export { tableFilterPlugin, TABLE_FILTER_DEFAULTS } from './plugins/table-filter.js';
|
|
7
7
|
export { csvTablePlugin, CSV_TABLE_DEFAULTS } from './plugins/csv-table.js';
|
|
8
8
|
export { portfolioForJiraPlanPlugin } from './plugins/portfolio-for-jira-plan.js';
|
|
9
|
+
export { drawioPlugin } from './plugins/drawio.js';
|
|
9
10
|
import { MacroRegistry } from './registry.js';
|
|
10
11
|
import { coreMacrosPlugin } from './plugins/core.js';
|
|
11
12
|
import { tableFilterPlugin } from './plugins/table-filter.js';
|
|
@@ -15,13 +16,15 @@ import { anchor, children, codeBlock, excerpt, excerptInclude, expand, includePa
|
|
|
15
16
|
import { tableExcerpt, tableExcerptInclude, tableFilter, tableJoiner } from './plugins/table-filter.js';
|
|
16
17
|
import { csvTable } from './plugins/csv-table.js';
|
|
17
18
|
import { portfolioForJiraPlan } from './plugins/portfolio-for-jira-plan.js';
|
|
19
|
+
import { drawio, drawioPlugin } from './plugins/drawio.js';
|
|
18
20
|
/** Creates a registry pre-loaded with all built-in plugins. */
|
|
19
21
|
export function createDefaultRegistry() {
|
|
20
22
|
return new MacroRegistry()
|
|
21
23
|
.use(coreMacrosPlugin)
|
|
22
24
|
.use(tableFilterPlugin)
|
|
23
25
|
.use(csvTablePlugin)
|
|
24
|
-
.use(portfolioForJiraPlanPlugin)
|
|
26
|
+
.use(portfolioForJiraPlanPlugin)
|
|
27
|
+
.use(drawioPlugin);
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
27
30
|
* Глобальный реестр по умолчанию (core + table-filter). Используется,
|
|
@@ -60,4 +63,5 @@ export const macros = {
|
|
|
60
63
|
tableJoiner,
|
|
61
64
|
csvTable,
|
|
62
65
|
portfolioForJiraPlan,
|
|
66
|
+
drawio,
|
|
63
67
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type MacroPlugin } from '../types.js';
|
|
2
|
+
/** XML keeps its attachment name, including extensionless Confluence diagrams. */
|
|
3
|
+
export declare const drawioPlugin: MacroPlugin;
|
|
4
|
+
export declare const drawio: (name: string, opts?: Record<string, string | undefined>) => import("../../index.js").Markdown;
|
|
5
|
+
/** Published attachments belong to the target page and use its latest version. */
|
|
6
|
+
export declare function unpinDrawioStorage(storage: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { macro } from '../builder.js';
|
|
2
|
+
import { paramMap } from '../types.js';
|
|
3
|
+
import { escapeXmlAttr, structuredMacro } from '../xml.js';
|
|
4
|
+
/** XML keeps its attachment name, including extensionless Confluence diagrams. */
|
|
5
|
+
export const drawioPlugin = {
|
|
6
|
+
name: 'drawio',
|
|
7
|
+
macros: [{ name: 'drawio', render(ctx) {
|
|
8
|
+
const params = paramMap(ctx.params);
|
|
9
|
+
const name = params.diagramName;
|
|
10
|
+
if (!name)
|
|
11
|
+
throw new Error('drawio: diagramName is required');
|
|
12
|
+
if (params.format === 'image') {
|
|
13
|
+
return `<ac:image><ri:attachment ri:filename="${escapeXmlAttr(params.preview || `${name}.png`)}" /></ac:image>`;
|
|
14
|
+
}
|
|
15
|
+
if (params.format && params.format !== 'macro')
|
|
16
|
+
throw new Error('drawio: format must be macro or image');
|
|
17
|
+
return structuredMacro('drawio', ctx.macroId, { params: ctx.params.filter(p => p.name !== 'format' && p.name !== 'preview').sort((a, b) => a.name.localeCompare(b.name)) });
|
|
18
|
+
} }],
|
|
19
|
+
};
|
|
20
|
+
export const drawio = (name, opts = {}) => macro('drawio').param('diagramName', name).withParams(opts).toMarkdown();
|
|
21
|
+
/** Published attachments belong to the target page and use its latest version. */
|
|
22
|
+
export function unpinDrawioStorage(storage) {
|
|
23
|
+
return storage.replace(/<ac:structured-macro\b[^>]*ac:name="drawio"[^>]*>[\s\S]*?<\/ac:structured-macro>/g, block => block.replace(/<ac:parameter ac:name="(?:revision|pageId)">[\s\S]*?<\/ac:parameter>/g, ''));
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface DrawioReference {
|
|
2
|
+
name: string;
|
|
3
|
+
preview: string;
|
|
4
|
+
format: 'macro' | 'image';
|
|
5
|
+
revision?: string;
|
|
6
|
+
pageId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function drawioAttachmentNames(attachments: {
|
|
9
|
+
title: string;
|
|
10
|
+
extensions?: {
|
|
11
|
+
mediaType?: string;
|
|
12
|
+
};
|
|
13
|
+
}[]): string[];
|
|
14
|
+
/** Skip code examples; ordinary and table-cell references remain attachments. */
|
|
15
|
+
export declare function drawioReferences(markdown: string): DrawioReference[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
import { parsePlaceholder } from './render.js';
|
|
3
|
+
export function drawioAttachmentNames(attachments) {
|
|
4
|
+
return attachments.filter(a => a.extensions?.mediaType === 'application/vnd.jgraph.mxfile' || /\.drawio$/i.test(a.title)).map(a => a.title);
|
|
5
|
+
}
|
|
6
|
+
/** Skip code examples; ordinary and table-cell references remain attachments. */
|
|
7
|
+
export function drawioReferences(markdown) {
|
|
8
|
+
const refs = [];
|
|
9
|
+
const tokens = new MarkdownIt().parse(markdown, {});
|
|
10
|
+
for (const token of tokens)
|
|
11
|
+
for (const child of token.children ?? []) {
|
|
12
|
+
if (child.type !== 'text')
|
|
13
|
+
continue;
|
|
14
|
+
for (const match of child.content.matchAll(/\{\{drawio:([^}]+)\}\}/g)) {
|
|
15
|
+
const { name, attrs } = parsePlaceholder(match[1]);
|
|
16
|
+
const opts = Object.fromEntries(attrs);
|
|
17
|
+
if (!name || /[/\\]/.test(name) || name === '.' || name === '..')
|
|
18
|
+
throw new Error('drawio: invalid attachment filename');
|
|
19
|
+
if (opts.format && !['macro', 'image'].includes(opts.format))
|
|
20
|
+
throw new Error('drawio: format must be macro or image');
|
|
21
|
+
const preview = opts.preview || `${name}.png`;
|
|
22
|
+
if (/[/\\]/.test(preview) || preview === '.' || preview === '..')
|
|
23
|
+
throw new Error('drawio: invalid preview filename');
|
|
24
|
+
refs.push({ name, preview, format: opts.format === 'image' ? 'image' : 'macro', revision: opts.revision, pageId: opts.pageId });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return refs;
|
|
28
|
+
}
|
package/dist/markdown/native.js
CHANGED
|
@@ -45,6 +45,7 @@ export const NATIVE_DIRECTIVES = {
|
|
|
45
45
|
};
|
|
46
46
|
/** Строчные плейсхолдеры: имя → имя макроса. */
|
|
47
47
|
export const NATIVE_PLACEHOLDERS = {
|
|
48
|
+
drawio: 'drawio',
|
|
48
49
|
toc: 'toc',
|
|
49
50
|
children: 'children',
|
|
50
51
|
jira: 'jira',
|
|
@@ -56,6 +57,7 @@ export const NATIVE_PLACEHOLDERS = {
|
|
|
56
57
|
/** Полный перечень макросов с нативной md-разметкой (для документации/UI). */
|
|
57
58
|
export function nativeMacroList() {
|
|
58
59
|
return [
|
|
60
|
+
{ macro: 'drawio', syntax: '{{drawio:diagram.drawio|format=macro}} | {{drawio:diagram.drawio|format=image}}' },
|
|
59
61
|
{ macro: 'info', syntax: '> [!INFO] Заголовок?' },
|
|
60
62
|
{ macro: 'note', syntax: '> [!NOTE] Заголовок?' },
|
|
61
63
|
{ macro: 'warning', syntax: '> [!WARNING] Заголовок?' },
|
|
@@ -75,7 +77,7 @@ export function nativeMacroList() {
|
|
|
75
77
|
const ADMONITION_FIRST_RE = /^>\s*\[!([A-Za-z]+)\]\s*(.*)$/;
|
|
76
78
|
const DIRECTIVE_OPEN_RE = /^:::\s+([a-z-]+)(?:\s+(.*?))?\s*$/;
|
|
77
79
|
const DIRECTIVE_CLOSE_RE = /^:::\s*$/;
|
|
78
|
-
const PLACEHOLDER_INLINE_RE = /\{\{(toc|children|jira|status|anchor|properties-report|portfolio-for-jira-plan)(?::((?:[^{}]|\{[^{])*?))?\}\}/g;
|
|
80
|
+
const PLACEHOLDER_INLINE_RE = /\{\{(drawio|toc|children|jira|status|anchor|properties-report|portfolio-for-jira-plan)(?::((?:[^{}]|\{[^{])*?))?\}\}/g;
|
|
79
81
|
const FENCE_RE = /^\s*(`{3,}|~{3,})/;
|
|
80
82
|
/** Компактная пара маркеров без тела — для строчного контекста. */
|
|
81
83
|
function inlineMarker(macroName, params) {
|
|
@@ -108,6 +110,8 @@ function placeholderParams(kind, raw) {
|
|
|
108
110
|
if (raw === undefined || raw.trim() === '')
|
|
109
111
|
return [];
|
|
110
112
|
const { head, attrs } = splitAttrs(raw);
|
|
113
|
+
if (kind === 'drawio')
|
|
114
|
+
return [{ name: 'diagramName', value: head }, ...attrs];
|
|
111
115
|
const params = [];
|
|
112
116
|
const headIsPair = head.includes('=');
|
|
113
117
|
if (headIsPair) {
|
|
@@ -206,6 +210,7 @@ export function nativeToMarkers(src) {
|
|
|
206
210
|
let j = i + 1;
|
|
207
211
|
let innerFence = null;
|
|
208
212
|
let closed = false;
|
|
213
|
+
let depth = 1;
|
|
209
214
|
for (; j < lines.length; j++) {
|
|
210
215
|
const bl = lines[j];
|
|
211
216
|
const bfm = FENCE_RE.exec(bl);
|
|
@@ -215,9 +220,14 @@ export function nativeToMarkers(src) {
|
|
|
215
220
|
else if (bfm[1][0] === innerFence)
|
|
216
221
|
innerFence = null;
|
|
217
222
|
}
|
|
218
|
-
else if (innerFence === null
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
else if (innerFence === null) {
|
|
224
|
+
const nested = DIRECTIVE_OPEN_RE.exec(bl);
|
|
225
|
+
if (nested && NATIVE_DIRECTIVES[nested[1]])
|
|
226
|
+
depth++;
|
|
227
|
+
else if (DIRECTIVE_CLOSE_RE.test(bl) && --depth === 0) {
|
|
228
|
+
closed = true;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
221
231
|
}
|
|
222
232
|
body.push(bl);
|
|
223
233
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
2
|
import { extractPlaceholders } from './render.js';
|
|
3
|
+
import { drawioReferences } from './drawio.js';
|
|
3
4
|
export class MarkdownValidationError extends Error {
|
|
4
5
|
constructor(message) {
|
|
5
6
|
super(message);
|
|
@@ -8,6 +9,10 @@ export class MarkdownValidationError extends Error {
|
|
|
8
9
|
}
|
|
9
10
|
export function validateMarkdown(opts) {
|
|
10
11
|
const { images, files } = extractPlaceholders(opts.markdown);
|
|
12
|
+
for (const diagram of drawioReferences(opts.markdown)) {
|
|
13
|
+
files.push(diagram.name);
|
|
14
|
+
images.push(diagram.preview);
|
|
15
|
+
}
|
|
11
16
|
const tables = extractTablePlaceholders(opts.markdown);
|
|
12
17
|
const providedImages = new Set(opts.imagePaths.map((p) => basename(p)));
|
|
13
18
|
const providedFiles = new Set(opts.filePaths.map((p) => basename(p)));
|
package/dist/publish/publish.js
CHANGED
|
@@ -12,6 +12,7 @@ import { applyManagedNotice } from './notice.js';
|
|
|
12
12
|
import { processMacros } from '../macros/registry.js';
|
|
13
13
|
import { defaultMacroRegistry } from '../macros/index.js';
|
|
14
14
|
import { Markdown } from '../markdown/markdown.js';
|
|
15
|
+
import { unpinDrawioStorage } from '../macros/plugins/drawio.js';
|
|
15
16
|
// Content property с SHA-256 от рендеренного storage. Хранится отдельно
|
|
16
17
|
// от body через /rest/api/content/{id}/property/{key}, поэтому:
|
|
17
18
|
// - не попадает в HTML страницы (пользователь не видит);
|
|
@@ -200,6 +201,7 @@ export async function publishPage(opts, cfg) {
|
|
|
200
201
|
let storage = renderToStorage(markdown, urls, opts.render ?? {});
|
|
201
202
|
// 2.5. Преобразование маркеров макросов в XHTML.
|
|
202
203
|
storage = processMacros(storage, registry).toString();
|
|
204
|
+
storage = unpinDrawioStorage(storage);
|
|
203
205
|
// 2.6. Баннер «страница управляется извне». Вставляется здесь, в storage —
|
|
204
206
|
// не в markdown — поэтому источник/round-trip его не содержат. Баннер
|
|
205
207
|
// детерминирован (фиксированный macro-id), значит попадает в content-hash
|
package/package.json
CHANGED