confluence-md-sync 0.9.1 → 0.10.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.
|
@@ -336,6 +336,7 @@ class Converter {
|
|
|
336
336
|
info: 'INFO', note: 'NOTE', warning: 'WARNING', tip: 'TIP',
|
|
337
337
|
};
|
|
338
338
|
static NATIVE_PLACEHOLDER_NAME = {
|
|
339
|
+
search: 'search',
|
|
339
340
|
toc: 'toc', children: 'children', jira: 'jira', status: 'status',
|
|
340
341
|
anchor: 'anchor', detailssummary: 'properties-report',
|
|
341
342
|
'portfolio-for-jira-plan': 'portfolio-for-jira-plan',
|
|
@@ -432,8 +433,7 @@ class Converter {
|
|
|
432
433
|
const attrs = [];
|
|
433
434
|
let head = '';
|
|
434
435
|
for (const p of params) {
|
|
435
|
-
|
|
436
|
-
return null;
|
|
436
|
+
const value = /[|{}\n\r]/.test(p.value) || /^"/.test(p.value) ? JSON.stringify(p.value) : p.value;
|
|
437
437
|
if (name === 'jira' && p.name === 'key' && head === '') {
|
|
438
438
|
head = p.value;
|
|
439
439
|
continue;
|
|
@@ -449,7 +449,7 @@ class Converter {
|
|
|
449
449
|
const attrName = name === 'jira' && p.name === 'jqlQuery' ? 'jql' : p.name;
|
|
450
450
|
if (!/^[A-Za-z-]+$/.test(attrName))
|
|
451
451
|
return null;
|
|
452
|
-
attrs.push(`${attrName}=${
|
|
452
|
+
attrs.push(`${attrName}=${value}`);
|
|
453
453
|
}
|
|
454
454
|
const inner = [head, ...attrs].filter((s, i) => s !== '' || i > 0).join('|');
|
|
455
455
|
candidate = inner === '' ? `{{${ph}}}` : `{{${ph}:${inner}}}`;
|
|
@@ -468,7 +468,9 @@ class Converter {
|
|
|
468
468
|
for (const child of elements(el.children)) {
|
|
469
469
|
if (child.name === 'ac:parameter') {
|
|
470
470
|
const pname = getAttr(child, 'ac:name') ?? '';
|
|
471
|
-
|
|
471
|
+
const space = getAttr(el, 'ac:name') === 'search' && pname === 'spacekey'
|
|
472
|
+
? elements(child.children).find(n => n.name === 'ri:space') : undefined;
|
|
473
|
+
params.push({ name: pname, value: space ? getAttr(space, 'ri:space-key') ?? '' : decodeEntities(textContent(child.children)) });
|
|
472
474
|
}
|
|
473
475
|
else if (child.name === 'ac:rich-text-body')
|
|
474
476
|
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