confluence-cli 2.1.4 → 2.1.5

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.
@@ -34,6 +34,26 @@ function htmlToMarkdown(html) {
34
34
 
35
35
  markdown = markdown.replace(/<em[^>]*>(.*?)<\/em>/g, '*$1*');
36
36
 
37
+ // Multi-line <pre><code> → fenced block. Must run before the inline <code>
38
+ // rule and before catch-all tag stripping so indentation-sensitive bodies
39
+ // are wrapped in fences and skipped by the cleanup chain below.
40
+ markdown = markdown.replace(
41
+ /<pre[^>]*>\s*<code([^>]*)>([\s\S]*?)<\/code>\s*<\/pre>/g,
42
+ (_, codeAttrs, body) => {
43
+ const langMatch = codeAttrs.match(/class="language-([^"]+)"/);
44
+ const lang = langMatch ? langMatch[1] : '';
45
+ const trimmed = body.replace(/^\n+|\n+$/g, '');
46
+ // Size against entity-decoded content: the entity-decode pass runs
47
+ // after this rule, so `&#96;` / `&#x60;` would otherwise expose
48
+ // backticks inside the fence post-emission and break it.
49
+ const decoded = trimmed
50
+ .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)))
51
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, code) => String.fromCharCode(parseInt(code, 16)));
52
+ const fence = '`'.repeat(fenceLength(decoded));
53
+ return `\n${fence}${lang}\n${trimmed}\n${fence}\n`;
54
+ }
55
+ );
56
+
37
57
  markdown = markdown.replace(/<code[^>]*>(.*?)<\/code>/g, '`$1`');
38
58
 
39
59
  markdown = markdown.replace(/<(\w+)[^>]*>/g, '<$1>');
@@ -142,16 +162,60 @@ function htmlToMarkdown(html) {
142
162
 
143
163
  markdown = markdown.replace(/&([a-zA-Z]+);/g, (match, name) => NAMED_ENTITIES[name] || match);
144
164
 
145
- markdown = markdown.replace(/[ \t]+$/gm, '');
146
- markdown = markdown.replace(/^[ \t]+(?!([`>]|[*+-] |\d+[.)] ))/gm, '');
147
- markdown = markdown.replace(/^(#{1,6}[^\n]+)\n(?!\n)/gm, '$1\n\n');
148
- markdown = markdown.replace(/\n\s*\n\s*\n+/g, '\n\n');
149
- markdown = markdown.replace(/[ \t]+/g, ' ');
165
+ // Split on fenced code boundaries so cleanup rules (indent stripping,
166
+ // multi-space collapsing) don't mangle indentation-sensitive code.
167
+ // Backreference matches dynamically-sized fences emitted above when the
168
+ // body itself contains backticks.
169
+ const segments = splitOnFences(markdown);
170
+ markdown = segments
171
+ .map((seg, i) => (i % 2 === 1 ? seg : cleanupOutsideFence(seg)))
172
+ .join('');
150
173
  markdown = markdown.trim();
151
174
 
152
175
  return markdown;
153
176
  }
154
177
 
178
+ // CommonMark allows fenced code with N≥3 backticks where the body contains
179
+ // no run of N+ backticks. Pick the smallest N satisfying both so a code
180
+ // block whose payload itself contains ``` does not close its own fence.
181
+ function fenceLength(body) {
182
+ let max = 0;
183
+ const runs = body.match(/`+/g);
184
+ if (runs) {
185
+ for (const r of runs) if (r.length > max) max = r.length;
186
+ }
187
+ return Math.max(3, max + 1);
188
+ }
189
+
190
+ function splitOnFences(text) {
191
+ // CommonMark: a fence opens on a line that starts with up to 3 spaces
192
+ // followed by 3+ backticks, and closes on a line of equal-length
193
+ // backticks followed only by whitespace. Anchoring to line boundaries
194
+ // (^ / $ with m flag) prevents prose backticks (e.g. <p>literal ``` x</p>)
195
+ // from being mis-paired with real fence boundaries.
196
+ const result = [];
197
+ const re = /^ {0,3}(`{3,})[^\n]*\n[\s\S]*?\n {0,3}\1[\t ]*$/gm;
198
+ let lastIdx = 0;
199
+ let m;
200
+ while ((m = re.exec(text)) !== null) {
201
+ result.push(text.slice(lastIdx, m.index));
202
+ result.push(m[0]);
203
+ lastIdx = m.index + m[0].length;
204
+ }
205
+ result.push(text.slice(lastIdx));
206
+ return result;
207
+ }
208
+
209
+ function cleanupOutsideFence(text) {
210
+ let out = text;
211
+ out = out.replace(/[ \t]+$/gm, '');
212
+ out = out.replace(/^[ \t]+(?!([`>]|[*+-] |\d+[.)] ))/gm, '');
213
+ out = out.replace(/^(#{1,6}[^\n]+)\n(?!\n)/gm, '$1\n\n');
214
+ out = out.replace(/\n\s*\n\s*\n+/g, '\n\n');
215
+ out = out.replace(/[ \t]+/g, ' ');
216
+ return out;
217
+ }
218
+
155
219
  module.exports = {
156
220
  htmlToMarkdown,
157
221
  NAMED_ENTITIES
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "confluence-cli",
9
- "version": "2.1.4",
9
+ "version": "2.1.5",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "axios": "^1.15.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {