confluence-md-sync 0.10.1 → 0.11.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/export/to-markdown.js +10 -0
- package/dist/macros/index.d.ts +2 -0
- package/dist/macros/index.js +4 -0
- package/dist/macros/plugins/structurizr.d.ts +5 -0
- package/dist/macros/plugins/structurizr.js +17 -0
- package/dist/markdown/native.js +5 -1
- package/dist/markdown/render.d.ts +6 -0
- package/dist/markdown/render.js +30 -12
- package/dist/markdown/structurizr.d.ts +5 -0
- package/dist/markdown/structurizr.js +16 -0
- package/dist/markdown/validate.js +5 -0
- package/package.json +1 -1
|
@@ -1246,6 +1246,16 @@ class Converter {
|
|
|
1246
1246
|
throw new Unrepresentable();
|
|
1247
1247
|
const ref = kids[0];
|
|
1248
1248
|
const filename = getAttr(ref, 'ri:filename') ?? '';
|
|
1249
|
+
const architecture = /^(.*\.dsl)\.((?:[0-9a-f]{2})+)\.png$/.exec(filename);
|
|
1250
|
+
if (architecture && ref.name === 'ri:attachment' && !/[{}|\n/\\]/.test(architecture[1])) {
|
|
1251
|
+
const view = new TextDecoder('utf-8').decode(Uint8Array.from(architecture[2].match(/../g), b => parseInt(b, 16)));
|
|
1252
|
+
if (view && !/[{}|\n\uFFFD]/.test(view)) {
|
|
1253
|
+
this.files.add(architecture[1]);
|
|
1254
|
+
this.files.add(architecture[1].replace(/\.dsl$/, '.json'));
|
|
1255
|
+
this.images.add(filename);
|
|
1256
|
+
return `{{structurizr:${architecture[1]}|view=${view}}}`;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1249
1259
|
const stem = filename.replace(/\.png$/i, '');
|
|
1250
1260
|
const drawioName = getAttr(el, 'ac:alt')?.match(/^drawio:(.+)$/)?.[1]
|
|
1251
1261
|
?? (stem !== filename && (this.drawioNames.has(stem) || /\.drawio$/i.test(stem)) ? stem : undefined);
|
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 { structurizrPlugin } from './plugins/structurizr.js';
|
|
9
10
|
export { drawioPlugin } from './plugins/drawio.js';
|
|
10
11
|
export { searchPlugin } from './plugins/search.js';
|
|
11
12
|
import { MacroRegistry } from './registry.js';
|
|
@@ -51,5 +52,6 @@ export declare const macros: {
|
|
|
51
52
|
csvTable: typeof csvTable;
|
|
52
53
|
portfolioForJiraPlan: typeof portfolioForJiraPlan;
|
|
53
54
|
drawio: (name: string, opts?: Record<string, string | undefined>) => import("../index.js").Markdown;
|
|
55
|
+
structurizr: (name: string, view: string) => import("../index.js").Markdown;
|
|
54
56
|
search: (query: string, options?: Record<string, string | undefined>) => import("../index.js").Markdown;
|
|
55
57
|
};
|
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 { structurizrPlugin } from './plugins/structurizr.js';
|
|
9
10
|
export { drawioPlugin } from './plugins/drawio.js';
|
|
10
11
|
export { searchPlugin } from './plugins/search.js';
|
|
11
12
|
import { MacroRegistry } from './registry.js';
|
|
@@ -17,6 +18,7 @@ import { anchor, children, codeBlock, excerpt, excerptInclude, expand, includePa
|
|
|
17
18
|
import { tableExcerpt, tableExcerptInclude, tableFilter, tableJoiner } from './plugins/table-filter.js';
|
|
18
19
|
import { csvTable } from './plugins/csv-table.js';
|
|
19
20
|
import { portfolioForJiraPlan } from './plugins/portfolio-for-jira-plan.js';
|
|
21
|
+
import { structurizr, structurizrPlugin } from './plugins/structurizr.js';
|
|
20
22
|
import { drawio, drawioPlugin } from './plugins/drawio.js';
|
|
21
23
|
import { search, searchPlugin } from './plugins/search.js';
|
|
22
24
|
/** Creates a registry pre-loaded with all built-in plugins. */
|
|
@@ -27,6 +29,7 @@ export function createDefaultRegistry() {
|
|
|
27
29
|
.use(csvTablePlugin)
|
|
28
30
|
.use(portfolioForJiraPlanPlugin)
|
|
29
31
|
.use(drawioPlugin)
|
|
32
|
+
.use(structurizrPlugin)
|
|
30
33
|
.use(searchPlugin);
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
@@ -67,5 +70,6 @@ export const macros = {
|
|
|
67
70
|
csvTable,
|
|
68
71
|
portfolioForJiraPlan,
|
|
69
72
|
drawio,
|
|
73
|
+
structurizr,
|
|
70
74
|
search,
|
|
71
75
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type MacroPlugin } from '../types.js';
|
|
2
|
+
export declare function structurizrPreview(name: string, view: string): string;
|
|
3
|
+
/** Confluence needs no third-party plugin: the view is a PNG, sources remain attachments. */
|
|
4
|
+
export declare const structurizrPlugin: MacroPlugin;
|
|
5
|
+
export declare const structurizr: (name: string, view: string) => import("../../index.js").Markdown;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { macro } from '../builder.js';
|
|
2
|
+
import { paramMap } from '../types.js';
|
|
3
|
+
import { escapeXmlAttr } from '../xml.js';
|
|
4
|
+
export function structurizrPreview(name, view) {
|
|
5
|
+
if (typeof name !== 'string' || !/\.dsl$/i.test(name) || /[/\\{}|]/.test(name) || !view || view.length > 120 || /[{}|\n\r]/.test(view))
|
|
6
|
+
throw new Error('structurizr: a .dsl attachment and view key are required');
|
|
7
|
+
return `${name}.${Array.from(new TextEncoder().encode(view), b => b.toString(16).padStart(2, '0')).join('')}.png`;
|
|
8
|
+
}
|
|
9
|
+
/** Confluence needs no third-party plugin: the view is a PNG, sources remain attachments. */
|
|
10
|
+
export const structurizrPlugin = {
|
|
11
|
+
name: 'structurizr',
|
|
12
|
+
macros: [{ name: 'structurizr', render(ctx) {
|
|
13
|
+
const { name, view } = paramMap(ctx.params);
|
|
14
|
+
return `<ac:image ac:alt="${escapeXmlAttr(`Structurizr · ${view}`)}"><ri:attachment ri:filename="${escapeXmlAttr(structurizrPreview(name, view))}" /></ac:image>`;
|
|
15
|
+
} }],
|
|
16
|
+
};
|
|
17
|
+
export const structurizr = (name, view) => macro('structurizr').param('name', name).param('view', view).toMarkdown();
|
package/dist/markdown/native.js
CHANGED
|
@@ -47,6 +47,7 @@ export const NATIVE_DIRECTIVES = {
|
|
|
47
47
|
export const NATIVE_PLACEHOLDERS = {
|
|
48
48
|
search: 'search',
|
|
49
49
|
drawio: 'drawio',
|
|
50
|
+
structurizr: 'structurizr',
|
|
50
51
|
toc: 'toc',
|
|
51
52
|
children: 'children',
|
|
52
53
|
jira: 'jira',
|
|
@@ -59,6 +60,7 @@ export const NATIVE_PLACEHOLDERS = {
|
|
|
59
60
|
export function nativeMacroList() {
|
|
60
61
|
return [
|
|
61
62
|
{ macro: 'search', syntax: '{{search:query=…|spacekey=…|type=page|maxLimit=10}}' },
|
|
63
|
+
{ macro: 'structurizr', syntax: '{{structurizr:workspace.dsl|view=containers}}' },
|
|
62
64
|
{ macro: 'drawio', syntax: '{{drawio:diagram.drawio|format=macro}} | {{drawio:diagram.drawio|format=image}}' },
|
|
63
65
|
{ macro: 'info', syntax: '> [!INFO] Заголовок?' },
|
|
64
66
|
{ macro: 'note', syntax: '> [!NOTE] Заголовок?' },
|
|
@@ -79,7 +81,7 @@ export function nativeMacroList() {
|
|
|
79
81
|
const ADMONITION_FIRST_RE = /^>\s*\[!([A-Za-z]+)\]\s*(.*)$/;
|
|
80
82
|
const DIRECTIVE_OPEN_RE = /^:::\s+([a-z-]+)(?:\s+(.*?))?\s*$/;
|
|
81
83
|
const DIRECTIVE_CLOSE_RE = /^:::\s*$/;
|
|
82
|
-
const PLACEHOLDER_INLINE_RE = /\{\{(search|drawio|toc|children|jira|status|anchor|properties-report|portfolio-for-jira-plan)(?::((?:"(?:\\.|[^"\\])*"|[^{}"]|\{[^{])*?))?\}\}/g;
|
|
84
|
+
const PLACEHOLDER_INLINE_RE = /\{\{(search|drawio|structurizr|toc|children|jira|status|anchor|properties-report|portfolio-for-jira-plan)(?::((?:"(?:\\.|[^"\\])*"|[^{}"]|\{[^{])*?))?\}\}/g;
|
|
83
85
|
const FENCE_RE = /^\s*(`{3,}|~{3,})/;
|
|
84
86
|
/** Компактная пара маркеров без тела — для строчного контекста. */
|
|
85
87
|
function inlineMarker(macroName, params) {
|
|
@@ -122,6 +124,8 @@ function placeholderParams(kind, raw) {
|
|
|
122
124
|
if (raw === undefined || raw.trim() === '')
|
|
123
125
|
return [];
|
|
124
126
|
const { head, attrs } = splitAttrs(raw);
|
|
127
|
+
if (kind === 'structurizr')
|
|
128
|
+
return [{ name: 'name', value: head }, ...attrs];
|
|
125
129
|
if (kind === 'drawio')
|
|
126
130
|
return [{ name: 'diagramName', value: head }, ...attrs];
|
|
127
131
|
const params = [];
|
|
@@ -55,6 +55,12 @@ export interface RenderStorageOptions {
|
|
|
55
55
|
title: string;
|
|
56
56
|
space?: string;
|
|
57
57
|
} | null;
|
|
58
|
+
/** Local Markdown assets: attachment on this page, or a canonical repository URL. */
|
|
59
|
+
resourceResolver?: (href: string) => {
|
|
60
|
+
attachment: string;
|
|
61
|
+
} | {
|
|
62
|
+
url: string;
|
|
63
|
+
} | null;
|
|
58
64
|
}
|
|
59
65
|
export declare class MissingAttachmentUrlError extends Error {
|
|
60
66
|
constructor(message: string);
|
package/dist/markdown/render.js
CHANGED
|
@@ -120,22 +120,40 @@ export function renderToStorage(markdown, urls, opts = {}) {
|
|
|
120
120
|
// манифеста). Только markdown-ссылки (`<a href>` из markdown-it); плейсхолдеры
|
|
121
121
|
// {{file:}}/{{img:}} рендерятся ниже и сюда ещё не попали. Внешние URL/якоря
|
|
122
122
|
// резолвер отсекает (возвращает null) — они остаются как есть.
|
|
123
|
-
if (opts.
|
|
124
|
-
|
|
125
|
-
html = html.replace(/<a href="([^"]*)">([\s\S]*?)<\/a>/g, (full, href, inner) => {
|
|
123
|
+
if (opts.resourceResolver) {
|
|
124
|
+
html = html.replace(/<img\b[^>]*\bsrc="([^"]*)"[^>]*\/?>/g, (full, href) => {
|
|
126
125
|
const decoded = unescapeHtml(href);
|
|
127
126
|
if (/^(?:[a-z][a-z0-9+.-]*:|#|\/\/)/i.test(decoded))
|
|
128
|
-
return full;
|
|
129
|
-
const target =
|
|
127
|
+
return full;
|
|
128
|
+
const target = opts.resourceResolver(decoded);
|
|
130
129
|
if (!target)
|
|
131
130
|
return full;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
if ('url' in target)
|
|
132
|
+
return full.replace(`src="${href}"`, `src="${escapeXmlAttr(target.url)}"`);
|
|
133
|
+
const alt = /\balt="([^"]*)"/.exec(full)?.[1];
|
|
134
|
+
return `<ac:image${alt ? ` ac:alt="${escapeXmlAttr(unescapeHtml(alt))}"` : ''}><ri:attachment ri:filename="${escapeXmlAttr(target.attachment)}" /></ac:image>`;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (opts.linkResolver || opts.resourceResolver) {
|
|
138
|
+
html = html.replace(/<a href="([^"]*)"(?:\s+title="[^"]*")?>([\s\S]*?)<\/a>/g, (full, href, inner) => {
|
|
139
|
+
const decoded = unescapeHtml(href);
|
|
140
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|#|\/\/)/i.test(decoded))
|
|
141
|
+
return full; // http(s):, mailto:, #anchor
|
|
142
|
+
const target = opts.linkResolver?.(decoded);
|
|
143
|
+
const body = /<[^>]+>/.test(inner)
|
|
144
|
+
? `<ac:link-body>${inner}</ac:link-body>`
|
|
145
|
+
: plainTextLinkBody(unescapeHtml(inner));
|
|
146
|
+
if (target) {
|
|
147
|
+
const space = target.space ? ` ri:space-key="${escapeXmlAttr(target.space)}"` : '';
|
|
148
|
+
const anchor = decoded.includes('#') ? ` ac:anchor="${escapeXmlAttr(decoded.slice(decoded.indexOf('#') + 1))}"` : '';
|
|
149
|
+
return `<ac:link${anchor}><ri:page ri:content-title="${escapeXmlAttr(target.title)}"${space} />${body}</ac:link>`;
|
|
150
|
+
}
|
|
151
|
+
const resource = opts.resourceResolver?.(decoded);
|
|
152
|
+
if (!resource)
|
|
153
|
+
return full;
|
|
154
|
+
if ('url' in resource)
|
|
155
|
+
return `<a href="${escapeXmlAttr(resource.url)}">${inner}</a>`;
|
|
156
|
+
return `<ac:link><ri:attachment ri:filename="${escapeXmlAttr(resource.attachment)}" />${body}</ac:link>`;
|
|
139
157
|
});
|
|
140
158
|
}
|
|
141
159
|
return html.replace(PLACEHOLDER_RE, (_full, kind, rawBody) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
import { parsePlaceholder } from './render.js';
|
|
3
|
+
import { structurizrPreview } from '../macros/plugins/structurizr.js';
|
|
4
|
+
export function structurizrReferences(markdown) {
|
|
5
|
+
const refs = [];
|
|
6
|
+
for (const token of new MarkdownIt().parse(markdown, {}))
|
|
7
|
+
for (const child of token.children ?? []) {
|
|
8
|
+
if (child.type !== 'text')
|
|
9
|
+
continue;
|
|
10
|
+
for (const match of child.content.matchAll(/\{\{structurizr:([^}]+)\}\}/g)) {
|
|
11
|
+
const { name, attrs } = parsePlaceholder(match[1]);
|
|
12
|
+
refs.push({ name, json: name.replace(/\.dsl$/, '.json'), preview: structurizrPreview(name, Object.fromEntries(attrs).view) });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return refs;
|
|
16
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
2
|
import { extractPlaceholders } from './render.js';
|
|
3
|
+
import { structurizrReferences } from './structurizr.js';
|
|
3
4
|
import { drawioReferences } from './drawio.js';
|
|
4
5
|
export class MarkdownValidationError extends Error {
|
|
5
6
|
constructor(message) {
|
|
@@ -13,6 +14,10 @@ export function validateMarkdown(opts) {
|
|
|
13
14
|
files.push(diagram.name);
|
|
14
15
|
images.push(diagram.preview);
|
|
15
16
|
}
|
|
17
|
+
for (const diagram of structurizrReferences(opts.markdown)) {
|
|
18
|
+
files.push(diagram.name, diagram.json);
|
|
19
|
+
images.push(diagram.preview);
|
|
20
|
+
}
|
|
16
21
|
const tables = extractTablePlaceholders(opts.markdown);
|
|
17
22
|
const providedImages = new Set(opts.imagePaths.map((p) => basename(p)));
|
|
18
23
|
const providedFiles = new Set(opts.filePaths.map((p) => basename(p)));
|
package/package.json
CHANGED