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.
- package/README.md +35 -19
- package/dist/{chunk-5IY42AV6.js → chunk-6GCKWJJ7.js} +4 -4
- package/dist/{chunk-6RIA6AJ3.js → chunk-IFYMZLQI.js} +1 -9
- package/dist/{chunk-6RIA6AJ3.js.map → chunk-IFYMZLQI.js.map} +1 -1
- package/dist/{chunk-R4YTIP7E.js → chunk-JBDK5WP3.js} +2 -2
- package/dist/{chunk-JUM5ZG64.js → chunk-SIKUIQKX.js} +408 -28
- package/dist/chunk-SIKUIQKX.js.map +1 -0
- package/dist/chunk-ZTJYFJQG.js +59 -0
- package/dist/chunk-ZTJYFJQG.js.map +1 -0
- package/dist/cli.js +32 -5
- package/dist/cli.js.map +1 -1
- package/dist/image-downloader-VKPGS3TY.js +8 -0
- package/dist/index.js +3 -3
- package/dist/server-KLUDWSZZ.js +10 -0
- package/dist/tui/index.js +135 -21
- package/dist/tui/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-JUM5ZG64.js.map +0 -1
- package/dist/image-downloader-AKNR5YKJ.js +0 -8
- package/dist/server-FKROUVDL.js +0 -10
- /package/dist/{chunk-5IY42AV6.js.map → chunk-6GCKWJJ7.js.map} +0 -0
- /package/dist/{chunk-R4YTIP7E.js.map → chunk-JBDK5WP3.js.map} +0 -0
- /package/dist/{image-downloader-AKNR5YKJ.js.map → image-downloader-VKPGS3TY.js.map} +0 -0
- /package/dist/{server-FKROUVDL.js.map → server-KLUDWSZZ.js.map} +0 -0
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
__require,
|
|
3
2
|
logger
|
|
4
|
-
} from "./chunk-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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,
|
|
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="${
|
|
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
|
|
417
|
-
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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 `} "${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
|
-
|
|
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
|
-
|
|
966
|
+
const dimensions = buildImageDimensionAttributes(attrs);
|
|
967
|
+
return `<img src="${escapeHtmlAttribute(url)}" alt="${escapeHtmlAttribute(alt)}"${dimensions} />`;
|
|
582
968
|
});
|
|
583
|
-
|
|
584
|
-
|
|
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-
|
|
1240
|
+
//# sourceMappingURL=chunk-SIKUIQKX.js.map
|