confluence-cli 2.5.0 → 2.6.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.
@@ -21,6 +21,8 @@ class HtmlDepthExceededError extends Error {
21
21
  // whatever shape the source had (markdown-it emits them without a slash).
22
22
  const VOID_TAGS = new Set(['hr']);
23
23
  const CALLOUT_MARKERS = ['info', 'warning', 'note'];
24
+ // these tags are wrapped into Confluence HTML macro
25
+ const HTML_MACRO_TAGS = new Set(['svg', 'div']);
24
26
 
25
27
  // Phrasing-content tags that trigger the `<li>` / `<th>` / `<td>` `<p>`-wrap
26
28
  // quirk: if an item contains only inline children and no text-node newline,
@@ -218,6 +220,36 @@ function convertBlockquote(node, ctx) {
218
220
  </ac:structured-macro>`;
219
221
  }
220
222
 
223
+ // `<details><summary>` becomes expand macro. If no summary child is found,
224
+ // falls through to plain HTML.
225
+ function convertDetails(node, ctx) {
226
+ const children = node.children || [];
227
+ let summaryNode = null;
228
+ let bodyNodes = [];
229
+
230
+ for (const child of children) {
231
+ if (child.type === 'tag' && child.name === 'summary') {
232
+ summaryNode = child;
233
+ } else if (!isWhitespaceOnly(child)) {
234
+ bodyNodes.push(child);
235
+ }
236
+ }
237
+
238
+ if (!summaryNode) {
239
+ return `<details${renderAttrs(node.attribs)}>${walkChildren(node, ctx)}</details>`;
240
+ }
241
+
242
+ const titleHtml = walkChildren(summaryNode, ctx);
243
+ const cleanTitle = titleHtml.replace(/<[^>]+>/g, '').trim();
244
+
245
+ const bodyHtml = bodyNodes
246
+ .map((c) => walkNode(c, ctx))
247
+ .join('')
248
+ .trim();
249
+
250
+ return `<ac:structured-macro ac:name="expand"><ac:parameter ac:name="title">${cleanTitle}</ac:parameter><ac:rich-text-body>${bodyHtml}</ac:rich-text-body></ac:structured-macro>`;
251
+ }
252
+
221
253
  // Strict `<pre><code>` adjacency only — `<pre>` with whitespace siblings or
222
254
  // any other shape falls through as plain `<pre>`. The body needs manual
223
255
  // entity decode because the parser keeps entities raw and CDATA is opaque
@@ -243,6 +275,20 @@ function convertCodeBlock(node, ctx) {
243
275
  return `<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">${language}</ac:parameter><ac:plain-text-body><![CDATA[${body}]]></ac:plain-text-body></ac:structured-macro>`;
244
276
  }
245
277
 
278
+ // Wrap allowlisted HTML tags (svg, div) in Confluence HTML macro with CDATA.
279
+ // Used for embedding custom HTML that Confluence doesn't natively support.
280
+ function convertHtmlBlock(node, ctx) {
281
+ const { randomUUID } = require('crypto');
282
+ const inner = walkChildren(node, ctx);
283
+ const attrsStr = renderAttrs(node.attribs);
284
+ const openTag = `<${node.name}${attrsStr}>`;
285
+ const closeTag = `</${node.name}>`;
286
+ const htmlContent = openTag + inner + closeTag;
287
+ const safeContent = htmlContent.replace(/]]>/g, ']]]]><![CDATA[>');
288
+ const macroId = randomUUID();
289
+ return `<ac:structured-macro ac:name="html" ac:schema-version="1" ac:macro-id="${macroId}"><ac:plain-text-body><![CDATA[${safeContent}]]></ac:plain-text-body></ac:structured-macro>`;
290
+ }
291
+
246
292
  // Re-escape literal `"` inside attribute values. htmlparser2 with
247
293
  // `decodeEntities: false` keeps source-escaped entities intact, but a
248
294
  // single-quoted source attribute (`<a title='he said "hi"'>`) lands a
@@ -359,6 +405,8 @@ function dispatchTag(node, ctx) {
359
405
  return convertLink(node, ctx);
360
406
  case 'blockquote':
361
407
  return convertBlockquote(node, ctx);
408
+ case 'details':
409
+ return convertDetails(node, ctx);
362
410
  case 'table':
363
411
  case 'thead':
364
412
  case 'tbody':
@@ -375,6 +423,9 @@ function dispatchTag(node, ctx) {
375
423
  if (VOID_TAGS.has(node.name)) {
376
424
  return `<${node.name}${renderAttrs(node.attribs)} />`;
377
425
  }
426
+ if (HTML_MACRO_TAGS.has(node.name)) {
427
+ return convertHtmlBlock(node, ctx);
428
+ }
378
429
  return `<${node.name}${renderAttrs(node.attribs)}>${walkChildren(node, ctx)}</${node.name}>`;
379
430
  }
380
431
  }
@@ -21,7 +21,9 @@ const STASH_DELIM = '\uE000';
21
21
  // The body alternation `"[^"]*"|'[^']*'|[^>]` makes the match quote-aware
22
22
  // so a literal `>` inside a quoted attribute value (e.g.
23
23
  // `<mark title="1>0">`) does not terminate the tag prematurely.
24
- const PASSTHROUGH_TAG_RE = /<\/?(?:u|sub|sup|mark)(?=[\s/>])(?:"[^"]*"|'[^']*'|[^>])*>/gi;
24
+ const PASSTHROUGH_TAG_RE = /<\/?(?:u|sub|sup|mark|details|summary)(?=[\s/>])(?:"[^"]*"|'[^']*'|[^>])*>/gi;
25
+ // Block-level HTML elements that should pass through WITHOUT markdown processing of their content.
26
+ const PASSTHROUGH_BLOCK_RE = /<(svg|div)(?:\s[^>]*)?>[\s\S]*?<\/\1>/gi;
25
27
  // Single-backtick inline code spans. Block-level code (fenced + indented) is
26
28
  // detected via MarkdownIt's tokenizer in `_findCodeRanges` because a regex
27
29
  // can't reliably distinguish a 4-space-indented code block from a list-item
@@ -92,10 +94,19 @@ class MacroConverter {
92
94
  _renderMarkdownToHtml(markdown) {
93
95
  const codeRanges = this._findCodeRanges(markdown);
94
96
  const htmlStash = [];
95
- const stashHtml = (text) => text.replace(PASSTHROUGH_TAG_RE, (m) => {
96
- htmlStash.push(m);
97
- return `${STASH_DELIM}H${htmlStash.length - 1}${STASH_DELIM}`;
98
- });
97
+ const stashHtml = (text) => {
98
+ // block-level HTML (svg, div with all content) must be stashed before inline tags to avoid matching the closing tag as inline HTML
99
+ let result = text.replace(PASSTHROUGH_BLOCK_RE, (m) => {
100
+ htmlStash.push(m);
101
+ return `${STASH_DELIM}H${htmlStash.length - 1}${STASH_DELIM}`;
102
+ });
103
+ // Then stash inline HTML tags
104
+ result = result.replace(PASSTHROUGH_TAG_RE, (m) => {
105
+ htmlStash.push(m);
106
+ return `${STASH_DELIM}H${htmlStash.length - 1}${STASH_DELIM}`;
107
+ });
108
+ return result;
109
+ };
99
110
 
100
111
  let src = '';
101
112
  let pos = 0;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "confluence-cli",
9
- "version": "2.5.0",
9
+ "version": "2.6.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "axios": "~1.15.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {