collabmd 0.1.36 → 0.1.38
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 +1 -0
- package/dist/client/assets/{drawio-editor-D4Qoluwz.js → drawio-editor-BHI0pLby.js} +1 -1
- package/dist/client/assets/{drawioEditor-B65FWq-b.js → drawioEditor-DOaXwaY2.js} +2 -2
- package/dist/client/assets/{editor-session-DV80PhYL.js → editor-session-BFsA-WRc.js} +1 -1
- package/dist/client/assets/{excalidraw-editor-hiJQt1cX.js → excalidraw-editor-6pgH2kf5.js} +1 -1
- package/dist/client/assets/{excalidrawEditor-BnbblxK4.js → excalidrawEditor-Ck9hgrTa.js} +2 -2
- package/dist/client/assets/export-page-4M8Zo-AL.js +39 -0
- package/dist/client/assets/exportDocument-BAqaHlIn.css +1 -0
- package/dist/client/assets/{exportDocument-CULEGKmb.js → exportDocument-mlXOym-R.js} +2 -2
- package/dist/client/assets/{index-BjdYZiBM.js → index-CCfHrm_9.js} +2 -2
- package/dist/client/assets/{index-BryqP9QY.css → index-maPm_Kvk.css} +1 -1
- package/dist/client/assets/main-BQMPQ4CW.js +1410 -0
- package/dist/client/assets/preview-render-compiler-9SmLUP5V.js +1 -0
- package/dist/client/assets/{preview-render-compiler-Tkc6JX1O.js → preview-render-compiler-IUtgIlOM.js} +1 -1
- package/dist/client/assets/{preview-render-worker-1LBLqgKu.js → preview-render-worker-BwQ5sPy9.js} +1 -1
- package/dist/client/assets/runtime-config-BcUbZKho.js +1 -0
- package/dist/client/assets/{vault-api-client-DSx3mWkH.js → vault-api-client-BDKKvtOF.js} +1 -1
- package/dist/client/drawio-editor.html +1 -1
- package/dist/client/excalidraw-editor.html +1 -1
- package/dist/client/export-document.html +2 -2
- package/dist/client/index.html +2 -2
- package/docker-compose.yml +1 -0
- package/package.json +12 -9
- package/src/client/application/app-shell/ui-feature-shell.js +237 -0
- package/src/client/application/preview-render-compiler.js +120 -1
- package/src/client/application/preview-render-executor.js +4 -0
- package/src/client/application/preview-render-worker.js +2 -0
- package/src/client/application/preview-renderer.js +5 -2
- package/src/client/application/wiki-link-file-controller.js +7 -0
- package/src/client/application/workspace-route-controller.js +9 -0
- package/src/client/bootstrap/collabmd-app-shell.js +13 -0
- package/src/client/domain/hash-routes.js +49 -0
- package/src/client/domain/runtime-paths.js +1 -0
- package/src/client/export/export-pipeline.js +100 -26
- package/src/client/infrastructure/runtime-config.js +22 -24
- package/src/client/presentation/outline-controller.js +28 -7
- package/src/client/styles/features/preview-markdown.css +51 -0
- package/src/server/config/env.js +1 -0
- package/src/server/infrastructure/http/create-static-handler.js +2 -0
- package/dist/client/assets/export-page-BKDarp9x.js +0 -39
- package/dist/client/assets/exportDocument-BbkN85c4.css +0 -1
- package/dist/client/assets/main-QJcxyvDa.js +0 -1404
- package/dist/client/assets/preview-render-compiler-BCB8u6iM.js +0 -1
- package/dist/client/assets/runtime-config-DWTejofP.js +0 -1
|
@@ -116,6 +116,9 @@ export class CollabMdAppShell {
|
|
|
116
116
|
bindEvents(...args) { return uiFeature.bindEvents.apply(this, args); }
|
|
117
117
|
clearInitialFileBootstrap(...args) { return uiFeature.clearInitialFileBootstrap.apply(this, args); }
|
|
118
118
|
closeToolbarOverflowMenu(...args) { return uiFeature.closeToolbarOverflowMenu.apply(this, args); }
|
|
119
|
+
applyPendingPreviewRouteAnchor(...args) { return uiFeature.applyPendingPreviewRouteAnchor.apply(this, args); }
|
|
120
|
+
copyPreviewHeadingLink(...args) { return uiFeature.copyPreviewHeadingLink.apply(this, args); }
|
|
121
|
+
createPreviewHeadingLinkUrl(...args) { return uiFeature.createPreviewHeadingLinkUrl.apply(this, args); }
|
|
119
122
|
getStoredLineWrapping(...args) { return uiFeature.getStoredLineWrapping.apply(this, args); }
|
|
120
123
|
handleConnectionChange(...args) { return uiFeature.handleConnectionChange.apply(this, args); }
|
|
121
124
|
handleDocumentKeydown(...args) { return uiFeature.handleDocumentKeydown.apply(this, args); }
|
|
@@ -126,11 +129,14 @@ export class CollabMdAppShell {
|
|
|
126
129
|
initialize(...args) { return uiFeature.initialize.apply(this, args); }
|
|
127
130
|
initializeVisualViewportBinding(...args) { return uiFeature.initializeVisualViewportBinding.apply(this, args); }
|
|
128
131
|
initializeVersionMonitoring(...args) { return uiFeature.initializeVersionMonitoring.apply(this, args); }
|
|
132
|
+
navigatePreviewHeading(...args) { return uiFeature.navigatePreviewHeading.apply(this, args); }
|
|
129
133
|
promptForVersionReload(...args) { return uiFeature.promptForVersionReload.apply(this, args); }
|
|
134
|
+
requestPreviewRouteAnchor(...args) { return uiFeature.requestPreviewRouteAnchor.apply(this, args); }
|
|
130
135
|
scheduleBacklinkRefresh(...args) { return uiFeature.scheduleBacklinkRefresh.apply(this, args); }
|
|
131
136
|
setToolbarOverflowOpen(...args) { return uiFeature.setToolbarOverflowOpen.apply(this, args); }
|
|
132
137
|
showEditorLoadError(...args) { return uiFeature.showEditorLoadError.apply(this, args); }
|
|
133
138
|
showEditorLoading(...args) { return uiFeature.showEditorLoading.apply(this, args); }
|
|
139
|
+
syncPreviewHeadingLinkButtons(...args) { return uiFeature.syncPreviewHeadingLinkButtons.apply(this, args); }
|
|
134
140
|
syncVisualViewportBounds(...args) { return uiFeature.syncVisualViewportBounds.apply(this, args); }
|
|
135
141
|
syncToolbarOverflowVisibility(...args) { return uiFeature.syncToolbarOverflowVisibility.apply(this, args); }
|
|
136
142
|
syncWrapToggle(...args) { return uiFeature.syncWrapToggle.apply(this, args); }
|
|
@@ -331,6 +337,7 @@ export class CollabMdAppShell {
|
|
|
331
337
|
this.previewRenderer = new PreviewRenderer({
|
|
332
338
|
getContent: () => this.getPreviewSource(),
|
|
333
339
|
getFileList: () => this.fileExplorer.flatDocumentFiles,
|
|
340
|
+
getWikiLinkAutoCreate: () => this.runtimeConfig.wikiLinkAutoCreate !== false,
|
|
334
341
|
loadFileSource: async (filePath) => {
|
|
335
342
|
const payload = await this.vaultApiClient.readFile(filePath);
|
|
336
343
|
return String(payload?.content ?? '');
|
|
@@ -349,6 +356,8 @@ export class CollabMdAppShell {
|
|
|
349
356
|
this.excalidrawEmbed.reconcileEmbeds(this.elements.previewContent, { isLargeDocument: stats.isLargeDocument });
|
|
350
357
|
this.excalidrawEmbed.syncLayout();
|
|
351
358
|
this.scrollSyncController.setLargeDocumentMode(stats.isLargeDocument);
|
|
359
|
+
this.syncPreviewHeadingLinkButtons();
|
|
360
|
+
this.applyPendingPreviewRouteAnchor({ behavior: 'auto', clearMissing: true });
|
|
352
361
|
this.schedulePreviewLayoutSync({ delayMs: 0 });
|
|
353
362
|
this.refreshCommentUiLayout();
|
|
354
363
|
},
|
|
@@ -359,6 +368,7 @@ export class CollabMdAppShell {
|
|
|
359
368
|
},
|
|
360
369
|
onPreviewLayoutChange: () => {
|
|
361
370
|
this.scrollSyncController.invalidatePreviewBlocks();
|
|
371
|
+
this.applyPendingPreviewRouteAnchor({ behavior: 'auto', clearMissing: false });
|
|
362
372
|
this.schedulePreviewLayoutSync({ delayMs: 0 });
|
|
363
373
|
this.refreshCommentUiLayout();
|
|
364
374
|
},
|
|
@@ -366,6 +376,7 @@ export class CollabMdAppShell {
|
|
|
366
376
|
this.videoEmbed.syncLayout();
|
|
367
377
|
this.drawioEmbed.syncLayout();
|
|
368
378
|
this.excalidrawEmbed.syncLayout();
|
|
379
|
+
this.applyPendingPreviewRouteAnchor({ allowExpired: true, behavior: 'auto', clearMissing: true });
|
|
369
380
|
this.schedulePreviewLayoutSync({ delayMs: 0 });
|
|
370
381
|
this.refreshCommentUiLayout();
|
|
371
382
|
},
|
|
@@ -464,6 +475,7 @@ export class CollabMdAppShell {
|
|
|
464
475
|
refreshExplorer: () => this.fileExplorer.refresh(),
|
|
465
476
|
toastController: this.toastController,
|
|
466
477
|
vaultApiClient: this.vaultApiClient,
|
|
478
|
+
wikiLinkAutoCreate: this.runtimeConfig.wikiLinkAutoCreate !== false,
|
|
467
479
|
});
|
|
468
480
|
this.gitDiffView = new GitDiffViewController({
|
|
469
481
|
gitApiClient: this.gitApiClient,
|
|
@@ -635,6 +647,7 @@ export class CollabMdAppShell {
|
|
|
635
647
|
lobby: this.lobby,
|
|
636
648
|
navigation: this.navigation,
|
|
637
649
|
previewRenderer: this.previewRenderer,
|
|
650
|
+
requestPreviewRouteAnchor: (anchorId, filePath) => this.requestPreviewRouteAnchor(anchorId, filePath),
|
|
638
651
|
renderAvatars: () => this.renderAvatars(),
|
|
639
652
|
renderPresence: () => this.renderPresence(),
|
|
640
653
|
resetPreviewMode: () => this.resetPreviewMode(),
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const HASH_ROUTE_KEYS = Object.freeze({
|
|
2
|
+
authError: 'auth_error',
|
|
3
|
+
authPassword: 'auth_password',
|
|
4
|
+
file: 'file',
|
|
5
|
+
gitCommit: 'git-commit',
|
|
6
|
+
gitDiff: 'git-diff',
|
|
7
|
+
gitFileHistory: 'git-file-history',
|
|
8
|
+
gitFilePreview: 'git-file-preview',
|
|
9
|
+
gitHistory: 'git-history',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export function getHashParamsFromRaw(hash = '') {
|
|
13
|
+
const rawHash = String(hash ?? '');
|
|
14
|
+
const normalizedHash = rawHash.startsWith('#')
|
|
15
|
+
? rawHash.slice(1)
|
|
16
|
+
: rawHash;
|
|
17
|
+
return new URLSearchParams(normalizedHash);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getNormalizedHash(hash = '') {
|
|
21
|
+
const rawHash = String(hash ?? '');
|
|
22
|
+
return rawHash.startsWith('#')
|
|
23
|
+
? rawHash.slice(1)
|
|
24
|
+
: rawHash;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isCollabMdHashRoute(hash = '') {
|
|
28
|
+
const normalizedHash = getNormalizedHash(hash).trim();
|
|
29
|
+
if (!normalizedHash.includes('=')) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const params = getHashParamsFromRaw(hash);
|
|
34
|
+
return Object.values(HASH_ROUTE_KEYS).some((key) => params.has(key));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createFileRouteHash(filePath, { drawioMode = null, anchor = null } = {}) {
|
|
38
|
+
const params = new URLSearchParams();
|
|
39
|
+
if (filePath) {
|
|
40
|
+
params.set(HASH_ROUTE_KEYS.file, filePath);
|
|
41
|
+
}
|
|
42
|
+
if (drawioMode) {
|
|
43
|
+
params.set('drawio', drawioMode);
|
|
44
|
+
}
|
|
45
|
+
if (anchor) {
|
|
46
|
+
params.set('anchor', String(anchor).trim());
|
|
47
|
+
}
|
|
48
|
+
return params.toString();
|
|
49
|
+
}
|
|
@@ -26,6 +26,20 @@ const EXPORT_ASSET_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
26
26
|
const EXPORT_RENDER_SETTLE_TIMEOUT_MS = 10_000;
|
|
27
27
|
const VIDEO_POSTER_CAPTURE_TIMEOUT_MS = 10_000;
|
|
28
28
|
const VIDEO_POSTER_CAPTURE_SEEK_SECONDS = 1;
|
|
29
|
+
const DOCX_COMPATIBLE_IMAGE_MIME_TYPES = new Set([
|
|
30
|
+
'image/jpeg',
|
|
31
|
+
'image/jpg',
|
|
32
|
+
'image/png',
|
|
33
|
+
'image/svg+xml',
|
|
34
|
+
]);
|
|
35
|
+
const DOCX_PNG_VARIANT_IMAGE_MIME_TYPES = new Set([
|
|
36
|
+
'image/gif',
|
|
37
|
+
'image/webp',
|
|
38
|
+
]);
|
|
39
|
+
const DOCX_IMAGE_TYPE_LABELS = new Map([
|
|
40
|
+
['image/gif', 'GIF'],
|
|
41
|
+
['image/webp', 'WebP'],
|
|
42
|
+
]);
|
|
29
43
|
|
|
30
44
|
let mermaidLoaderPromise = null;
|
|
31
45
|
let assetCounter = 0;
|
|
@@ -49,6 +63,16 @@ function encodeSvgDataUrl(svgMarkup) {
|
|
|
49
63
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgMarkup)}`;
|
|
50
64
|
}
|
|
51
65
|
|
|
66
|
+
function encodeSvgBase64DataUrl(svgMarkup) {
|
|
67
|
+
const bytes = new TextEncoder().encode(String(svgMarkup ?? ''));
|
|
68
|
+
const chunkSize = 0x8000;
|
|
69
|
+
let binary = '';
|
|
70
|
+
for (let index = 0; index < bytes.length; index += chunkSize) {
|
|
71
|
+
binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
|
|
72
|
+
}
|
|
73
|
+
return `data:image/svg+xml;base64,${btoa(binary)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
52
76
|
function waitForAnimationFrame() {
|
|
53
77
|
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
54
78
|
}
|
|
@@ -144,28 +168,40 @@ function normalizeExportSvgMarkup(svgMarkup, options = {}) {
|
|
|
144
168
|
}
|
|
145
169
|
|
|
146
170
|
async function svgMarkupToPngDataUrl(svgMarkup) {
|
|
147
|
-
const
|
|
148
|
-
|
|
171
|
+
const svgBlobUrl = URL.createObjectURL(new Blob([svgMarkup], {
|
|
172
|
+
type: 'image/svg+xml;charset=utf-8',
|
|
173
|
+
}));
|
|
149
174
|
const svgProbe = document.createElement('div');
|
|
150
175
|
svgProbe.innerHTML = svgMarkup;
|
|
151
176
|
const svgElement = svgProbe.querySelector('svg');
|
|
152
|
-
const { width, height } = svgElement ? resolveSvgDimensions(svgElement) : {
|
|
153
|
-
height: image.naturalHeight || 800,
|
|
154
|
-
width: image.naturalWidth || 1200,
|
|
155
|
-
};
|
|
156
177
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
178
|
+
try {
|
|
179
|
+
let image;
|
|
180
|
+
try {
|
|
181
|
+
image = await loadImage(svgBlobUrl);
|
|
182
|
+
} catch {
|
|
183
|
+
image = await loadImage(encodeSvgBase64DataUrl(svgMarkup));
|
|
184
|
+
}
|
|
185
|
+
const { width, height } = svgElement ? resolveSvgDimensions(svgElement) : {
|
|
186
|
+
height: image.naturalHeight || 800,
|
|
187
|
+
width: image.naturalWidth || 1200,
|
|
188
|
+
};
|
|
164
189
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
190
|
+
const canvas = document.createElement('canvas');
|
|
191
|
+
canvas.width = Math.max(1, Math.ceil(width));
|
|
192
|
+
canvas.height = Math.max(1, Math.ceil(height));
|
|
193
|
+
const context = canvas.getContext('2d');
|
|
194
|
+
if (!context) {
|
|
195
|
+
throw new Error('Canvas is unavailable');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
context.fillStyle = '#ffffff';
|
|
199
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
200
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
201
|
+
return canvas.toDataURL('image/png');
|
|
202
|
+
} finally {
|
|
203
|
+
URL.revokeObjectURL(svgBlobUrl);
|
|
204
|
+
}
|
|
169
205
|
}
|
|
170
206
|
|
|
171
207
|
async function imageDataUrlToPngDataUrl(dataUrl) {
|
|
@@ -182,6 +218,33 @@ async function imageDataUrlToPngDataUrl(dataUrl) {
|
|
|
182
218
|
return canvas.toDataURL('image/png');
|
|
183
219
|
}
|
|
184
220
|
|
|
221
|
+
function getDataUrlMimeType(dataUrl = '') {
|
|
222
|
+
const match = String(dataUrl ?? '').match(/^data:([^;,]+)/iu);
|
|
223
|
+
return match?.[1]?.toLowerCase() || '';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function createDocxImageDataUrl(dataUrl, {
|
|
227
|
+
warnings = [],
|
|
228
|
+
} = {}) {
|
|
229
|
+
const mimeType = getDataUrlMimeType(dataUrl);
|
|
230
|
+
if (DOCX_COMPATIBLE_IMAGE_MIME_TYPES.has(mimeType)) {
|
|
231
|
+
return dataUrl;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!DOCX_PNG_VARIANT_IMAGE_MIME_TYPES.has(mimeType)) {
|
|
235
|
+
return dataUrl;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const imageTypeLabel = DOCX_IMAGE_TYPE_LABELS.get(mimeType) || mimeType;
|
|
239
|
+
try {
|
|
240
|
+
return await imageDataUrlToPngDataUrl(dataUrl);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const message = error instanceof Error ? error.message : `Failed to convert ${imageTypeLabel} image for DOCX`;
|
|
243
|
+
warnings.push(`Failed to convert ${imageTypeLabel} image for DOCX: ${message}`);
|
|
244
|
+
return dataUrl;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
185
248
|
async function waitForImageElement(image, {
|
|
186
249
|
timeoutMs = EXPORT_RENDER_SETTLE_TIMEOUT_MS,
|
|
187
250
|
} = {}) {
|
|
@@ -781,7 +844,7 @@ async function resolveDiagramShell(snapshot, shell, { label, prefix, render }) {
|
|
|
781
844
|
|
|
782
845
|
const svgMarkup = await render(source, target);
|
|
783
846
|
const svgDataUrl = encodeSvgDataUrl(svgMarkup);
|
|
784
|
-
const
|
|
847
|
+
const docxDataUrl = await svgMarkupToPngDataUrl(svgMarkup).catch(() => svgDataUrl);
|
|
785
848
|
const assetId = registerAsset(snapshot, {
|
|
786
849
|
dataUrl: svgDataUrl,
|
|
787
850
|
id: createAssetId(prefix),
|
|
@@ -789,7 +852,7 @@ async function resolveDiagramShell(snapshot, shell, { label, prefix, render }) {
|
|
|
789
852
|
mimeType: 'image/svg+xml',
|
|
790
853
|
text: svgMarkup,
|
|
791
854
|
variants: {
|
|
792
|
-
docx:
|
|
855
|
+
docx: docxDataUrl,
|
|
793
856
|
},
|
|
794
857
|
});
|
|
795
858
|
|
|
@@ -798,7 +861,7 @@ async function resolveDiagramShell(snapshot, shell, { label, prefix, render }) {
|
|
|
798
861
|
assetId,
|
|
799
862
|
className: `export-diagram--${prefix}`,
|
|
800
863
|
dataUrl: svgDataUrl,
|
|
801
|
-
docxDataUrl
|
|
864
|
+
docxDataUrl,
|
|
802
865
|
svgMarkup,
|
|
803
866
|
}));
|
|
804
867
|
}
|
|
@@ -811,7 +874,7 @@ async function resolveExcalidrawEmbed(snapshot, placeholder) {
|
|
|
811
874
|
|
|
812
875
|
const svgMarkup = await renderExcalidrawToSvgMarkup(target);
|
|
813
876
|
const svgDataUrl = encodeSvgDataUrl(svgMarkup);
|
|
814
|
-
const
|
|
877
|
+
const docxDataUrl = await svgMarkupToPngDataUrl(svgMarkup).catch(() => svgDataUrl);
|
|
815
878
|
const assetId = registerAsset(snapshot, {
|
|
816
879
|
dataUrl: svgDataUrl,
|
|
817
880
|
id: createAssetId('excalidraw'),
|
|
@@ -819,7 +882,7 @@ async function resolveExcalidrawEmbed(snapshot, placeholder) {
|
|
|
819
882
|
mimeType: 'image/svg+xml',
|
|
820
883
|
text: svgMarkup,
|
|
821
884
|
variants: {
|
|
822
|
-
docx:
|
|
885
|
+
docx: docxDataUrl,
|
|
823
886
|
},
|
|
824
887
|
});
|
|
825
888
|
|
|
@@ -828,7 +891,7 @@ async function resolveExcalidrawEmbed(snapshot, placeholder) {
|
|
|
828
891
|
assetId,
|
|
829
892
|
className: 'export-diagram--excalidraw',
|
|
830
893
|
dataUrl: svgDataUrl,
|
|
831
|
-
docxDataUrl
|
|
894
|
+
docxDataUrl,
|
|
832
895
|
svgMarkup,
|
|
833
896
|
}));
|
|
834
897
|
}
|
|
@@ -860,22 +923,33 @@ async function resolveImageAssets(snapshot, container) {
|
|
|
860
923
|
const imageNodes = Array.from(container.querySelectorAll('img'));
|
|
861
924
|
for (const imageNode of imageNodes) {
|
|
862
925
|
const src = imageNode.getAttribute('src') || '';
|
|
863
|
-
if (!src
|
|
926
|
+
if (!src) {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (src.startsWith('data:')) {
|
|
931
|
+
const docxDataUrl = await createDocxImageDataUrl(src, {
|
|
932
|
+
warnings: snapshot.warnings,
|
|
933
|
+
});
|
|
934
|
+
imageNode.setAttribute('data-export-docx-src', docxDataUrl);
|
|
864
935
|
continue;
|
|
865
936
|
}
|
|
866
937
|
|
|
867
938
|
try {
|
|
868
939
|
const imageUrl = new URL(src, window.location.href);
|
|
869
940
|
const dataUrl = await fetchAsDataUrl(imageUrl.toString());
|
|
941
|
+
const docxDataUrl = await createDocxImageDataUrl(dataUrl, {
|
|
942
|
+
warnings: snapshot.warnings,
|
|
943
|
+
});
|
|
870
944
|
const assetId = registerAsset(snapshot, {
|
|
871
945
|
dataUrl,
|
|
872
946
|
id: createAssetId('image'),
|
|
873
947
|
kind: 'image',
|
|
874
|
-
mimeType: dataUrl
|
|
948
|
+
mimeType: getDataUrlMimeType(dataUrl) || 'application/octet-stream',
|
|
875
949
|
});
|
|
876
950
|
imageNode.src = dataUrl;
|
|
877
951
|
imageNode.setAttribute('data-export-asset-id', assetId);
|
|
878
|
-
imageNode.setAttribute('data-export-docx-src',
|
|
952
|
+
imageNode.setAttribute('data-export-docx-src', docxDataUrl);
|
|
879
953
|
} catch (error) {
|
|
880
954
|
const message = error instanceof Error ? error.message : `Failed to inline ${src}`;
|
|
881
955
|
snapshot.warnings.push(message);
|
|
@@ -5,18 +5,22 @@ import {
|
|
|
5
5
|
resolveAppUrl,
|
|
6
6
|
resolveWsBaseUrl,
|
|
7
7
|
} from '../domain/runtime-paths.js';
|
|
8
|
+
import {
|
|
9
|
+
createFileRouteHash,
|
|
10
|
+
getHashParamsFromRaw,
|
|
11
|
+
HASH_ROUTE_KEYS,
|
|
12
|
+
isCollabMdHashRoute,
|
|
13
|
+
} from '../domain/hash-routes.js';
|
|
8
14
|
|
|
9
15
|
export function getRuntimeConfig() {
|
|
10
16
|
return getClientRuntimeConfig();
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
export { resolveApiUrl, resolveAppPath, resolveAppUrl, resolveWsBaseUrl };
|
|
20
|
+
export { createFileRouteHash, isCollabMdHashRoute };
|
|
14
21
|
|
|
15
22
|
function getHashParams() {
|
|
16
|
-
|
|
17
|
-
? window.location.hash.slice(1)
|
|
18
|
-
: window.location.hash;
|
|
19
|
-
return new URLSearchParams(rawHash);
|
|
23
|
+
return getHashParamsFromRaw(window.location.hash);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
function normalizeDiffScope(scope) {
|
|
@@ -30,8 +34,8 @@ function normalizeDiffScope(scope) {
|
|
|
30
34
|
export function getHashRoute() {
|
|
31
35
|
const params = getHashParams();
|
|
32
36
|
|
|
33
|
-
if (params.has(
|
|
34
|
-
const historicalFilePath = params.get(
|
|
37
|
+
if (params.has(HASH_ROUTE_KEYS.gitFilePreview)) {
|
|
38
|
+
const historicalFilePath = params.get(HASH_ROUTE_KEYS.gitFilePreview) || null;
|
|
35
39
|
return {
|
|
36
40
|
currentFilePath: params.get('current') || historicalFilePath,
|
|
37
41
|
filePath: historicalFilePath,
|
|
@@ -40,28 +44,28 @@ export function getHashRoute() {
|
|
|
40
44
|
};
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
if (params.has(
|
|
47
|
+
if (params.has(HASH_ROUTE_KEYS.gitFileHistory)) {
|
|
44
48
|
return {
|
|
45
|
-
filePath: params.get(
|
|
49
|
+
filePath: params.get(HASH_ROUTE_KEYS.gitFileHistory) || null,
|
|
46
50
|
type: 'git-file-history',
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
if (params.has(
|
|
54
|
+
if (params.has(HASH_ROUTE_KEYS.gitHistory)) {
|
|
51
55
|
return { type: 'git-history' };
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
if (params.has(
|
|
58
|
+
if (params.has(HASH_ROUTE_KEYS.gitCommit)) {
|
|
55
59
|
return {
|
|
56
|
-
hash: params.get(
|
|
60
|
+
hash: params.get(HASH_ROUTE_KEYS.gitCommit) || null,
|
|
57
61
|
historyFilePath: params.get('history') || null,
|
|
58
62
|
path: params.get('path') || null,
|
|
59
63
|
type: 'git-commit',
|
|
60
64
|
};
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
if (params.has(
|
|
64
|
-
const filePath = params.get(
|
|
67
|
+
if (params.has(HASH_ROUTE_KEYS.gitDiff)) {
|
|
68
|
+
const filePath = params.get(HASH_ROUTE_KEYS.gitDiff) || null;
|
|
65
69
|
return {
|
|
66
70
|
filePath,
|
|
67
71
|
scope: normalizeDiffScope(params.get('scope') || (filePath ? 'working-tree' : 'all')),
|
|
@@ -69,10 +73,11 @@ export function getHashRoute() {
|
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
if (params.has(
|
|
76
|
+
if (params.has(HASH_ROUTE_KEYS.file)) {
|
|
73
77
|
return {
|
|
78
|
+
anchor: params.get('anchor') || null,
|
|
74
79
|
drawioMode: params.get('drawio') || null,
|
|
75
|
-
filePath: params.get(
|
|
80
|
+
filePath: params.get(HASH_ROUTE_KEYS.file),
|
|
76
81
|
type: 'file',
|
|
77
82
|
};
|
|
78
83
|
}
|
|
@@ -80,15 +85,8 @@ export function getHashRoute() {
|
|
|
80
85
|
return { type: 'empty' };
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
export function navigateToFile(filePath, { drawioMode = null } = {}) {
|
|
84
|
-
|
|
85
|
-
if (filePath) {
|
|
86
|
-
params.set('file', filePath);
|
|
87
|
-
}
|
|
88
|
-
if (drawioMode) {
|
|
89
|
-
params.set('drawio', drawioMode);
|
|
90
|
-
}
|
|
91
|
-
window.location.hash = params.toString();
|
|
88
|
+
export function navigateToFile(filePath, { drawioMode = null, anchor = null } = {}) {
|
|
89
|
+
window.location.hash = createFileRouteHash(filePath, { drawioMode, anchor });
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
export function navigateToGitDiff({ filePath = null, scope = 'all' } = {}) {
|
|
@@ -109,10 +109,7 @@ export class OutlineController {
|
|
|
109
109
|
this.navigation.querySelectorAll('.outline-item').forEach((button) => {
|
|
110
110
|
button.addEventListener('click', () => {
|
|
111
111
|
const target = document.getElementById(button.dataset.target);
|
|
112
|
-
this.
|
|
113
|
-
this.notifyHeadingNavigation(target, button.dataset.target);
|
|
114
|
-
this.scrollHeadingIntoView(target, { behavior: 'auto' });
|
|
115
|
-
this.setActiveItem(button.dataset.target, { behavior: 'auto' });
|
|
112
|
+
this.navigateToHeading(target, button.dataset.target, { behavior: 'auto' });
|
|
116
113
|
|
|
117
114
|
if (this.mobileBreakpointQuery.matches) {
|
|
118
115
|
this.close();
|
|
@@ -123,7 +120,7 @@ export class OutlineController {
|
|
|
123
120
|
this.observeHeadings(Array.from(headings));
|
|
124
121
|
}
|
|
125
122
|
|
|
126
|
-
cleanup() {
|
|
123
|
+
cleanup({ preservePinnedHeading = false } = {}) {
|
|
127
124
|
if (this.activeHeadingFrame) {
|
|
128
125
|
cancelAnimationFrame(this.activeHeadingFrame);
|
|
129
126
|
this.activeHeadingFrame = null;
|
|
@@ -131,7 +128,9 @@ export class OutlineController {
|
|
|
131
128
|
|
|
132
129
|
this.previewContainer?.removeEventListener('scroll', this.handlePreviewScroll);
|
|
133
130
|
this.headings = [];
|
|
134
|
-
|
|
131
|
+
if (!preservePinnedHeading) {
|
|
132
|
+
this.pinnedHeadingId = null;
|
|
133
|
+
}
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
setActiveItem(id, { scrollIntoView = true, behavior = 'smooth' } = {}) {
|
|
@@ -150,7 +149,7 @@ export class OutlineController {
|
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
observeHeadings(headings) {
|
|
153
|
-
this.cleanup();
|
|
152
|
+
this.cleanup({ preservePinnedHeading: true });
|
|
154
153
|
|
|
155
154
|
if (!this.previewContainer || headings.length === 0) {
|
|
156
155
|
return;
|
|
@@ -158,6 +157,16 @@ export class OutlineController {
|
|
|
158
157
|
|
|
159
158
|
this.headings = headings;
|
|
160
159
|
this.previewContainer.addEventListener('scroll', this.handlePreviewScroll, { passive: true });
|
|
160
|
+
|
|
161
|
+
const pinnedHeading = this.getPinnedHeading();
|
|
162
|
+
if (pinnedHeading) {
|
|
163
|
+
this.setActiveItem(pinnedHeading.id, {
|
|
164
|
+
behavior: 'auto',
|
|
165
|
+
scrollIntoView: true,
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
161
170
|
this.updateActiveHeading();
|
|
162
171
|
}
|
|
163
172
|
|
|
@@ -247,6 +256,18 @@ export class OutlineController {
|
|
|
247
256
|
});
|
|
248
257
|
}
|
|
249
258
|
|
|
259
|
+
navigateToHeading(target, headingId, { behavior = 'auto' } = {}) {
|
|
260
|
+
if (!target || !headingId) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.pinnedHeadingId = headingId;
|
|
265
|
+
this.notifyHeadingNavigation(target, headingId);
|
|
266
|
+
this.scrollHeadingIntoView(target, { behavior });
|
|
267
|
+
this.setActiveItem(headingId, { behavior });
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
250
271
|
scrollHeadingIntoView(target, { behavior = 'smooth' } = {}) {
|
|
251
272
|
if (!target || !this.previewContainer) {
|
|
252
273
|
return;
|
|
@@ -52,6 +52,57 @@
|
|
|
52
52
|
font-weight: 600;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
.preview-content .preview-heading-link-button {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
width: 1.18em;
|
|
58
|
+
min-width: 1.18em;
|
|
59
|
+
height: 1.18em;
|
|
60
|
+
margin-inline-start: 0.28em;
|
|
61
|
+
border: 0;
|
|
62
|
+
border-radius: var(--radius-full);
|
|
63
|
+
background: transparent;
|
|
64
|
+
color: color-mix(in oklab, var(--color-text-muted) 78%, transparent);
|
|
65
|
+
transform: translateY(-0.04em);
|
|
66
|
+
transition:
|
|
67
|
+
transform var(--transition-interactive),
|
|
68
|
+
background var(--transition-interactive),
|
|
69
|
+
box-shadow var(--transition-interactive),
|
|
70
|
+
color var(--transition-interactive),
|
|
71
|
+
opacity var(--transition-interactive);
|
|
72
|
+
vertical-align: middle;
|
|
73
|
+
opacity: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.preview-content .preview-heading-link-button > svg {
|
|
77
|
+
width: 0.74em;
|
|
78
|
+
height: 0.74em;
|
|
79
|
+
stroke-width: 2;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.preview-content .preview-heading-link-button:hover {
|
|
83
|
+
background: color-mix(in oklab, var(--color-primary) 12%, transparent);
|
|
84
|
+
box-shadow: var(--shadow-accent-outline);
|
|
85
|
+
color: var(--color-primary);
|
|
86
|
+
transform: translateY(-0.04em) scale(1.04);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.preview-content h1:hover .preview-heading-link-button,
|
|
90
|
+
.preview-content h2:hover .preview-heading-link-button,
|
|
91
|
+
.preview-content h3:hover .preview-heading-link-button,
|
|
92
|
+
.preview-content h4:hover .preview-heading-link-button,
|
|
93
|
+
.preview-content h5:hover .preview-heading-link-button,
|
|
94
|
+
.preview-content h6:hover .preview-heading-link-button,
|
|
95
|
+
.preview-content .preview-heading-link-button:focus-visible {
|
|
96
|
+
opacity: 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.preview-content .preview-heading-link-button:focus-visible {
|
|
100
|
+
background: color-mix(in oklab, var(--color-primary) 14%, transparent);
|
|
101
|
+
box-shadow: var(--shadow-focus-compact);
|
|
102
|
+
color: var(--color-primary);
|
|
103
|
+
outline: 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
55
106
|
.preview-content p {
|
|
56
107
|
max-width: 72ch;
|
|
57
108
|
margin-bottom: var(--space-4);
|
package/src/server/config/env.js
CHANGED
|
@@ -334,6 +334,7 @@ export function loadConfig(overrides = {}) {
|
|
|
334
334
|
vaultDir,
|
|
335
335
|
publicWsBaseUrl: process.env.PUBLIC_WS_BASE_URL || '',
|
|
336
336
|
testWsRoomHydrateDelayMs: parsePositiveInt(process.env.TEST_WS_ROOM_HYDRATE_DELAY_MS, 0),
|
|
337
|
+
wikiLinkAutoCreate: process.env.COLLABMD_WIKI_LINK_AUTO_CREATE !== 'false',
|
|
337
338
|
wsHeartbeatIntervalMs: parsePositiveInt(process.env.WS_HEARTBEAT_INTERVAL_MS, 30_000),
|
|
338
339
|
wsRoomIdleGraceMs: parsePositiveInt(process.env.WS_ROOM_IDLE_GRACE_MS, 60_000),
|
|
339
340
|
wsBasePath: normalizeWsBasePath(process.env.WS_BASE_PATH || '/ws'),
|
|
@@ -51,6 +51,7 @@ function buildRuntimeConfig({
|
|
|
51
51
|
gitEnabled,
|
|
52
52
|
nodeEnv,
|
|
53
53
|
publicWsBaseUrl,
|
|
54
|
+
wikiLinkAutoCreate,
|
|
54
55
|
wsBasePath,
|
|
55
56
|
}) {
|
|
56
57
|
return `window.__COLLABMD_CONFIG__ = ${JSON.stringify({
|
|
@@ -61,6 +62,7 @@ function buildRuntimeConfig({
|
|
|
61
62
|
environment: nodeEnv,
|
|
62
63
|
gitEnabled,
|
|
63
64
|
publicWsBaseUrl,
|
|
65
|
+
wikiLinkAutoCreate,
|
|
64
66
|
wsBasePath,
|
|
65
67
|
})};\n`;
|
|
66
68
|
}
|