confluence-md-sync 0.9.1 → 0.10.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.
|
@@ -140,6 +140,14 @@ class Converter {
|
|
|
140
140
|
if (meaningful.length === 1 && meaningful[0].kind === 'el' && meaningful[0].name === 'ac:structured-macro') {
|
|
141
141
|
return this.macroToMd(meaningful[0]);
|
|
142
142
|
}
|
|
143
|
+
if (meaningful.length > 1 && meaningful.every(n => n.kind === 'el' && n.name === 'ac:structured-macro')) {
|
|
144
|
+
const native = meaningful.map(n => this.tryNativeMd(n, getAttr(n, 'ac:name') ?? ''));
|
|
145
|
+
if (native.every(s => s?.startsWith('{{') && !s.includes('\n'))) {
|
|
146
|
+
this.stats.native += native.length;
|
|
147
|
+
this.stats.normalized++; // A paragraph around block macros is redundant in Confluence.
|
|
148
|
+
return native.join(' ');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
143
151
|
return this.paragraphMd(el.children);
|
|
144
152
|
}
|
|
145
153
|
if (el.name === 'hr')
|
|
@@ -336,6 +344,7 @@ class Converter {
|
|
|
336
344
|
info: 'INFO', note: 'NOTE', warning: 'WARNING', tip: 'TIP',
|
|
337
345
|
};
|
|
338
346
|
static NATIVE_PLACEHOLDER_NAME = {
|
|
347
|
+
search: 'search',
|
|
339
348
|
toc: 'toc', children: 'children', jira: 'jira', status: 'status',
|
|
340
349
|
anchor: 'anchor', detailssummary: 'properties-report',
|
|
341
350
|
'portfolio-for-jira-plan': 'portfolio-for-jira-plan',
|
|
@@ -432,8 +441,7 @@ class Converter {
|
|
|
432
441
|
const attrs = [];
|
|
433
442
|
let head = '';
|
|
434
443
|
for (const p of params) {
|
|
435
|
-
|
|
436
|
-
return null;
|
|
444
|
+
const value = /[|{}\n\r]/.test(p.value) || /^"/.test(p.value) ? JSON.stringify(p.value) : p.value;
|
|
437
445
|
if (name === 'jira' && p.name === 'key' && head === '') {
|
|
438
446
|
head = p.value;
|
|
439
447
|
continue;
|
|
@@ -449,7 +457,7 @@ class Converter {
|
|
|
449
457
|
const attrName = name === 'jira' && p.name === 'jqlQuery' ? 'jql' : p.name;
|
|
450
458
|
if (!/^[A-Za-z-]+$/.test(attrName))
|
|
451
459
|
return null;
|
|
452
|
-
attrs.push(`${attrName}=${
|
|
460
|
+
attrs.push(`${attrName}=${value}`);
|
|
453
461
|
}
|
|
454
462
|
const inner = [head, ...attrs].filter((s, i) => s !== '' || i > 0).join('|');
|
|
455
463
|
candidate = inner === '' ? `{{${ph}}}` : `{{${ph}:${inner}}}`;
|
|
@@ -468,7 +476,9 @@ class Converter {
|
|
|
468
476
|
for (const child of elements(el.children)) {
|
|
469
477
|
if (child.name === 'ac:parameter') {
|
|
470
478
|
const pname = getAttr(child, 'ac:name') ?? '';
|
|
471
|
-
|
|
479
|
+
const space = getAttr(el, 'ac:name') === 'search' && pname === 'spacekey'
|
|
480
|
+
? elements(child.children).find(n => n.name === 'ri:space') : undefined;
|
|
481
|
+
params.push({ name: pname, value: space ? getAttr(space, 'ri:space-key') ?? '' : decodeEntities(textContent(child.children)) });
|
|
472
482
|
}
|
|
473
483
|
else if (child.name === 'ac:rich-text-body')
|
|
474
484
|
richBody = child;
|
package/dist/macros/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { tableFilterPlugin, TABLE_FILTER_DEFAULTS } from './plugins/table-filter
|
|
|
7
7
|
export { csvTablePlugin, CSV_TABLE_DEFAULTS } from './plugins/csv-table.js';
|
|
8
8
|
export { portfolioForJiraPlanPlugin } from './plugins/portfolio-for-jira-plan.js';
|
|
9
9
|
export { drawioPlugin } from './plugins/drawio.js';
|
|
10
|
+
export { searchPlugin } from './plugins/search.js';
|
|
10
11
|
import { MacroRegistry } from './registry.js';
|
|
11
12
|
import { anchor, children, codeBlock, excerpt, excerptInclude, expand, includePage, jiraIssue, panel, status, toc } from './plugins/core.js';
|
|
12
13
|
import { tableExcerpt, tableExcerptInclude, tableFilter, tableJoiner } from './plugins/table-filter.js';
|
|
@@ -50,4 +51,5 @@ export declare const macros: {
|
|
|
50
51
|
csvTable: typeof csvTable;
|
|
51
52
|
portfolioForJiraPlan: typeof portfolioForJiraPlan;
|
|
52
53
|
drawio: (name: string, opts?: Record<string, string | undefined>) => import("../index.js").Markdown;
|
|
54
|
+
search: (query: string, options?: Record<string, string | undefined>) => import("../index.js").Markdown;
|
|
53
55
|
};
|
package/dist/macros/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { tableFilterPlugin, TABLE_FILTER_DEFAULTS } from './plugins/table-filter
|
|
|
7
7
|
export { csvTablePlugin, CSV_TABLE_DEFAULTS } from './plugins/csv-table.js';
|
|
8
8
|
export { portfolioForJiraPlanPlugin } from './plugins/portfolio-for-jira-plan.js';
|
|
9
9
|
export { drawioPlugin } from './plugins/drawio.js';
|
|
10
|
+
export { searchPlugin } from './plugins/search.js';
|
|
10
11
|
import { MacroRegistry } from './registry.js';
|
|
11
12
|
import { coreMacrosPlugin } from './plugins/core.js';
|
|
12
13
|
import { tableFilterPlugin } from './plugins/table-filter.js';
|
|
@@ -17,6 +18,7 @@ import { tableExcerpt, tableExcerptInclude, tableFilter, tableJoiner } from './p
|
|
|
17
18
|
import { csvTable } from './plugins/csv-table.js';
|
|
18
19
|
import { portfolioForJiraPlan } from './plugins/portfolio-for-jira-plan.js';
|
|
19
20
|
import { drawio, drawioPlugin } from './plugins/drawio.js';
|
|
21
|
+
import { search, searchPlugin } from './plugins/search.js';
|
|
20
22
|
/** Creates a registry pre-loaded with all built-in plugins. */
|
|
21
23
|
export function createDefaultRegistry() {
|
|
22
24
|
return new MacroRegistry()
|
|
@@ -24,7 +26,8 @@ export function createDefaultRegistry() {
|
|
|
24
26
|
.use(tableFilterPlugin)
|
|
25
27
|
.use(csvTablePlugin)
|
|
26
28
|
.use(portfolioForJiraPlanPlugin)
|
|
27
|
-
.use(drawioPlugin)
|
|
29
|
+
.use(drawioPlugin)
|
|
30
|
+
.use(searchPlugin);
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
30
33
|
* Глобальный реестр по умолчанию (core + table-filter). Используется,
|
|
@@ -64,4 +67,5 @@ export const macros = {
|
|
|
64
67
|
csvTable,
|
|
65
68
|
portfolioForJiraPlan,
|
|
66
69
|
drawio,
|
|
70
|
+
search,
|
|
67
71
|
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MacroPlugin } from '../types.js';
|
|
2
|
+
/** Search Results uses a space reference, not a text parameter, in storage. */
|
|
3
|
+
export declare const searchPlugin: MacroPlugin;
|
|
4
|
+
export declare const search: (query: string, options?: Record<string, string | undefined>) => import("../../index.js").Markdown;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { macro } from '../builder.js';
|
|
2
|
+
import { escapeXmlAttr, structuredMacro } from '../xml.js';
|
|
3
|
+
/** Search Results uses a space reference, not a text parameter, in storage. */
|
|
4
|
+
export const searchPlugin = {
|
|
5
|
+
name: 'search',
|
|
6
|
+
macros: [{ name: 'search', render: ctx => structuredMacro('search', ctx.macroId, {
|
|
7
|
+
params: [...ctx.params].sort((a, b) => a.name.localeCompare(b.name)).map(p => p.name === 'spacekey' && p.value
|
|
8
|
+
? { ...p, value: `<ri:space ri:space-key="${escapeXmlAttr(p.value)}" />`, raw: true }
|
|
9
|
+
: p),
|
|
10
|
+
}) }],
|
|
11
|
+
};
|
|
12
|
+
export const search = (query, options = {}) => macro('search').param('query', query).withParams(options).toMarkdown();
|
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
|
+
search: 'search',
|
|
48
49
|
drawio: 'drawio',
|
|
49
50
|
toc: 'toc',
|
|
50
51
|
children: 'children',
|
|
@@ -57,6 +58,7 @@ export const NATIVE_PLACEHOLDERS = {
|
|
|
57
58
|
/** Полный перечень макросов с нативной md-разметкой (для документации/UI). */
|
|
58
59
|
export function nativeMacroList() {
|
|
59
60
|
return [
|
|
61
|
+
{ macro: 'search', syntax: '{{search:query=…|spacekey=…|type=page|maxLimit=10}}' },
|
|
60
62
|
{ macro: 'drawio', syntax: '{{drawio:diagram.drawio|format=macro}} | {{drawio:diagram.drawio|format=image}}' },
|
|
61
63
|
{ macro: 'info', syntax: '> [!INFO] Заголовок?' },
|
|
62
64
|
{ macro: 'note', syntax: '> [!NOTE] Заголовок?' },
|
|
@@ -77,7 +79,7 @@ export function nativeMacroList() {
|
|
|
77
79
|
const ADMONITION_FIRST_RE = /^>\s*\[!([A-Za-z]+)\]\s*(.*)$/;
|
|
78
80
|
const DIRECTIVE_OPEN_RE = /^:::\s+([a-z-]+)(?:\s+(.*?))?\s*$/;
|
|
79
81
|
const DIRECTIVE_CLOSE_RE = /^:::\s*$/;
|
|
80
|
-
const PLACEHOLDER_INLINE_RE = /\{\{(drawio|toc|children|jira|status|anchor|properties-report|portfolio-for-jira-plan)(?::((?:[^{}]|\{[^{])*?))?\}\}/g;
|
|
82
|
+
const PLACEHOLDER_INLINE_RE = /\{\{(search|drawio|toc|children|jira|status|anchor|properties-report|portfolio-for-jira-plan)(?::((?:"(?:\\.|[^"\\])*"|[^{}"]|\{[^{])*?))?\}\}/g;
|
|
81
83
|
const FENCE_RE = /^\s*(`{3,}|~{3,})/;
|
|
82
84
|
/** Компактная пара маркеров без тела — для строчного контекста. */
|
|
83
85
|
function inlineMarker(macroName, params) {
|
|
@@ -94,16 +96,26 @@ function blockMarker(macroName, params, body) {
|
|
|
94
96
|
}
|
|
95
97
|
/** `a|b=c|d=e` → первый сегмент + пары; поведение как у parsePlaceholder. */
|
|
96
98
|
function splitAttrs(raw) {
|
|
97
|
-
const parts = raw.
|
|
99
|
+
const parts = raw.match(/(?:"(?:\\.|[^"\\])*"|[^|"])+/g) ?? [];
|
|
100
|
+
const value = (text) => {
|
|
101
|
+
const trimmed = text.trim();
|
|
102
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(trimmed);
|
|
105
|
+
}
|
|
106
|
+
catch { /* Unquoted legacy values remain literal. */ }
|
|
107
|
+
}
|
|
108
|
+
return trimmed;
|
|
109
|
+
};
|
|
98
110
|
const attrs = [];
|
|
99
111
|
for (const part of parts.slice(1)) {
|
|
100
112
|
const eq = part.indexOf('=');
|
|
101
113
|
if (eq === -1)
|
|
102
114
|
attrs.push({ name: part.trim(), value: '' });
|
|
103
115
|
else
|
|
104
|
-
attrs.push({ name: part.slice(0, eq).trim(), value: part.slice(eq + 1)
|
|
116
|
+
attrs.push({ name: part.slice(0, eq).trim(), value: value(part.slice(eq + 1)) });
|
|
105
117
|
}
|
|
106
|
-
return { head: parts[0]
|
|
118
|
+
return { head: parts[0]?.trim() ?? '', attrs };
|
|
107
119
|
}
|
|
108
120
|
/** Параметры плейсхолдера → параметры макроса Confluence. */
|
|
109
121
|
function placeholderParams(kind, raw) {
|
|
@@ -116,9 +128,21 @@ function placeholderParams(kind, raw) {
|
|
|
116
128
|
const headIsPair = head.includes('=');
|
|
117
129
|
if (headIsPair) {
|
|
118
130
|
const eq = head.indexOf('=');
|
|
119
|
-
|
|
131
|
+
const v = head.slice(eq + 1).trim();
|
|
132
|
+
let decoded = v;
|
|
133
|
+
if (v.startsWith('"') && v.endsWith('"')) {
|
|
134
|
+
try {
|
|
135
|
+
decoded = JSON.parse(v);
|
|
136
|
+
}
|
|
137
|
+
catch { /* Legacy unquoted values remain literal. */ }
|
|
138
|
+
}
|
|
139
|
+
attrs.unshift({ name: head.slice(0, eq).trim(), value: decoded });
|
|
120
140
|
}
|
|
121
141
|
switch (kind) {
|
|
142
|
+
case 'search':
|
|
143
|
+
if (!headIsPair && head !== '')
|
|
144
|
+
params.push({ name: 'query', value: head });
|
|
145
|
+
break;
|
|
122
146
|
case 'jira':
|
|
123
147
|
if (!headIsPair && head !== '')
|
|
124
148
|
params.push({ name: 'key', value: head });
|
|
@@ -154,12 +178,20 @@ function directiveParams(name, rest) {
|
|
|
154
178
|
if (!rest)
|
|
155
179
|
return params;
|
|
156
180
|
const free = [];
|
|
157
|
-
const re = /([A-Za-
|
|
181
|
+
const re = /([A-Za-z_][\w-]*)=("(?:\\.|[^"\\])*"|\S+)|(\S+)/g;
|
|
158
182
|
for (const m of rest.matchAll(re)) {
|
|
159
|
-
if (m[1])
|
|
160
|
-
|
|
183
|
+
if (m[1]) {
|
|
184
|
+
let value = m[2];
|
|
185
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
186
|
+
try {
|
|
187
|
+
value = JSON.parse(value);
|
|
188
|
+
}
|
|
189
|
+
catch { /* Preserve malformed legacy values as text. */ }
|
|
190
|
+
}
|
|
191
|
+
params.push({ name: m[1], value });
|
|
192
|
+
}
|
|
161
193
|
else
|
|
162
|
-
free.push(m[
|
|
194
|
+
free.push(m[3]);
|
|
163
195
|
}
|
|
164
196
|
if (free.length && name === 'expand')
|
|
165
197
|
params.unshift({ name: 'title', value: free.join(' ') });
|
package/package.json
CHANGED