tdecollab 0.3.5 → 0.3.6

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.
@@ -1,20 +1,24 @@
1
1
  import {
2
- __require,
3
2
  logger
4
- } from "./chunk-6RIA6AJ3.js";
3
+ } from "./chunk-IFYMZLQI.js";
5
4
 
6
5
  // tools/common/env-loader.ts
7
6
  import dotenv from "dotenv";
7
+ import fs from "fs";
8
8
  import path from "path";
9
9
  import os from "os";
10
10
  var loaded = false;
11
+ var lastResult = null;
11
12
  function getHomeDir() {
12
13
  return process.env.HOME || process.env.USERPROFILE || os.homedir();
13
14
  }
14
15
  function loadEnv() {
15
- const result = { loadedFiles: [], skippedFiles: [] };
16
- if (loaded) return result;
16
+ if (loaded && lastResult) return lastResult;
17
+ const result = { loadedFiles: [], skippedFiles: [], sources: {} };
17
18
  loaded = true;
19
+ for (const key of Object.keys(process.env)) {
20
+ result.sources[key] = "shell env";
21
+ }
18
22
  const candidates = [
19
23
  // 우선순위 2: 현재 디렉토리
20
24
  path.resolve(process.cwd(), "tdecollab.env"),
@@ -22,15 +26,31 @@ function loadEnv() {
22
26
  path.join(getHomeDir(), ".config", "tdecollab", ".env")
23
27
  ];
24
28
  for (const filepath of candidates) {
29
+ let parsed = {};
30
+ try {
31
+ parsed = dotenv.parse(fs.readFileSync(filepath, "utf-8"));
32
+ } catch {
33
+ }
34
+ const beforeKeys = new Set(Object.keys(process.env));
25
35
  const out = dotenv.config({ path: filepath, override: false });
26
36
  if (out.error) {
27
37
  result.skippedFiles.push(filepath);
28
38
  } else {
29
39
  result.loadedFiles.push(filepath);
40
+ for (const key of Object.keys(parsed)) {
41
+ if (!beforeKeys.has(key) && process.env[key] !== void 0) {
42
+ result.sources[key] = filepath;
43
+ }
44
+ }
30
45
  }
31
46
  }
47
+ lastResult = result;
32
48
  return result;
33
49
  }
50
+ function getEnvSource(key) {
51
+ const result = loadEnv();
52
+ return result.sources[key] || "<unset>";
53
+ }
34
54
 
35
55
  // tools/confluence/api/content.ts
36
56
  var ConfluenceContentApi = class {
@@ -322,7 +342,8 @@ function getEnvOrThrow(key, description) {
322
342
  }
323
343
  function loadConfluenceConfig() {
324
344
  const baseUrl = getEnvOrThrow("CONFLUENCE_BASE_URL", "Confluence \uAE30\uBCF8 URL");
325
- const username = process.env.CONFLUENCE_USERNAME;
345
+ const authType = (process.env.CONFLUENCE_AUTH_TYPE || "bearer").toLowerCase();
346
+ const username = authType === "basic" ? process.env.CONFLUENCE_USERNAME : void 0;
326
347
  const token = getEnvOrThrow("CONFLUENCE_API_TOKEN", "Confluence PAT \uD1A0\uD070");
327
348
  const mermaidMacroName = process.env.CONFLUENCE_MERMAID_MACRO_NAME || "mermaiddiagram";
328
349
  const inlineCodeStyle = process.env.CONFLUENCE_INLINE_CODE_STYLE || "color: #d04437; font-weight: bold;";
@@ -369,14 +390,191 @@ function loadGitlabConfig() {
369
390
 
370
391
  // tools/confluence/converters/md-to-storage.ts
371
392
  import MarkdownIt from "markdown-it";
393
+ var DEFAULT_MERMAID_MACRO_NAME = "mermaiddiagram";
394
+ var DEFAULT_INLINE_CODE_STYLE = "color: #d04437; font-weight: bold;";
395
+ var TASK_ITEM_PATTERN = /^\[([ xX])\]\s+/;
396
+ function decodeLocalImagePath(value) {
397
+ try {
398
+ return decodeURI(value);
399
+ } catch {
400
+ return value;
401
+ }
402
+ }
403
+ function escapeXmlAttribute(value) {
404
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
405
+ }
406
+ function parseImageDimensionTitle(title) {
407
+ if (!title) {
408
+ return "";
409
+ }
410
+ const width = title.match(/\bwidth=(\d+)\b/i)?.[1];
411
+ const height = title.match(/\bheight=(\d+)\b/i)?.[1];
412
+ const attrs = [
413
+ width ? ` ac:width="${escapeXmlAttribute(width)}"` : "",
414
+ height ? ` ac:height="${escapeXmlAttribute(height)}"` : ""
415
+ ];
416
+ return attrs.join("");
417
+ }
418
+ function getTaskMarker(content) {
419
+ const match = content.match(TASK_ITEM_PATTERN);
420
+ if (!match) {
421
+ return null;
422
+ }
423
+ return {
424
+ checked: match[1].toLowerCase() === "x",
425
+ body: content.slice(match[0].length)
426
+ };
427
+ }
428
+ function findMatchingCloseIndex(tokens, openIndex) {
429
+ const openToken = tokens[openIndex];
430
+ let nesting = 1;
431
+ for (let idx = openIndex + 1; idx < tokens.length; idx++) {
432
+ const token = tokens[idx];
433
+ if (token.type === openToken.type) {
434
+ nesting++;
435
+ }
436
+ if (token.type === openToken.type.replace("_open", "_close")) {
437
+ nesting--;
438
+ if (nesting === 0) {
439
+ return idx;
440
+ }
441
+ }
442
+ }
443
+ return openIndex;
444
+ }
445
+ function annotateTaskListTokens(tokens) {
446
+ const listItemStack = [];
447
+ for (let idx = 0; idx < tokens.length; idx++) {
448
+ const token = tokens[idx];
449
+ if (token.type === "list_item_open") {
450
+ listItemStack.push(idx);
451
+ continue;
452
+ }
453
+ if (token.type === "list_item_close") {
454
+ listItemStack.pop();
455
+ continue;
456
+ }
457
+ if (token.type !== "inline" || listItemStack.length === 0) {
458
+ continue;
459
+ }
460
+ const task = getTaskMarker(token.content);
461
+ if (!task) {
462
+ continue;
463
+ }
464
+ const listItemOpenIndex = listItemStack[listItemStack.length - 1];
465
+ if (tokens[listItemOpenIndex].meta?.task) {
466
+ continue;
467
+ }
468
+ token.content = task.body;
469
+ if (token.children?.[0]?.type === "text") {
470
+ token.children[0].content = token.children[0].content.replace(TASK_ITEM_PATTERN, "");
471
+ }
472
+ tokens[listItemOpenIndex].meta = {
473
+ ...tokens[listItemOpenIndex].meta || {},
474
+ task
475
+ };
476
+ if (tokens[idx - 1]?.type === "paragraph_open") {
477
+ tokens[idx - 1].meta = {
478
+ ...tokens[idx - 1].meta || {},
479
+ taskBodyParagraph: true
480
+ };
481
+ }
482
+ if (tokens[idx + 1]?.type === "paragraph_close") {
483
+ tokens[idx + 1].meta = {
484
+ ...tokens[idx + 1].meta || {},
485
+ taskBodyParagraph: true
486
+ };
487
+ }
488
+ }
489
+ annotateTaskListBoundaries(tokens);
490
+ annotateTaskListCloseTokens(tokens);
491
+ }
492
+ function annotateTaskListBoundaries(tokens) {
493
+ for (let idx = 0; idx < tokens.length; idx++) {
494
+ const token = tokens[idx];
495
+ if (token.type !== "bullet_list_open") {
496
+ continue;
497
+ }
498
+ const closeIndex = findMatchingCloseIndex(tokens, idx);
499
+ const directItemIndexes = [];
500
+ for (let cursor = idx + 1; cursor < closeIndex; cursor++) {
501
+ if (tokens[cursor].type === "list_item_open" && tokens[cursor].level === token.level + 1) {
502
+ directItemIndexes.push(cursor);
503
+ }
504
+ }
505
+ if (directItemIndexes.length === 0) {
506
+ continue;
507
+ }
508
+ const itemKinds = directItemIndexes.map((itemIndex) => !!tokens[itemIndex].meta?.task);
509
+ const firstIsNormal = !itemKinds[0];
510
+ const lastIsNormal = !itemKinds[itemKinds.length - 1];
511
+ token.meta = { ...token.meta || {}, renderList: firstIsNormal };
512
+ tokens[closeIndex].meta = { ...tokens[closeIndex].meta || {}, renderList: lastIsNormal };
513
+ directItemIndexes.forEach((itemIndex, itemPosition) => {
514
+ const itemToken = tokens[itemIndex];
515
+ if (!itemToken.meta?.task) {
516
+ return;
517
+ }
518
+ itemToken.meta.taskCloseParentBefore = itemPosition > 0 && !itemKinds[itemPosition - 1];
519
+ itemToken.meta.taskOpenParentAfter = itemPosition < itemKinds.length - 1 && !itemKinds[itemPosition + 1];
520
+ });
521
+ }
522
+ }
523
+ function annotateTaskListCloseTokens(tokens) {
524
+ const listItemStack = [];
525
+ for (let idx = 0; idx < tokens.length; idx++) {
526
+ const token = tokens[idx];
527
+ if (token.type === "list_item_open") {
528
+ listItemStack.push(idx);
529
+ continue;
530
+ }
531
+ if (token.type !== "list_item_close") {
532
+ continue;
533
+ }
534
+ const openIndex = listItemStack.pop();
535
+ if (openIndex === void 0 || !tokens[openIndex].meta?.task) {
536
+ continue;
537
+ }
538
+ token.meta = {
539
+ ...token.meta || {},
540
+ task: tokens[openIndex].meta.task,
541
+ taskOpenParentAfter: tokens[openIndex].meta.taskOpenParentAfter
542
+ };
543
+ }
544
+ }
545
+ function stripMarkdownFrontmatter(markdown) {
546
+ const frontmatterMatch = markdown.match(/^---[ \t]*\r?\n[\s\S]*?\r?\n(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/);
547
+ if (!frontmatterMatch) {
548
+ return markdown;
549
+ }
550
+ return markdown.slice(frontmatterMatch[0].length).replace(/^\r?\n/, "");
551
+ }
552
+ function stripLeakedConfluencePageIdArtifacts(markdown) {
553
+ let prepared = markdown;
554
+ const leakedPageIdPattern = /^(?:[ \t]*\r?\n)*(?:---|\*\*\*)[ \t]*\r?\n+(?:[ \t]*\r?\n)*#{1,6}[ \t]+confluence\\?_page\\?_id:[ \t]*\d+[ \t]*\r?\n+/i;
555
+ while (leakedPageIdPattern.test(prepared)) {
556
+ prepared = prepared.replace(leakedPageIdPattern, "").replace(/^\r?\n/, "");
557
+ }
558
+ return prepared;
559
+ }
560
+ function prepareMarkdownForConfluenceStorage(markdown) {
561
+ const withoutLeadingArtifacts = stripLeakedConfluencePageIdArtifacts(markdown);
562
+ const withoutFrontmatter = stripMarkdownFrontmatter(withoutLeadingArtifacts);
563
+ return stripLeakedConfluencePageIdArtifacts(withoutFrontmatter);
564
+ }
372
565
  var MarkdownToStorageConverter = class {
373
566
  md;
374
567
  mermaidMacroName;
375
568
  inlineCodeStyle;
376
- constructor() {
377
- const config = loadConfluenceConfig();
378
- this.mermaidMacroName = config.mermaidMacroName;
379
- this.inlineCodeStyle = config.inlineCodeStyle;
569
+ constructor(options) {
570
+ if (options) {
571
+ this.mermaidMacroName = options.mermaidMacroName || DEFAULT_MERMAID_MACRO_NAME;
572
+ this.inlineCodeStyle = options.inlineCodeStyle || DEFAULT_INLINE_CODE_STYLE;
573
+ } else {
574
+ const config = loadConfluenceConfig();
575
+ this.mermaidMacroName = config.mermaidMacroName;
576
+ this.inlineCodeStyle = config.inlineCodeStyle;
577
+ }
380
578
  this.md = new MarkdownIt({
381
579
  html: true,
382
580
  linkify: true,
@@ -384,6 +582,50 @@ var MarkdownToStorageConverter = class {
384
582
  xhtmlOut: true
385
583
  // Confluence XML 파서와의 호환성을 위해 XHTML 출력 활성화
386
584
  });
585
+ this.md.core.ruler.after("inline", "confluence_task_list", (state) => {
586
+ annotateTaskListTokens(state.tokens);
587
+ });
588
+ this.md.renderer.rules.bullet_list_open = (tokens, idx, options2, env, self) => {
589
+ if (tokens[idx].meta?.renderList === false) {
590
+ return "";
591
+ }
592
+ return self.renderToken(tokens, idx, options2);
593
+ };
594
+ this.md.renderer.rules.bullet_list_close = (tokens, idx, options2, env, self) => {
595
+ if (tokens[idx].meta?.renderList === false) {
596
+ return "";
597
+ }
598
+ return self.renderToken(tokens, idx, options2);
599
+ };
600
+ this.md.renderer.rules.list_item_open = (tokens, idx, options2, env, self) => {
601
+ const task = tokens[idx].meta?.task;
602
+ if (!task) {
603
+ return self.renderToken(tokens, idx, options2);
604
+ }
605
+ const prefix = tokens[idx].meta.taskCloseParentBefore ? "</ul>\n" : "";
606
+ const status = task.checked ? "complete" : "incomplete";
607
+ return `${prefix}<ac:task-list><ac:task><ac:task-status>${status}</ac:task-status><ac:task-body>`;
608
+ };
609
+ this.md.renderer.rules.list_item_close = (tokens, idx, options2, env, self) => {
610
+ if (!tokens[idx].meta?.task) {
611
+ return self.renderToken(tokens, idx, options2);
612
+ }
613
+ const suffix = tokens[idx].meta.taskOpenParentAfter ? "\n<ul>" : "";
614
+ return suffix;
615
+ };
616
+ this.md.renderer.rules.paragraph_open = (tokens, idx, options2, env, self) => {
617
+ if (tokens[idx].meta?.taskBodyParagraph) {
618
+ return "";
619
+ }
620
+ return self.renderToken(tokens, idx, options2);
621
+ };
622
+ this.md.renderer.rules.paragraph_close = (tokens, idx, options2, env, self) => {
623
+ const task = tokens[idx].meta?.taskBodyParagraph;
624
+ if (task) {
625
+ return "</ac:task-body></ac:task></ac:task-list>";
626
+ }
627
+ return self.renderToken(tokens, idx, options2);
628
+ };
387
629
  this.md.renderer.rules.fence = (tokens, idx) => {
388
630
  const token = tokens[idx];
389
631
  const code = token.content.trim();
@@ -404,17 +646,19 @@ var MarkdownToStorageConverter = class {
404
646
  <ac:plain-text-body><![CDATA[${code}]]></ac:plain-text-body>
405
647
  </ac:structured-macro>`;
406
648
  };
407
- this.md.renderer.rules.image = (tokens, idx, options, env, self) => {
649
+ this.md.renderer.rules.image = (tokens, idx, options2, env, self) => {
408
650
  const token = tokens[idx];
409
651
  const src = token.attrGet("src") || "";
410
652
  const alt = token.content || "";
653
+ const dimensionAttrs = parseImageDimensionTitle(token.attrGet("title"));
411
654
  const isExternal = src.startsWith("http://") || src.startsWith("https://");
412
- const altAttr = alt ? ` ac:alt="${this.md.utils.escapeHtml(alt)}"` : "";
655
+ const altAttr = alt ? ` ac:alt="${escapeXmlAttribute(alt)}"` : "";
413
656
  if (isExternal) {
414
- return `<ac:image${altAttr}><ri:url ri:value="${src}" /></ac:image>`;
657
+ return `<ac:image${altAttr}${dimensionAttrs}><ri:url ri:value="${escapeXmlAttribute(src)}" /></ac:image>`;
415
658
  } else {
416
- const filename = src.split("/").pop() || src;
417
- return `<ac:image${altAttr}><ri:attachment ri:filename="${filename}" /></ac:image>`;
659
+ const decodedSrc = decodeLocalImagePath(src);
660
+ const filename = decodedSrc.split("/").pop() || decodedSrc;
661
+ return `<ac:image${altAttr}${dimensionAttrs}><ri:attachment ri:filename="${escapeXmlAttribute(filename)}" /></ac:image>`;
418
662
  }
419
663
  };
420
664
  this.md.renderer.rules.code_inline = (tokens, idx) => {
@@ -424,10 +668,10 @@ var MarkdownToStorageConverter = class {
424
668
  };
425
669
  }
426
670
  convert(markdown) {
427
- return this.md.render(markdown);
671
+ return this.md.render(prepareMarkdownForConfluenceStorage(markdown));
428
672
  }
429
673
  extractLocalImages(markdown) {
430
- const tokens = this.md.parse(markdown, {});
674
+ const tokens = this.md.parse(prepareMarkdownForConfluenceStorage(markdown), {});
431
675
  const localImages = /* @__PURE__ */ new Set();
432
676
  const walk = (tokens2) => {
433
677
  for (const token of tokens2) {
@@ -450,6 +694,38 @@ var MarkdownToStorageConverter = class {
450
694
  // tools/confluence/converters/storage-to-md.ts
451
695
  import TurndownService from "turndown";
452
696
  import { gfm } from "turndown-plugin-gfm";
697
+ import { createRequire } from "module";
698
+ function parseHtmlDocument(html) {
699
+ if (typeof window !== "undefined" && typeof window.DOMParser !== "undefined") {
700
+ const parser = new window.DOMParser();
701
+ return parser.parseFromString(html, "text/html");
702
+ }
703
+ const requireBase = process.argv[1] || `${process.cwd()}/package.json`;
704
+ const nodeRequire = createRequire(requireBase);
705
+ const { JSDOM } = nodeRequire("jsdom");
706
+ return new JSDOM(html).window.document;
707
+ }
708
+ function getXmlAttribute(attrs, name) {
709
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
710
+ return attrs.match(new RegExp(`(?:^|\\s)${escapedName}="([^"]*)"`, "i"))?.[1];
711
+ }
712
+ function escapeHtmlAttribute(value) {
713
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
714
+ }
715
+ function escapeMarkdownImageText(value) {
716
+ return value.replace(/\\/g, "\\\\").replace(/]/g, "\\]");
717
+ }
718
+ function escapeMarkdownImageDestination(value) {
719
+ return value.replace(/\)/g, "\\)");
720
+ }
721
+ function buildImageDimensionAttributes(attrs) {
722
+ const width = getXmlAttribute(attrs, "ac:width") || getXmlAttribute(attrs, "width");
723
+ const height = getXmlAttribute(attrs, "ac:height") || getXmlAttribute(attrs, "height");
724
+ return [
725
+ width ? ` width="${escapeHtmlAttribute(width)}"` : "",
726
+ height ? ` height="${escapeHtmlAttribute(height)}"` : ""
727
+ ].join("");
728
+ }
453
729
  var StorageToMarkdownConverter = class {
454
730
  turndown;
455
731
  jiraBaseUrl;
@@ -467,6 +743,37 @@ var StorageToMarkdownConverter = class {
467
743
  this.setupRules();
468
744
  }
469
745
  setupRules() {
746
+ this.turndown.addRule("imagesWithDimensions", {
747
+ filter: (node) => {
748
+ if (node.nodeName.toLowerCase() !== "img") return false;
749
+ const element = node;
750
+ return element.hasAttribute("width") || element.hasAttribute("height");
751
+ },
752
+ replacement: (_content, node) => {
753
+ const element = node;
754
+ const src = element.getAttribute("src") || "";
755
+ const alt = element.getAttribute("alt") || "";
756
+ const width = element.getAttribute("width");
757
+ const height = element.getAttribute("height");
758
+ const title = [
759
+ width ? `width=${width}` : "",
760
+ height ? `height=${height}` : ""
761
+ ].filter(Boolean).join(" ");
762
+ return `![${escapeMarkdownImageText(alt)}](${escapeMarkdownImageDestination(src)} "${title}")`;
763
+ }
764
+ });
765
+ this.turndown.addRule("lists", {
766
+ filter: (node) => {
767
+ const nodeName = node.nodeName.toLowerCase();
768
+ if (nodeName !== "ul" && nodeName !== "ol") return false;
769
+ return Array.from(node.children).some((child) => {
770
+ return child.nodeName.toLowerCase() === "li" && this.getExplicitListItemDepth(child) !== void 0;
771
+ });
772
+ },
773
+ replacement: (_content, node) => {
774
+ return this.renderList(node, 0);
775
+ }
776
+ });
470
777
  this.turndown.addRule("tables", {
471
778
  filter: ["table"],
472
779
  replacement: (content, node) => {
@@ -566,6 +873,83 @@ ${bodyMd}
566
873
  }
567
874
  });
568
875
  }
876
+ renderList(listElement, depth) {
877
+ const isOrdered = listElement.nodeName.toLowerCase() === "ol";
878
+ const start = Number(listElement.getAttribute("start") || "1");
879
+ let orderedIndex = Number.isFinite(start) && start > 0 ? start : 1;
880
+ const renderedItems = Array.from(listElement.children).filter((child) => child.nodeName.toLowerCase() === "li").map((item) => {
881
+ const itemDepth = this.getListItemDepth(item, depth);
882
+ const marker = isOrdered ? `${orderedIndex++}.` : "-";
883
+ const indent = " ".repeat(itemDepth);
884
+ const content = this.renderListItemContent(item);
885
+ const firstLine = `${indent}${marker}${content ? ` ${content.split("\n")[0]}` : ""}`;
886
+ const remainingLines = content.split("\n").slice(1).filter((line) => line.trim().length > 0).map((line) => `${indent} ${line}`).join("\n");
887
+ const nestedLists = Array.from(item.children).filter((child) => {
888
+ const nodeName = child.nodeName.toLowerCase();
889
+ return nodeName === "ul" || nodeName === "ol";
890
+ }).map((child) => this.renderList(child, itemDepth + 1).trim()).filter(Boolean).join("\n");
891
+ return [firstLine, remainingLines, nestedLists].filter(Boolean).join("\n");
892
+ }).filter(Boolean);
893
+ return `
894
+
895
+ ${renderedItems.join("\n")}
896
+
897
+ `;
898
+ }
899
+ renderListItemContent(item) {
900
+ const clone = item.cloneNode(true);
901
+ clone.querySelectorAll("ul, ol").forEach((child) => child.remove());
902
+ return this.turndown.turndown(clone.innerHTML).replace(/\n{2,}/g, "\n").trim();
903
+ }
904
+ getListItemDepth(item, fallbackDepth) {
905
+ return this.getExplicitListItemDepth(item) ?? fallbackDepth;
906
+ }
907
+ getExplicitListItemDepth(item) {
908
+ const explicitDepth = item.getAttribute("data-indent-level") || item.getAttribute("data-indent") || item.getAttribute("data-level");
909
+ if (explicitDepth !== null) {
910
+ const parsedDepth = Number(explicitDepth);
911
+ if (Number.isFinite(parsedDepth) && parsedDepth >= 0) {
912
+ return parsedDepth;
913
+ }
914
+ }
915
+ const className = item.getAttribute("class") || "";
916
+ const classDepth = className.match(/(?:^|\s)(?:ql-indent|indent)-(\d+)(?:\s|$)/)?.[1];
917
+ if (classDepth) {
918
+ const parsedDepth = Number(classDepth);
919
+ if (Number.isFinite(parsedDepth) && parsedDepth >= 0) {
920
+ return parsedDepth;
921
+ }
922
+ }
923
+ const style = item.getAttribute("style") || "";
924
+ const marginLeftPx = style.match(/margin-left:\s*(\d+(?:\.\d+)?)px/i)?.[1];
925
+ if (marginLeftPx) {
926
+ const parsedPx = Number(marginLeftPx);
927
+ if (Number.isFinite(parsedPx) && parsedPx > 0) {
928
+ return Math.max(0, Math.round(parsedPx / 40));
929
+ }
930
+ }
931
+ return void 0;
932
+ }
933
+ normalizeMalformedConfluenceLists(document) {
934
+ let changed = true;
935
+ while (changed) {
936
+ changed = false;
937
+ const lists = Array.from(document.querySelectorAll("ul, ol"));
938
+ for (const list of lists) {
939
+ const childLists = Array.from(list.children).filter((child) => {
940
+ const nodeName = child.nodeName.toLowerCase();
941
+ return nodeName === "ul" || nodeName === "ol";
942
+ });
943
+ for (const childList of childLists) {
944
+ const previous = childList.previousElementSibling;
945
+ if (previous?.nodeName.toLowerCase() === "li") {
946
+ previous.appendChild(childList);
947
+ changed = true;
948
+ }
949
+ }
950
+ }
951
+ }
952
+ }
569
953
  convert(storageHtml, imageUrlMap, jiraIssueMap) {
570
954
  this.jiraIssueMap = jiraIssueMap;
571
955
  if (!storageHtml) return "";
@@ -574,21 +958,16 @@ ${bodyMd}
574
958
  }).replace(/<ac:structured-macro\s+ac:name="([^"]*)"/gi, '<div data-macro-name-tag data-macro-name="$1"').replace(/<\/ac:structured-macro>/gi, "</div>").replace(/<ac:parameter\s+ac:name="([^"]*)"/gi, '<div data-macro-param-tag data-macro-param-name="$1"').replace(/<\/ac:parameter>/gi, "</div>").replace(/<ac:plain-text-body>/gi, "<pre data-macro-body>").replace(/<\/ac:plain-text-body>/gi, "</pre>").replace(/<ac:rich-text-body>/gi, "<div data-macro-rich-body>").replace(/<\/ac:rich-text-body>/gi, "</div>").replace(/<ac:image([^>]*)>[\s\S]*?<ri:attachment\s+ri:filename="([^"]*)"\s*\/?>[\s\S]*?<\/ac:image>/gi, (match, attrs, filename) => {
575
959
  const altMatch = attrs.match(/ac:alt="([^"]*)"/i);
576
960
  const alt = altMatch ? altMatch[1] : filename;
577
- return `<img src="${filename}" alt="${alt}" />`;
961
+ const dimensions = buildImageDimensionAttributes(attrs);
962
+ return `<img src="${escapeHtmlAttribute(filename)}" alt="${escapeHtmlAttribute(alt)}"${dimensions} />`;
578
963
  }).replace(/<ac:image([^>]*)>[\s\S]*?<ri:url\s+ri:value="([^"]*)"\s*\/?>[\s\S]*?<\/ac:image>/gi, (match, attrs, url) => {
579
964
  const altMatch = attrs.match(/ac:alt="([^"]*)"/i);
