tdecollab 0.3.4 → 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 +43 -17
- package/dist/{chunk-JI2YUE7N.js → chunk-4QW3PXPJ.js} +4 -4
- package/dist/chunk-4QW3PXPJ.js.map +1 -0
- package/dist/{chunk-4DPCLTF4.js → chunk-6GCKWJJ7.js} +3 -3
- package/dist/{chunk-5OB3KU5D.js → chunk-SIKUIQKX.js} +428 -38
- 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 +31 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/server-KLUDWSZZ.js +10 -0
- package/dist/tui/index.js +134 -20
- package/dist/tui/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-5OB3KU5D.js.map +0 -1
- package/dist/chunk-JI2YUE7N.js.map +0 -1
- package/dist/server-VTZMTIEK.js +0 -10
- /package/dist/{chunk-4DPCLTF4.js.map → chunk-6GCKWJJ7.js.map} +0 -0
- /package/dist/{server-VTZMTIEK.js.map → server-KLUDWSZZ.js.map} +0 -0
|
@@ -4,16 +4,21 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// tools/common/env-loader.ts
|
|
6
6
|
import dotenv from "dotenv";
|
|
7
|
+
import fs from "fs";
|
|
7
8
|
import path from "path";
|
|
8
9
|
import os from "os";
|
|
9
10
|
var loaded = false;
|
|
11
|
+
var lastResult = null;
|
|
10
12
|
function getHomeDir() {
|
|
11
13
|
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
12
14
|
}
|
|
13
15
|
function loadEnv() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
if (loaded && lastResult) return lastResult;
|
|
17
|
+
const result = { loadedFiles: [], skippedFiles: [], sources: {} };
|
|
16
18
|
loaded = true;
|
|
19
|
+
for (const key of Object.keys(process.env)) {
|
|
20
|
+
result.sources[key] = "shell env";
|
|
21
|
+
}
|
|
17
22
|
const candidates = [
|
|
18
23
|
// 우선순위 2: 현재 디렉토리
|
|
19
24
|
path.resolve(process.cwd(), "tdecollab.env"),
|
|
@@ -21,15 +26,31 @@ function loadEnv() {
|
|
|
21
26
|
path.join(getHomeDir(), ".config", "tdecollab", ".env")
|
|
22
27
|
];
|
|
23
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));
|
|
24
35
|
const out = dotenv.config({ path: filepath, override: false });
|
|
25
36
|
if (out.error) {
|
|
26
37
|
result.skippedFiles.push(filepath);
|
|
27
38
|
} else {
|
|
28
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
|
+
}
|
|
29
45
|
}
|
|
30
46
|
}
|
|
47
|
+
lastResult = result;
|
|
31
48
|
return result;
|
|
32
49
|
}
|
|
50
|
+
function getEnvSource(key) {
|
|
51
|
+
const result = loadEnv();
|
|
52
|
+
return result.sources[key] || "<unset>";
|
|
53
|
+
}
|
|
33
54
|
|
|
34
55
|
// tools/confluence/api/content.ts
|
|
35
56
|
var ConfluenceContentApi = class {
|
|
@@ -38,14 +59,14 @@ var ConfluenceContentApi = class {
|
|
|
38
59
|
}
|
|
39
60
|
async getPage(id, expand) {
|
|
40
61
|
const expandParam = expand ? expand.join(",") : "body.storage,version,space,metadata.labels";
|
|
41
|
-
const response = await this.client.get(
|
|
62
|
+
const response = await this.client.get(`rest/api/content/${id}`, {
|
|
42
63
|
params: { expand: expandParam }
|
|
43
64
|
});
|
|
44
65
|
return response.data;
|
|
45
66
|
}
|
|
46
67
|
async getPageByTitle(spaceKey, title, expand) {
|
|
47
68
|
const expandParam = expand ? expand.join(",") : "body.storage,version,space";
|
|
48
|
-
const response = await this.client.get("
|
|
69
|
+
const response = await this.client.get("rest/api/content", {
|
|
49
70
|
params: {
|
|
50
71
|
spaceKey,
|
|
51
72
|
title,
|
|
@@ -73,7 +94,7 @@ var ConfluenceContentApi = class {
|
|
|
73
94
|
if (params.parentId) {
|
|
74
95
|
data.ancestors = [{ id: params.parentId }];
|
|
75
96
|
}
|
|
76
|
-
const response = await this.client.post("
|
|
97
|
+
const response = await this.client.post("rest/api/content", data);
|
|
77
98
|
if (params.labels && params.labels.length > 0) {
|
|
78
99
|
await this.addLabels(response.data.id, params.labels);
|
|
79
100
|
}
|
|
@@ -91,14 +112,14 @@ var ConfluenceContentApi = class {
|
|
|
91
112
|
}
|
|
92
113
|
}
|
|
93
114
|
};
|
|
94
|
-
const response = await this.client.put(
|
|
115
|
+
const response = await this.client.put(`rest/api/content/${params.id}`, data);
|
|
95
116
|
return response.data;
|
|
96
117
|
}
|
|
97
118
|
async deletePage(id) {
|
|
98
|
-
await this.client.delete(
|
|
119
|
+
await this.client.delete(`rest/api/content/${id}`);
|
|
99
120
|
}
|
|
100
121
|
async getChildPages(id, start = 0, limit = 25) {
|
|
101
|
-
const response = await this.client.get(
|
|
122
|
+
const response = await this.client.get(`rest/api/content/${id}/child/page`, {
|
|
102
123
|
params: { start, limit }
|
|
103
124
|
});
|
|
104
125
|
return response.data.results;
|
|
@@ -112,7 +133,7 @@ var ConfluenceContentApi = class {
|
|
|
112
133
|
// For now, simple implementation to support createPage.
|
|
113
134
|
async addLabels(id, labels) {
|
|
114
135
|
const data = labels.map((name) => ({ prefix: "global", name }));
|
|
115
|
-
await this.client.post(
|
|
136
|
+
await this.client.post(`rest/api/content/${id}/label`, data);
|
|
116
137
|
}
|
|
117
138
|
// Attachment 관련 메서드 (upsert: 기존 파일이 있으면 업데이트, 없으면 신규 업로드)
|
|
118
139
|
async uploadAttachment(pageId, filename, fileContent, contentType) {
|
|
@@ -132,13 +153,13 @@ var ConfluenceContentApi = class {
|
|
|
132
153
|
let response;
|
|
133
154
|
if (existing) {
|
|
134
155
|
response = await this.client.post(
|
|
135
|
-
|
|
156
|
+
`rest/api/content/${pageId}/child/attachment/${existing.id}/data`,
|
|
136
157
|
form,
|
|
137
158
|
{ headers }
|
|
138
159
|
);
|
|
139
160
|
} else {
|
|
140
161
|
response = await this.client.post(
|
|
141
|
-
|
|
162
|
+
`rest/api/content/${pageId}/child/attachment`,
|
|
142
163
|
form,
|
|
143
164
|
{ headers }
|
|
144
165
|
);
|
|
@@ -149,7 +170,7 @@ var ConfluenceContentApi = class {
|
|
|
149
170
|
return response.data;
|
|
150
171
|
}
|
|
151
172
|
async getAttachments(pageId, filename) {
|
|
152
|
-
const response = await this.client.get(
|
|
173
|
+
const response = await this.client.get(`rest/api/content/${pageId}/child/attachment`, {
|
|
153
174
|
params: {
|
|
154
175
|
filename,
|
|
155
176
|
expand: "version"
|
|
@@ -171,7 +192,7 @@ var ConfluenceSpaceApi = class {
|
|
|
171
192
|
this.client = client;
|
|
172
193
|
}
|
|
173
194
|
async getSpaces(type = "global", start = 0, limit = 25) {
|
|
174
|
-
const response = await this.client.get("
|
|
195
|
+
const response = await this.client.get("rest/api/space", {
|
|
175
196
|
params: { type, start, limit }
|
|
176
197
|
});
|
|
177
198
|
return response.data.results;
|
|
@@ -189,7 +210,7 @@ var ConfluenceSearchApi = class {
|
|
|
189
210
|
}
|
|
190
211
|
async searchByCql(cql, start = 0, limit = 25, expand) {
|
|
191
212
|
const expandParam = expand ? expand.join(",") : "body.storage,version,space,metadata.labels";
|
|
192
|
-
const response = await this.client.get("
|
|
213
|
+
const response = await this.client.get("rest/api/content/search", {
|
|
193
214
|
params: {
|
|
194
215
|
cql,
|
|
195
216
|
start,
|
|
@@ -237,20 +258,23 @@ var ConflictError = class extends TdeCollabError {
|
|
|
237
258
|
|
|
238
259
|
// tools/common/http-client.ts
|
|
239
260
|
function createHttpClient(config) {
|
|
261
|
+
const normalizedBaseUrl = config.baseUrl.endsWith("/") ? config.baseUrl : `${config.baseUrl}/`;
|
|
240
262
|
const client = axios.create({
|
|
241
|
-
baseURL:
|
|
263
|
+
baseURL: normalizedBaseUrl,
|
|
242
264
|
timeout: 3e4,
|
|
243
265
|
// 30초 타임아웃
|
|
266
|
+
adapter: "http",
|
|
267
|
+
// Electron 환경(Obsidian)에서 XHR 대신 Node.js http 모듈을 사용하여 CORS 우회
|
|
244
268
|
headers: {
|
|
245
269
|
"Content-Type": "application/json"
|
|
246
270
|
}
|
|
247
271
|
});
|
|
248
272
|
client.interceptors.request.use((reqConfig) => {
|
|
249
|
-
if (config.auth.
|
|
250
|
-
reqConfig.headers.Authorization = `Bearer ${config.auth.token}`;
|
|
251
|
-
} else if (config.auth.username && config.auth.token && !reqConfig.headers.Authorization) {
|
|
273
|
+
if (config.auth.username && config.auth.token && !reqConfig.headers.Authorization) {
|
|
252
274
|
const token = Buffer.from(`${config.auth.username}:${config.auth.token}`).toString("base64");
|
|
253
275
|
reqConfig.headers.Authorization = `Basic ${token}`;
|
|
276
|
+
} else if (config.auth.token && !reqConfig.headers.Authorization && !reqConfig.headers["PRIVATE-TOKEN"]) {
|
|
277
|
+
reqConfig.headers.Authorization = `Bearer ${config.auth.token}`;
|
|
254
278
|
}
|
|
255
279
|
logger.debug(`[HTTP Request] ${reqConfig.method?.toUpperCase()} ${reqConfig.url}`, {
|
|
256
280
|
headers: reqConfig.headers,
|
|
@@ -318,7 +342,8 @@ function getEnvOrThrow(key, description) {
|
|
|
318
342
|
}
|
|
319
343
|
function loadConfluenceConfig() {
|
|
320
344
|
const baseUrl = getEnvOrThrow("CONFLUENCE_BASE_URL", "Confluence \uAE30\uBCF8 URL");
|
|
321
|
-
const
|
|
345
|
+
const authType = (process.env.CONFLUENCE_AUTH_TYPE || "bearer").toLowerCase();
|
|
346
|
+
const username = authType === "basic" ? process.env.CONFLUENCE_USERNAME : void 0;
|
|
322
347
|
const token = getEnvOrThrow("CONFLUENCE_API_TOKEN", "Confluence PAT \uD1A0\uD070");
|
|
323
348
|
const mermaidMacroName = process.env.CONFLUENCE_MERMAID_MACRO_NAME || "mermaiddiagram";
|
|
324
349
|
const inlineCodeStyle = process.env.CONFLUENCE_INLINE_CODE_STYLE || "color: #d04437; font-weight: bold;";
|
|
@@ -365,14 +390,191 @@ function loadGitlabConfig() {
|
|
|
365
390
|
|
|
366
391
|
// tools/confluence/converters/md-to-storage.ts
|
|
367
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
|
+
}
|
|
368
565
|
var MarkdownToStorageConverter = class {
|
|
369
566
|
md;
|
|
370
567
|
mermaidMacroName;
|
|
371
568
|
inlineCodeStyle;
|
|
372
|
-
constructor() {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
+
}
|
|
376
578
|
this.md = new MarkdownIt({
|
|
377
579
|
html: true,
|
|
378
580
|
linkify: true,
|
|
@@ -380,6 +582,50 @@ var MarkdownToStorageConverter = class {
|
|
|
380
582
|
xhtmlOut: true
|
|
381
583
|
// Confluence XML 파서와의 호환성을 위해 XHTML 출력 활성화
|
|
382
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
|
+
};
|
|
383
629
|
this.md.renderer.rules.fence = (tokens, idx) => {
|
|
384
630
|
const token = tokens[idx];
|
|
385
631
|
const code = token.content.trim();
|
|
@@ -400,17 +646,19 @@ var MarkdownToStorageConverter = class {
|
|
|
400
646
|
<ac:plain-text-body><![CDATA[${code}]]></ac:plain-text-body>
|
|
401
647
|
</ac:structured-macro>`;
|
|
402
648
|
};
|
|
403
|
-
this.md.renderer.rules.image = (tokens, idx,
|
|
649
|
+
this.md.renderer.rules.image = (tokens, idx, options2, env, self) => {
|
|
404
650
|
const token = tokens[idx];
|
|
405
651
|
const src = token.attrGet("src") || "";
|
|
406
652
|
const alt = token.content || "";
|
|
653
|
+
const dimensionAttrs = parseImageDimensionTitle(token.attrGet("title"));
|
|
407
654
|
const isExternal = src.startsWith("http://") || src.startsWith("https://");
|
|
408
|
-
const altAttr = alt ? ` ac:alt="${
|
|
655
|
+
const altAttr = alt ? ` ac:alt="${escapeXmlAttribute(alt)}"` : "";
|
|
409
656
|
if (isExternal) {
|
|
410
|
-
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>`;
|
|
411
658
|
} else {
|
|
412
|
-
const
|
|
413
|
-
|
|
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>`;
|
|
414
662
|
}
|
|
415
663
|
};
|
|
416
664
|
this.md.renderer.rules.code_inline = (tokens, idx) => {
|
|
@@ -420,10 +668,10 @@ var MarkdownToStorageConverter = class {
|
|
|
420
668
|
};
|
|
421
669
|
}
|
|
422
670
|
convert(markdown) {
|
|
423
|
-
return this.md.render(markdown);
|
|
671
|
+
return this.md.render(prepareMarkdownForConfluenceStorage(markdown));
|
|
424
672
|
}
|
|
425
673
|
extractLocalImages(markdown) {
|
|
426
|
-
const tokens = this.md.parse(markdown, {});
|
|
674
|
+
const tokens = this.md.parse(prepareMarkdownForConfluenceStorage(markdown), {});
|
|
427
675
|
const localImages = /* @__PURE__ */ new Set();
|
|
428
676
|
const walk = (tokens2) => {
|
|
429
677
|
for (const token of tokens2) {
|
|
@@ -446,7 +694,38 @@ var MarkdownToStorageConverter = class {
|
|
|
446
694
|
// tools/confluence/converters/storage-to-md.ts
|
|
447
695
|
import TurndownService from "turndown";
|
|
448
696
|
import { gfm } from "turndown-plugin-gfm";
|
|
449
|
-
import {
|
|
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
|
+
}
|
|
450
729
|
var StorageToMarkdownConverter = class {
|
|
451
730
|
turndown;
|
|
452
731
|
jiraBaseUrl;
|
|
@@ -464,6 +743,37 @@ var StorageToMarkdownConverter = class {
|
|
|
464
743
|
this.setupRules();
|
|
465
744
|
}
|
|
466
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
|
+
});
|
|
467
777
|
this.turndown.addRule("tables", {
|
|
468
778
|
filter: ["table"],
|
|
469
779
|
replacement: (content, node) => {
|
|
@@ -563,6 +873,83 @@ ${bodyMd}
|
|
|
563
873
|
}
|
|
564
874
|
});
|
|
565
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
|
+
}
|
|
566
953
|
convert(storageHtml, imageUrlMap, jiraIssueMap) {
|
|
567
954
|
this.jiraIssueMap = jiraIssueMap;
|
|
568
955
|
if (!storageHtml) return "";
|
|
@@ -571,14 +958,16 @@ ${bodyMd}
|
|
|
571
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) => {
|
|
572
959
|
const altMatch = attrs.match(/ac:alt="([^"]*)"/i);
|
|
573
960
|
const alt = altMatch ? altMatch[1] : filename;
|
|
574
|
-
|
|
961
|
+
const dimensions = buildImageDimensionAttributes(attrs);
|
|
962
|
+
return `<img src="${escapeHtmlAttribute(filename)}" alt="${escapeHtmlAttribute(alt)}"${dimensions} />`;
|
|
575
963
|
}).replace(/<ac:image([^>]*)>[\s\S]*?<ri:url\s+ri:value="([^"]*)"\s*\/?>[\s\S]*?<\/ac:image>/gi, (match, attrs, url) => {
|
|
576
964
|
const altMatch = attrs.match(/ac:alt="([^"]*)"/i);
|
|
577
965
|
const alt = altMatch ? altMatch[1] : "";
|
|
578
|
-
|
|
966
|
+
const dimensions = buildImageDimensionAttributes(attrs);
|
|
967
|
+
return `<img src="${escapeHtmlAttribute(url)}" alt="${escapeHtmlAttribute(alt)}"${dimensions} />`;
|
|
579
968
|
});
|
|
580
|
-
const
|
|
581
|
-
|
|
969
|
+
const document = parseHtmlDocument(processedHtml);
|
|
970
|
+
this.normalizeMalformedConfluenceLists(document);
|
|
582
971
|
if (imageUrlMap && imageUrlMap.size > 0) {
|
|
583
972
|
const images = document.querySelectorAll("img");
|
|
584
973
|
images.forEach((img) => {
|
|
@@ -630,7 +1019,7 @@ var JiraIssueApi = class {
|
|
|
630
1019
|
if (params.customFields) {
|
|
631
1020
|
Object.assign(fields, params.customFields);
|
|
632
1021
|
}
|
|
633
|
-
const response = await this.client.post("
|
|
1022
|
+
const response = await this.client.post("rest/api/2/issue", { fields });
|
|
634
1023
|
return response.data;
|
|
635
1024
|
}
|
|
636
1025
|
async updateIssue(issueKey, params) {
|
|
@@ -716,7 +1105,7 @@ var JiraSearchApi = class {
|
|
|
716
1105
|
if (fields && fields.length > 0) {
|
|
717
1106
|
params.fields = fields.join(",");
|
|
718
1107
|
}
|
|
719
|
-
const response = await this.client.get("
|
|
1108
|
+
const response = await this.client.get("rest/api/2/search", { params });
|
|
720
1109
|
return response.data;
|
|
721
1110
|
}
|
|
722
1111
|
};
|
|
@@ -829,6 +1218,7 @@ function createGitlabClient(config) {
|
|
|
829
1218
|
|
|
830
1219
|
export {
|
|
831
1220
|
loadEnv,
|
|
1221
|
+
getEnvSource,
|
|
832
1222
|
ConfluenceContentApi,
|
|
833
1223
|
ConfluenceSpaceApi,
|
|
834
1224
|
ConfluenceSearchApi,
|
|
@@ -847,4 +1237,4 @@ export {
|
|
|
847
1237
|
GitlabPipelineApi,
|
|
848
1238
|
createGitlabClient
|
|
849
1239
|
};
|
|
850
|
-
//# sourceMappingURL=chunk-
|
|
1240
|
+
//# sourceMappingURL=chunk-SIKUIQKX.js.map
|