@wonseok-han/confluence-sync 2.0.2 → 2.0.3
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/dist/documents/catalog.js +5 -3
- package/dist/site/assets/app.js +4 -4
- package/dist/site/assets/workspace.worker.js +103 -103
- package/dist/site/index.html +2 -2
- package/dist/sync/prepare.js +23 -27
- package/dist/sync/reconcile.js +2 -2
- package/dist/sync/render.js +4 -4
- package/dist/sync/staging.js +2 -3
- package/dist/web/assets/app.js +4 -4
- package/dist/web/assets/workspace.worker.js +103 -103
- package/package.json +1 -1
package/dist/site/index.html
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="ko"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'sha256-gC1xP5ikAuS9OEjIyx77wCJoX4h4jhP+0cQnKC4FHD4='; connect-src 'self' http://127.0.0.1:* http://localhost:* https://*.blob.vercel-storage.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-src 'self'; worker-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'"><meta name="referrer" content="no-referrer"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="csync-token" content=""><meta name="csync-runtime" content="standalone"><meta name="csync-version" content="2.0.
|
|
3
|
-
<header class="app-header"><a class="brand" href="/" aria-label="confluence-sync 홈"><img class="brand-symbol" src="/assets/favicon.svg?v=paperlink" alt="" aria-hidden="true" width="30" height="30">confluence<span class="brand-light">sync</span></a><span class="app-version" aria-label="현재 버전">v2.0.
|
|
2
|
+
<html lang="ko"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'sha256-gC1xP5ikAuS9OEjIyx77wCJoX4h4jhP+0cQnKC4FHD4='; connect-src 'self' http://127.0.0.1:* http://localhost:* https://*.blob.vercel-storage.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-src 'self'; worker-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'"><meta name="referrer" content="no-referrer"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="csync-token" content=""><meta name="csync-runtime" content="standalone"><meta name="csync-version" content="2.0.3"><title>문서 워크스페이스 · confluence-sync</title><link rel="icon" type="image/svg+xml" href="/assets/favicon.svg?v=paperlink"><link rel="stylesheet" href="/assets/app.css"></head><body>
|
|
3
|
+
<header class="app-header"><a class="brand" href="/" aria-label="confluence-sync 홈"><img class="brand-symbol" src="/assets/favicon.svg?v=paperlink" alt="" aria-hidden="true" width="30" height="30">confluence<span class="brand-light">sync</span></a><span class="app-version" aria-label="현재 버전">v2.0.3</span><span class="local-status"><i></i><span id="local-connection-status">브라우저 폴더 선택 필요</span></span><button id="local-disconnect" class="quiet" type="button" hidden>연결 해제</button></header>
|
|
4
4
|
<main id="workspace-interface" class="app-main">
|
|
5
5
|
<div class="page-heading"><div><div class="eyebrow">DOCUMENT WORKSPACE</div><h1>문서 워크스페이스<span class="title-dot">.</span></h1></div><div class="page-heading-actions"><p>문서를 변환하고, Confluence에서 가져오거나 업로드하세요.</p><button id="open-help" aria-haspopup="dialog" aria-controls="help-dialog">사용 도움말</button></div></div>
|
|
6
6
|
<div class="mode-tabs" role="tablist" aria-label="문서 작업"><button id="tab-convert" role="tab" aria-selected="true" aria-controls="convert-view">문서 변환</button><button id="tab-sync" role="tab" aria-selected="false" aria-controls="sync-view" tabindex="-1">Confluence</button></div>
|
package/dist/sync/prepare.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import {
|
|
5
|
+
import { readDoc } from '../documents/catalog.js';
|
|
6
6
|
import { prepareMarkdown } from '../conversion/markdown.js';
|
|
7
7
|
import { within } from './workspace.js';
|
|
8
8
|
import { buildTreeRenderer } from './render.js';
|
|
@@ -12,38 +12,34 @@ export async function preparePushTree(base, rels, selected, options) {
|
|
|
12
12
|
try {
|
|
13
13
|
const chosen = new Set(selected);
|
|
14
14
|
const sourceRenderer = buildTreeRenderer(base, rels);
|
|
15
|
-
const vault =
|
|
16
|
-
|
|
15
|
+
const vault = sourceRenderer.vault;
|
|
16
|
+
const preparedDocs = { ...sourceRenderer.docs };
|
|
17
|
+
for (const rel of rels.filter(rel => chosen.has(rel))) {
|
|
17
18
|
const original = resolve(base, rel), destination = resolve(stage, rel);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const target = resolve(stage, asset);
|
|
25
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
26
|
-
copyFileSync(source, target);
|
|
27
|
-
}
|
|
19
|
+
// Preview renders actual temporary PNGs too, so attachment hashes match Push.
|
|
20
|
+
const converted = await prepareMarkdown(readFileSync(original, 'utf8'), original, base, vault, stage, false, undefined, false);
|
|
21
|
+
for (const [asset, source] of converted.copies) {
|
|
22
|
+
const target = resolve(stage, asset);
|
|
23
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
24
|
+
copyFileSync(source, target);
|
|
28
25
|
}
|
|
29
|
-
// Use the
|
|
26
|
+
// Use the source renderer as the final asset authority: conversion may
|
|
30
27
|
// miss balanced parentheses in Markdown paths or assets inside raw HTML.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
copyFileSync(asset.abs, target);
|
|
41
|
-
}
|
|
28
|
+
const doc = sourceRenderer.docs[rel];
|
|
29
|
+
for (const asset of sourceRenderer.render(rel, doc.body, doc.title).attachments) {
|
|
30
|
+
if (!within(base, asset.abs) || !existsSync(asset.abs))
|
|
31
|
+
continue;
|
|
32
|
+
const target = resolve(stage, relative(base, asset.abs));
|
|
33
|
+
if (existsSync(target))
|
|
34
|
+
continue;
|
|
35
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
36
|
+
copyFileSync(asset.abs, target);
|
|
42
37
|
}
|
|
43
38
|
mkdirSync(dirname(destination), { recursive: true });
|
|
44
|
-
writeFileSync(destination, text);
|
|
39
|
+
writeFileSync(destination, converted.text);
|
|
40
|
+
preparedDocs[rel] = readDoc(stage, rel);
|
|
45
41
|
}
|
|
46
|
-
return { ...buildTreeRenderer(stage, rels, { referenceRoots: [], ...options, referenceBase: base }), dispose };
|
|
42
|
+
return { ...buildTreeRenderer(stage, rels, { referenceRoots: [], ...options, referenceBase: base }, preparedDocs), original: sourceRenderer, dispose };
|
|
47
43
|
}
|
|
48
44
|
catch (error) {
|
|
49
45
|
dispose();
|
package/dist/sync/reconcile.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { buildFolderIndex, parentKeyOf, readDoc } from '../documents/catalog.js';
|
|
5
|
-
export function reconcilePages(base, mapping, allFiles, selected, scope) {
|
|
5
|
+
export function reconcilePages(base, mapping, allFiles, selected, scope, sourceDocs) {
|
|
6
6
|
const next = structuredClone(mapping);
|
|
7
7
|
const byId = new Map(Object.entries(mapping).map(([key, entry]) => [entry.pageId, { key, entry }]));
|
|
8
8
|
const oldFolders = buildFolderIndex(Object.keys(mapping).filter(key => key.endsWith('.md')));
|
|
9
9
|
const folders = buildFolderIndex(allFiles);
|
|
10
|
-
const docs = new Map(allFiles.map(key => [key, readDoc(base, key)]));
|
|
10
|
+
const docs = new Map(allFiles.map(key => [key, sourceDocs?.[key] ?? readDoc(base, key)]));
|
|
11
11
|
const seen = new Map();
|
|
12
12
|
for (const [key, doc] of docs) {
|
|
13
13
|
const id = doc.fm.pageId ?? mapping[key]?.pageId;
|
package/dist/sync/render.js
CHANGED
|
@@ -11,10 +11,10 @@ import { buildReferenceLookup } from './references.js';
|
|
|
11
11
|
import { relative } from 'node:path';
|
|
12
12
|
import { toStorage } from '../documents/markdown.js';
|
|
13
13
|
import { buildDocIndex, buildVault, collectAssets, vaultResolver, resolveAnchorTargets, } from '../documents/catalog.js';
|
|
14
|
-
export function buildTreeRenderer(baseDir, rels, options) {
|
|
15
|
-
const { titles, anchors, docs } = buildDocIndex(rels, baseDir);
|
|
14
|
+
export function buildTreeRenderer(baseDir, rels, options, sourceDocs) {
|
|
15
|
+
const { titles, anchors, docs } = buildDocIndex(rels, baseDir, sourceDocs);
|
|
16
16
|
// Obsidian [[wikilink]] 해석용 이름 인덱스
|
|
17
|
-
const vault = buildVault(rels, collectAssets(baseDir).map((f) => relative(baseDir, f)), options?.referenceBase ?? baseDir);
|
|
17
|
+
const vault = buildVault(rels, collectAssets(baseDir).map((f) => relative(baseDir, f).split('\\').join('/')), options?.referenceBase ?? baseDir);
|
|
18
18
|
// 누가 어느 헤딩을 가리키는지 먼저 모은다 — 그 헤딩에만 Anchor 매크로를 심는다.
|
|
19
19
|
const lookup = options ? buildReferenceLookup(options.referenceBase ?? baseDir, rels, options.referenceRoots, options.baseUrl) : null;
|
|
20
20
|
const anchorTargets = resolveAnchorTargets(docs, vault, lookup?.localTarget);
|
|
@@ -44,5 +44,5 @@ export function buildTreeRenderer(baseDir, rels, options) {
|
|
|
44
44
|
});
|
|
45
45
|
return { ...result, linkWarnings: [...warnings], referenceLinks: [...references] };
|
|
46
46
|
};
|
|
47
|
-
return { titles, docs, render };
|
|
47
|
+
return { titles, docs, render, vault };
|
|
48
48
|
}
|
package/dist/sync/staging.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { green, cyan } from '../shared/colors.js';
|
|
2
2
|
import { reconcilePages } from './reconcile.js';
|
|
3
3
|
import { matchesLocalState } from './local-state.js';
|
|
4
|
-
import { buildTreeRenderer } from './render.js';
|
|
5
4
|
/** Document-level upload selection. Source files are never copied into the index. */
|
|
6
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
6
|
import { existsSync, lstatSync, readFileSync, mkdirSync, writeFileSync, renameSync, unlinkSync, realpathSync } from 'node:fs';
|
|
@@ -101,8 +100,8 @@ export async function inspectStaging(base, options = {}, only) {
|
|
|
101
100
|
const prepared = await preparePushTree(base, all, selected, { referenceRoots: options.referenceRoots ?? [], baseUrl: options.baseUrl });
|
|
102
101
|
try {
|
|
103
102
|
const originalMapping = loadMapping(mappingPath(base));
|
|
104
|
-
const mapping = reconcilePages(base, originalMapping, all, selected, requireScope(workspace));
|
|
105
|
-
const legacy =
|
|
103
|
+
const mapping = reconcilePages(base, originalMapping, all, selected, requireScope(workspace), prepared.original.docs);
|
|
104
|
+
const legacy = prepared.original;
|
|
106
105
|
const rows = selected.map(path => {
|
|
107
106
|
const doc = prepared.docs[path], rendered = prepared.render(path, doc.body, doc.title), hash = docHash(doc.title, rendered);
|
|
108
107
|
if (sourceHash(base, path) !== sources[path])
|