580
965
  const alt = altMatch ? altMatch[1] : "";
581
- return `<img src="${url}" alt="${alt}" />`;
966
+ const dimensions = buildImageDimensionAttributes(attrs);
967
+ return `<img src="${escapeHtmlAttribute(url)}" alt="${escapeHtmlAttribute(alt)}"${dimensions} />`;
582
968
  });
583
- let document;
584
- if (typeof window !== "undefined" && typeof window.DOMParser !== "undefined") {
585
- const parser = new window.DOMParser();
586
- document = parser.parseFromString(processedHtml, "text/html");
587
- } else {
588
- const { JSDOM } = __require("jsdom");
589
- const dom = new JSDOM(processedHtml);
590
- document = dom.window.document;
591
- }
969
+ const document = parseHtmlDocument(processedHtml);
970
+ this.normalizeMalformedConfluenceLists(document);
592
971
  if (imageUrlMap && imageUrlMap.size > 0) {
593
972
  const images = document.querySelectorAll("img");
594
973
  images.forEach((img) => {
@@ -839,6 +1218,7 @@ function createGitlabClient(config) {
839
1218
 
840
1219
  export {
841
1220
  loadEnv,
1221
+ getEnvSource,
842
1222
  ConfluenceContentApi,
843
1223
  ConfluenceSpaceApi,
844
1224
  ConfluenceSearchApi,
@@ -857,4 +1237,4 @@ export {
857
1237
  GitlabPipelineApi,
858
1238
  createGitlabClient
859
1239
  };
860
- //# sourceMappingURL=chunk-JUM5ZG64.js.map
1240
+ //# sourceMappingURL=chunk-SIKUIQKX.js.map