confluence-md-sync 0.10.1 → 0.11.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.
@@ -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);
@@ -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
  };
@@ -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();
@@ -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 = [];
@@ -0,0 +1,5 @@
1
+ export declare function structurizrReferences(markdown: string): {
2
+ name: string;
3
+ json: string;
4
+ preview: string;
5
+ }[];
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-md-sync",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "Publish Markdown to Confluence (Data Center & Cloud): idempotent page sync, attachment dedup, tables and a pluggable macro system",
5
5
  "keywords": [
6
6
  "confluence",