confluence-cli 2.1.0 → 2.1.2
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/lib/macro-converter.js +53 -30
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/lib/macro-converter.js
CHANGED
|
@@ -2,6 +2,11 @@ const MarkdownIt = require('markdown-it');
|
|
|
2
2
|
const { htmlToMarkdown } = require('./html-to-markdown');
|
|
3
3
|
|
|
4
4
|
const VALID_LINK_STYLES = ['smart', 'plain', 'wiki'];
|
|
5
|
+
const CALLOUT_MARKERS = ['info', 'warning', 'note'];
|
|
6
|
+
// U+E000 (Unicode Private Use Area) is used as the stash placeholder
|
|
7
|
+
// delimiter. Declared as an explicit escape so the byte is visible in source
|
|
8
|
+
// and survives editor / formatter / lint passes that strip invisible chars.
|
|
9
|
+
const STASH_DELIM = '\uE000';
|
|
5
10
|
|
|
6
11
|
class MacroConverter {
|
|
7
12
|
constructor({ isCloud = false, webUrlPrefix = '', buildUrl = null, linkStyle = null } = {}) {
|
|
@@ -23,23 +28,32 @@ class MacroConverter {
|
|
|
23
28
|
this.markdown.enable(['table', 'strikethrough', 'linkify']);
|
|
24
29
|
|
|
25
30
|
this.markdown.core.ruler.before('normalize', 'confluence_macros', (state) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return `> **WARNING**\n> ${content.trim().replace(/\n/g, '\n> ')}`;
|
|
31
|
+
// Stash fenced code blocks and inline code so the admonition rewrite
|
|
32
|
+
// below cannot transform `[!info]` tokens that the author intended as
|
|
33
|
+
// literal text inside code.
|
|
34
|
+
const stash = [];
|
|
35
|
+
state.src = state.src.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]+`/g, (m) => {
|
|
36
|
+
stash.push(m);
|
|
37
|
+
return `${STASH_DELIM}${stash.length - 1}${STASH_DELIM}`;
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
// Anchor `[!info]` to the start of a line (string start or after a
|
|
41
|
+
// newline) so prose mid-paragraph, headings on the same line, and
|
|
42
|
+
// `> [!info]` GitHub-style alerts are left alone. The latter would
|
|
43
|
+
// otherwise expand to a nested blockquote that the storage handler's
|
|
44
|
+
// lazy regex cannot balance, producing malformed XML.
|
|
45
|
+
for (const m of CALLOUT_MARKERS) {
|
|
46
|
+
const re = new RegExp(`(^|\\n)\\[!${m}\\]\\s*([\\s\\S]*?)(?=\\n\\s*\\n|\\n\\s*\\[!|$)`, 'g');
|
|
47
|
+
state.src = state.src.replace(re, (_, pre, content) =>
|
|
48
|
+
`${pre}> **${m.toUpperCase()}**\n> ${content.trim().replace(/\n/g, '\n> ')}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
// Fall back to the original match if the index is out of range so a
|
|
53
|
+
// literal U+E000<digits>U+E000 in user prose survives untouched instead
|
|
54
|
+
// of becoming the string "undefined".
|
|
55
|
+
const restoreRe = new RegExp(`${STASH_DELIM}(\\d+)${STASH_DELIM}`, 'g');
|
|
56
|
+
state.src = state.src.replace(restoreRe, (m, i) => stash[+i] ?? m);
|
|
43
57
|
});
|
|
44
58
|
}
|
|
45
59
|
|
|
@@ -89,27 +103,36 @@ class MacroConverter {
|
|
|
89
103
|
);
|
|
90
104
|
|
|
91
105
|
storage = storage.replace(/<blockquote>(.*?)<\/blockquote>/gs, (_, content) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const cleanContent = content.replace(/<p><strong>NOTE<\/strong><\/p>\s*/, '');
|
|
104
|
-
return `<ac:structured-macro ac:name="note">
|
|
105
|
-
<ac:rich-text-body>${cleanContent}</ac:rich-text-body>
|
|
106
|
-
</ac:structured-macro>`;
|
|
107
|
-
} else {
|
|
106
|
+
// Detect the marker only when it sits at the very start of the first
|
|
107
|
+
// paragraph, immediately followed by a `</p>` close (separated form) or
|
|
108
|
+
// a `\n` (same-line body form). This is the same anchor condition the
|
|
109
|
+
// strip step uses below, so detection and stripping stay in sync.
|
|
110
|
+
// Without this anchor, a quotation that merely *mentions* `**INFO**` —
|
|
111
|
+
// e.g. `> Use **INFO** at the start.` — would be silently wrapped in an
|
|
112
|
+
// info macro, surprising the author.
|
|
113
|
+
const marker = CALLOUT_MARKERS.find((m) =>
|
|
114
|
+
new RegExp(`<p><strong>${m.toUpperCase()}<\\/strong>(<\\/p>|\\s*\\n)`).test(content)
|
|
115
|
+
);
|
|
116
|
+
if (!marker) {
|
|
108
117
|
// Plain blockquote — `> …` is a quotation, not an alert. Use the
|
|
109
118
|
// `> **INFO**` / `> **WARNING**` / `> **NOTE**` markers above to
|
|
110
119
|
// produce a Confluence info / warning / note macro instead.
|
|
111
120
|
return `<blockquote>${content}</blockquote>`;
|
|
112
121
|
}
|
|
122
|
+
// Strip the leading `<strong>MARKER</strong>`. markdown-it produces two
|
|
123
|
+
// shapes depending on whether a blank `>` line separates marker and body:
|
|
124
|
+
// case A (separated): `<p><strong>MARKER</strong></p>\n<p>body</p>`
|
|
125
|
+
// case B (same-line): `<p><strong>MARKER</strong>\nbody</p>`
|
|
126
|
+
// The original cleanup only handled case A, so case B leaked the marker
|
|
127
|
+
// into the rendered macro body. README's recommended `> **INFO**\n> body`
|
|
128
|
+
// form parses as case B — exactly the form that broke.
|
|
129
|
+
const cleanContent = content.replace(
|
|
130
|
+
new RegExp(`<p><strong>${marker.toUpperCase()}<\\/strong>(<\\/p>\\s*|\\s*\\n)`),
|
|
131
|
+
(_, tail) => tail.startsWith('</p>') ? '' : '<p>'
|
|
132
|
+
);
|
|
133
|
+
return `<ac:structured-macro ac:name="${marker}">
|
|
134
|
+
<ac:rich-text-body>${cleanContent}</ac:rich-text-body>
|
|
135
|
+
</ac:structured-macro>`;
|
|
113
136
|
});
|
|
114
137
|
|
|
115
138
|
storage = storage.replace(/<table>(.*?)<\/table>/gs, '<table>$1</table>');
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "confluence-cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "confluence-cli",
|
|
9
|
-
"version": "2.1.
|
|
9
|
+
"version": "2.1.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"axios": "^1.15.0",
|