collabmd 0.1.47 → 0.1.48
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 +9 -6
- package/dist/client/assets/JetBrainsMono-Regular-BQaDgvhP.woff2 +0 -0
- package/dist/client/assets/babel-CY5KOv-I.js +16 -0
- package/dist/client/assets/comment-ui-shared-DuoyznXW.js +7 -0
- package/dist/client/assets/dist-ChRoo3IB.js +9 -0
- package/dist/client/assets/document-formatter-Dxl_a8XO.js +2 -0
- package/dist/client/assets/{drawio-editor-BjrxTe3l.js → drawio-editor-BVaNNtYp.js} +2 -2
- package/dist/client/assets/{drawioEditor-DUVZjSSr.js → drawioEditor-BkIBlXx0.js} +2 -2
- package/dist/client/assets/editor-session-DMyVdHLj.js +54 -0
- package/dist/client/assets/embedded-editor-base-B3l6Xy65.css +1 -0
- package/dist/client/assets/estree-CuT_pB8K.js +44 -0
- package/dist/client/assets/{excalidraw-editor-BlcN6umJ.css → excalidraw-editor-BbSbI80a.css} +1 -1
- package/dist/client/assets/excalidraw-editor-BjYaNEMY.js +9 -0
- package/dist/client/assets/{excalidrawEditor-B0jOK3lv.js → excalidrawEditor-ChqF_d82.js} +2 -2
- package/dist/client/assets/export-page-Dyu0XCbu.js +57 -0
- package/dist/client/assets/exportDocument-CJBx4Xjx.css +1 -0
- package/dist/client/assets/{exportDocument-Dg9MjkIk.js → exportDocument-Dgvfa2Sd.js} +2 -2
- package/dist/client/assets/{file-icon-svg-CQAjlWjK.js → file-icon-svg-Y_kpF957.js} +1 -1
- package/dist/client/assets/file-kind-BwMIcZbi.js +1 -0
- package/dist/client/assets/file-search-DDLyyLDU.js +1 -0
- package/dist/client/assets/html-Cdnr2qzU.js +25 -0
- package/dist/client/assets/index-CSNIMyOP.css +1 -0
- package/dist/client/assets/index-DG3N2Ps-.js +2 -0
- package/dist/client/assets/main-D0wCxpVn.js +903 -0
- package/dist/client/assets/markdown-CByV-4CU.js +70 -0
- package/dist/client/assets/mermaid.core-BJRLUQZE.js +11 -0
- package/dist/client/assets/{preview-render-compiler-BApvlLLo.js → preview-render-compiler-CrJIooVD.js} +2 -2
- package/dist/client/assets/preview-render-compiler-O0JxBVNF.js +1 -0
- package/dist/client/assets/{preview-render-profile-CE-5rVTC.js → preview-render-profile-B4RWYWHt.js} +1 -1
- package/dist/client/assets/{preview-render-worker-BmN79bVc.js → preview-render-worker-_QTxqKvP.js} +22 -22
- package/dist/client/assets/quick-switcher-controller-CaEjEtq_.js +1 -0
- package/dist/client/assets/standalone-6z5x8CWe.js +29 -0
- package/dist/client/assets/{vault-api-client-_LpV1aIn.js → vault-api-client-BkwdFjx0.js} +2 -2
- package/dist/client/assets/{es-CAsWR6hG.js → vault-utils-DvoSftJT.js} +1 -1
- package/dist/client/assets/yaml-9ltHPDNO.js +154 -0
- 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 +41 -19
- package/package.json +4 -1
- package/src/client/app/index.html +39 -17
- package/src/client/application/app-shell/export-feature.js +17 -1
- package/src/client/application/app-shell/git-feature.js +28 -9
- package/src/client/application/app-shell/ui-feature-identity.js +13 -0
- package/src/client/application/app-shell/ui-feature-shell.js +164 -9
- package/src/client/application/app-shell-elements.js +4 -0
- package/src/client/application/workspace-coordinator.js +21 -4
- package/src/client/application/workspace-preview-controller.js +120 -2
- package/src/client/application/workspace-route-controller.js +4 -1
- package/src/client/assets/vendor/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
- package/src/client/assets/vendor/jetbrains-mono/OFL.txt +93 -0
- package/src/client/bootstrap/collabmd-app-shell.js +21 -5
- package/src/client/domain/document-formatter.js +193 -0
- package/src/client/domain/file-search.js +73 -0
- package/src/client/domain/git-labels.js +5 -0
- package/src/client/domain/vault-paths.js +6 -4
- package/src/client/domain/wiki-link-completions.js +20 -20
- package/src/client/drawio-editor.js +8 -3
- package/src/client/excalidraw-editor.js +152 -6
- package/src/client/export/export-host.js +56 -35
- package/src/client/export/export-page.js +14 -4
- package/src/client/export/export-pipeline.js +266 -25
- package/src/client/infrastructure/editor-session.js +4 -0
- package/src/client/infrastructure/editor-view-adapter.js +60 -14
- package/src/client/presentation/bases-preview-controller.js +35 -11
- package/src/client/presentation/file-action-controller.js +15 -2
- package/src/client/presentation/file-explorer-controller.js +3 -0
- package/src/client/presentation/file-explorer-view.js +2 -1
- package/src/client/presentation/file-history-view-controller.js +6 -1
- package/src/client/presentation/file-tree-state.js +1 -11
- package/src/client/presentation/git-diff-view-controller.js +36 -11
- package/src/client/presentation/git-panel-controller.js +10 -6
- package/src/client/presentation/quick-switcher-controller.js +54 -154
- package/src/client/styles/components/scrollbars.css +10 -0
- package/src/client/styles/export-document.css +44 -29
- package/src/client/styles/features/collaboration-presence.css +13 -1
- package/src/client/styles/features/preview-markdown.css +54 -0
- package/src/client/styles/foundation/tokens.css +8 -0
- package/src/client/styles/layout/shell.css +76 -24
- package/src/client/styles/layout/view-modes.css +2 -0
- package/src/client/styles/overrides/codemirror.css +31 -19
- package/src/client/styles/surfaces/excalidraw-editor.css +42 -0
- package/src/domain/file-kind.js +12 -1
- package/src/server/domain/ripgrep-search-service.js +110 -37
- package/src/server/infrastructure/persistence/path-utils.js +10 -4
- package/src/server/infrastructure/persistence/vault-file-store.js +2 -0
- package/dist/client/assets/comment-ui-shared-DWkk9Vh3.js +0 -7
- package/dist/client/assets/editor-session-DfZuRgB_.js +0 -54
- package/dist/client/assets/embedded-editor-base-DvMynhKu.css +0 -1
- package/dist/client/assets/excalidraw-editor-D7pnUCLH.js +0 -9
- package/dist/client/assets/export-page-Bs3xE1fg.js +0 -57
- package/dist/client/assets/exportDocument-PnYU3v1s.css +0 -1
- package/dist/client/assets/file-kind-BbqgGzpc.js +0 -1
- package/dist/client/assets/index-BIpgxyGZ.css +0 -1
- package/dist/client/assets/index-Z5sI0Bj-.js +0 -2
- package/dist/client/assets/main-Dwnj9Gaq.js +0 -903
- package/dist/client/assets/mermaid.core-CLZBlpIW.js +0 -11
- package/dist/client/assets/preview-render-compiler-BQ_3h9vD.js +0 -1
- package/dist/client/assets/quick-switcher-controller-ButWPmCI.js +0 -12
- package/dist/client/assets/vault-utils-wdQ0RNR9.js +0 -1
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
sanitizeSvgMarkup,
|
|
15
15
|
} from '../application/preview-diagram-utils.js';
|
|
16
16
|
import { compilePreviewDocument } from '../application/preview-render-compiler.js';
|
|
17
|
-
import { stripVaultFileExtension } from '../../domain/file-kind.js';
|
|
17
|
+
import { isMarkdownFilePath, stripVaultFileExtension } from '../../domain/file-kind.js';
|
|
18
|
+
import { resolveVaultRelativePath } from '../../domain/vault-paths.js';
|
|
19
|
+
import { resolveWikiTargetPath } from '../../domain/wiki-link-resolver.js';
|
|
18
20
|
import { parseSceneJson, sceneToInitialData } from '../domain/excalidraw-scene.js';
|
|
19
21
|
import { escapeHtml } from '../domain/vault-utils.js';
|
|
20
22
|
import { downloadBlob } from '../browser-utils.js';
|
|
@@ -22,7 +24,7 @@ import { resolveApiUrl, resolveAppUrl } from '../infrastructure/runtime-config.j
|
|
|
22
24
|
|
|
23
25
|
const EXPORT_PAGE_SOURCE = 'collabmd-export-page';
|
|
24
26
|
const EXPORT_HOST_SOURCE = 'collabmd-export-host';
|
|
25
|
-
const
|
|
27
|
+
const EXPORT_MERMAID_CONFIG = Object.freeze({
|
|
26
28
|
htmlLabels: false,
|
|
27
29
|
flowchart: {
|
|
28
30
|
defaultRenderer: 'dagre-wrapper',
|
|
@@ -33,7 +35,6 @@ const LIGHT_EXPORT_MERMAID_THEME = Object.freeze({
|
|
|
33
35
|
useMaxWidth: true,
|
|
34
36
|
},
|
|
35
37
|
startOnLoad: false,
|
|
36
|
-
theme: 'default',
|
|
37
38
|
});
|
|
38
39
|
const EXPORT_ASSET_FETCH_TIMEOUT_MS = 10_000;
|
|
39
40
|
const EXPORT_ASSET_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -77,6 +78,10 @@ function createAssetId(prefix = 'asset') {
|
|
|
77
78
|
return `${prefix}-${assetCounter.toString(36)}`;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function normalizeExportTheme(theme) {
|
|
82
|
+
return theme === 'dark' ? 'dark' : 'light';
|
|
83
|
+
}
|
|
84
|
+
|
|
80
85
|
function createDocumentTitle(filePath, title = '') {
|
|
81
86
|
const normalizedTitle = String(title ?? '').trim();
|
|
82
87
|
if (normalizedTitle) {
|
|
@@ -87,6 +92,65 @@ function createDocumentTitle(filePath, title = '') {
|
|
|
87
92
|
return stripVaultFileExtension(leaf) || 'document';
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
function createDocumentAnchorId(filePath) {
|
|
96
|
+
return `export-doc-${encodeURIComponent(String(filePath ?? '').toLowerCase()).replace(/%/gu, '-').replace(/\./gu, '-')}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createExportHeadingSlug(value) {
|
|
100
|
+
return String(value ?? '').trim().toLowerCase().replace(/[^a-z0-9]+/gu, '-').replace(/^-|-$/gu, '');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function rewriteDirectoryDocumentLinks(container, { fileList, sourceFilePath }) {
|
|
104
|
+
const exportedPaths = new Set(fileList);
|
|
105
|
+
const sourceAnchorId = createDocumentAnchorId(sourceFilePath);
|
|
106
|
+
|
|
107
|
+
Array.from(container.querySelectorAll('[id]')).forEach((element) => {
|
|
108
|
+
element.id = `${sourceAnchorId}-${element.id}`;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
Array.from(container.querySelectorAll('a[href]')).forEach((link) => {
|
|
112
|
+
const rawHref = String(link.getAttribute('href') || '').trim();
|
|
113
|
+
const wikiTarget = String(link.getAttribute('data-wiki-target') || '').trim();
|
|
114
|
+
if (wikiTarget) {
|
|
115
|
+
const [targetPath, targetHeading = ''] = wikiTarget.split('#', 2);
|
|
116
|
+
const resolved = targetPath ? resolveWikiTargetPath(targetPath, fileList) : sourceFilePath;
|
|
117
|
+
if (resolved && exportedPaths.has(resolved)) {
|
|
118
|
+
const headingSlug = createExportHeadingSlug(targetHeading);
|
|
119
|
+
const targetAnchorId = createDocumentAnchorId(resolved);
|
|
120
|
+
link.setAttribute('href', headingSlug ? `#${targetAnchorId}-${headingSlug}` : `#${targetAnchorId}`);
|
|
121
|
+
link.removeAttribute('target');
|
|
122
|
+
link.removeAttribute('rel');
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!rawHref || /^(?:[a-z][a-z\d+.-]*:|\/\/)/iu.test(rawHref)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (rawHref.startsWith('#')) {
|
|
132
|
+
link.setAttribute('href', `#${sourceAnchorId}-${rawHref.slice(1)}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const [pathPart, fragment = ''] = rawHref.split('#', 2);
|
|
137
|
+
const resolved = resolveVaultRelativePath(sourceFilePath, pathPart);
|
|
138
|
+
if (!exportedPaths.has(resolved)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const targetAnchorId = createDocumentAnchorId(resolved);
|
|
143
|
+
link.setAttribute('href', fragment ? `#${targetAnchorId}-${fragment}` : `#${targetAnchorId}`);
|
|
144
|
+
link.removeAttribute('target');
|
|
145
|
+
link.removeAttribute('rel');
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function replaceChildrenFromHtml(target, html) {
|
|
150
|
+
const parsed = new DOMParser().parseFromString(`<body>${String(html ?? '')}</body>`, 'text/html');
|
|
151
|
+
target.replaceChildren(...parsed.body.childNodes);
|
|
152
|
+
}
|
|
153
|
+
|
|
90
154
|
function encodeSvgBase64DataUrl(svgMarkup) {
|
|
91
155
|
const bytes = new TextEncoder().encode(String(svgMarkup ?? ''));
|
|
92
156
|
const chunkSize = 0x8000;
|
|
@@ -117,7 +181,7 @@ function trimSvgCanvas(svgMarkup, { padding = 16 } = {}) {
|
|
|
117
181
|
probe.style.top = '0';
|
|
118
182
|
probe.style.visibility = 'hidden';
|
|
119
183
|
probe.style.pointerEvents = 'none';
|
|
120
|
-
probe
|
|
184
|
+
replaceChildrenFromHtml(probe, svgMarkup);
|
|
121
185
|
document.body.appendChild(probe);
|
|
122
186
|
|
|
123
187
|
try {
|
|
@@ -167,7 +231,7 @@ async function svgMarkupToPngDataUrl(svgMarkup) {
|
|
|
167
231
|
type: 'image/svg+xml;charset=utf-8',
|
|
168
232
|
}));
|
|
169
233
|
const svgProbe = document.createElement('div');
|
|
170
|
-
svgProbe
|
|
234
|
+
replaceChildrenFromHtml(svgProbe, svgMarkup);
|
|
171
235
|
const svgElement = svgProbe.querySelector('svg');
|
|
172
236
|
|
|
173
237
|
try {
|
|
@@ -638,7 +702,7 @@ function createDiagramFigureElement({
|
|
|
638
702
|
if (svgMarkup) {
|
|
639
703
|
const surface = document.createElement('div');
|
|
640
704
|
surface.className = 'export-diagram-surface';
|
|
641
|
-
surface
|
|
705
|
+
replaceChildrenFromHtml(surface, svgMarkup);
|
|
642
706
|
|
|
643
707
|
const svgElement = surface.querySelector('svg');
|
|
644
708
|
if (svgElement) {
|
|
@@ -678,7 +742,7 @@ async function ensureMermaidRuntime() {
|
|
|
678
742
|
throw new Error('Mermaid runtime failed to initialize');
|
|
679
743
|
}
|
|
680
744
|
|
|
681
|
-
runtime.initialize(
|
|
745
|
+
runtime.initialize({ ...EXPORT_MERMAID_CONFIG, theme: 'default' });
|
|
682
746
|
return runtime;
|
|
683
747
|
});
|
|
684
748
|
|
|
@@ -708,7 +772,10 @@ function prepareMermaidSource(source) {
|
|
|
708
772
|
|
|
709
773
|
async function renderMermaidToSvgMarkup(source) {
|
|
710
774
|
const mermaid = await ensureMermaidRuntime();
|
|
711
|
-
mermaid.initialize(
|
|
775
|
+
mermaid.initialize({
|
|
776
|
+
...EXPORT_MERMAID_CONFIG,
|
|
777
|
+
theme: document.documentElement.dataset.theme === 'dark' ? 'dark' : 'default',
|
|
778
|
+
});
|
|
712
779
|
const renderHost = document.createElement('div');
|
|
713
780
|
renderHost.style.position = 'fixed';
|
|
714
781
|
renderHost.style.left = '-10000px';
|
|
@@ -861,6 +928,97 @@ async function resolveDiagramShell(snapshot, shell, { label, prefix, render }) {
|
|
|
861
928
|
}));
|
|
862
929
|
}
|
|
863
930
|
|
|
931
|
+
function getBaseCellText(cell) {
|
|
932
|
+
if (cell?.text != null) {
|
|
933
|
+
return String(cell.text);
|
|
934
|
+
}
|
|
935
|
+
if (Array.isArray(cell?.value)) {
|
|
936
|
+
return cell.value.join(', ');
|
|
937
|
+
}
|
|
938
|
+
return cell?.value == null ? '' : String(cell.value);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
async function resolveBaseEmbed(snapshot, placeholder) {
|
|
942
|
+
const response = await fetchJson(resolveApiUrl('/base/query'), {
|
|
943
|
+
body: JSON.stringify({
|
|
944
|
+
activeFilePath: snapshot.filePath,
|
|
945
|
+
path: placeholder.dataset.basePath || '',
|
|
946
|
+
search: '',
|
|
947
|
+
source: placeholder.dataset.baseSource || null,
|
|
948
|
+
sourcePath: placeholder.dataset.baseSourcePath || snapshot.filePath,
|
|
949
|
+
view: placeholder.dataset.baseView || '',
|
|
950
|
+
}),
|
|
951
|
+
headers: { 'Content-Type': 'application/json' },
|
|
952
|
+
method: 'POST',
|
|
953
|
+
});
|
|
954
|
+
const result = response.result;
|
|
955
|
+
if (!result || !Array.isArray(result.columns) || !Array.isArray(result.groups)) {
|
|
956
|
+
throw new Error('Base query returned no rendered view');
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// ponytail: static tables preserve base data; share card/list renderers if those export layouts become necessary.
|
|
960
|
+
if (result.view?.type && result.view.type !== 'table') {
|
|
961
|
+
snapshot.warnings.push(`Base ${result.view.type} view exported as a static table`);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const shell = document.createElement('section');
|
|
965
|
+
shell.className = 'bases-shell bases-shell-static';
|
|
966
|
+
const toolbar = document.createElement('header');
|
|
967
|
+
toolbar.className = 'bases-toolbar';
|
|
968
|
+
const toolbarMain = document.createElement('div');
|
|
969
|
+
toolbarMain.className = 'bases-toolbar-main';
|
|
970
|
+
const title = document.createElement('strong');
|
|
971
|
+
title.textContent = result.view?.name || 'Base view';
|
|
972
|
+
const meta = document.createElement('div');
|
|
973
|
+
meta.className = 'bases-meta';
|
|
974
|
+
meta.textContent = `${result.totalRows ?? 0} results`;
|
|
975
|
+
toolbarMain.append(title, meta);
|
|
976
|
+
toolbar.appendChild(toolbarMain);
|
|
977
|
+
shell.appendChild(toolbar);
|
|
978
|
+
|
|
979
|
+
const content = document.createElement('div');
|
|
980
|
+
content.className = 'bases-content';
|
|
981
|
+
result.groups.forEach((group) => {
|
|
982
|
+
if (result.groups.length > 1) {
|
|
983
|
+
const heading = document.createElement('h3');
|
|
984
|
+
heading.textContent = group.label || group.key || 'Group';
|
|
985
|
+
content.appendChild(heading);
|
|
986
|
+
}
|
|
987
|
+
const tableShell = document.createElement('div');
|
|
988
|
+
tableShell.className = 'bases-table-shell';
|
|
989
|
+
const table = document.createElement('table');
|
|
990
|
+
table.className = 'bases-table';
|
|
991
|
+
const headRow = table.createTHead().insertRow();
|
|
992
|
+
result.columns.forEach((column) => {
|
|
993
|
+
const heading = document.createElement('th');
|
|
994
|
+
heading.scope = 'col';
|
|
995
|
+
heading.textContent = column.label || column.id || '';
|
|
996
|
+
headRow.appendChild(heading);
|
|
997
|
+
});
|
|
998
|
+
const body = table.createTBody();
|
|
999
|
+
(group.rows || []).forEach((row) => {
|
|
1000
|
+
const tableRow = body.insertRow();
|
|
1001
|
+
result.columns.forEach((column, columnIndex) => {
|
|
1002
|
+
const cell = tableRow.insertCell();
|
|
1003
|
+
const text = getBaseCellText(row.cells?.[column.id]);
|
|
1004
|
+
if (columnIndex === 0 && row.path) {
|
|
1005
|
+
const link = document.createElement('a');
|
|
1006
|
+
link.href = buildWikiLinkHref(row.path);
|
|
1007
|
+
link.dataset.wikiTarget = row.path;
|
|
1008
|
+
link.textContent = text;
|
|
1009
|
+
cell.appendChild(link);
|
|
1010
|
+
} else {
|
|
1011
|
+
cell.textContent = text;
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
tableShell.appendChild(table);
|
|
1016
|
+
content.appendChild(tableShell);
|
|
1017
|
+
});
|
|
1018
|
+
shell.appendChild(content);
|
|
1019
|
+
placeholder.replaceWith(shell);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
864
1022
|
async function resolveExcalidrawEmbed(snapshot, placeholder) {
|
|
865
1023
|
const target = String(placeholder.dataset.embedTarget ?? '').trim();
|
|
866
1024
|
if (!target) {
|
|
@@ -1026,6 +1184,20 @@ export async function resolveExportAssets(snapshot, {
|
|
|
1026
1184
|
}
|
|
1027
1185
|
}
|
|
1028
1186
|
|
|
1187
|
+
const baseEmbeds = Array.from(container.querySelectorAll('.bases-embed-placeholder[data-base-key]'));
|
|
1188
|
+
for (const placeholder of baseEmbeds) {
|
|
1189
|
+
try {
|
|
1190
|
+
await resolveBaseEmbed(snapshot, placeholder);
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1193
|
+
snapshot.warnings.push(`Base export failed: ${message}`);
|
|
1194
|
+
placeholder.replaceWith(Object.assign(document.createElement('p'), {
|
|
1195
|
+
className: 'export-warning',
|
|
1196
|
+
textContent: `Base export failed: ${message}`,
|
|
1197
|
+
}));
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1029
1201
|
const excalidrawEmbeds = Array.from(container.querySelectorAll('.excalidraw-embed-placeholder[data-embed-key]'));
|
|
1030
1202
|
for (const placeholder of excalidrawEmbeds) {
|
|
1031
1203
|
try {
|
|
@@ -1057,9 +1229,11 @@ export async function resolveExportAssets(snapshot, {
|
|
|
1057
1229
|
}
|
|
1058
1230
|
|
|
1059
1231
|
export async function prepareExportSnapshot({
|
|
1232
|
+
directoryFileList = [],
|
|
1060
1233
|
fileList = [],
|
|
1061
1234
|
filePath,
|
|
1062
1235
|
markdownText = '',
|
|
1236
|
+
theme = 'light',
|
|
1063
1237
|
title = '',
|
|
1064
1238
|
} = {}) {
|
|
1065
1239
|
const normalizedFilePath = String(filePath ?? '').trim();
|
|
@@ -1070,6 +1244,7 @@ export async function prepareExportSnapshot({
|
|
|
1070
1244
|
generatedAt: new Date().toISOString(),
|
|
1071
1245
|
html: '',
|
|
1072
1246
|
sourceMarkdown: normalizedMarkdown,
|
|
1247
|
+
theme: normalizeExportTheme(theme),
|
|
1073
1248
|
title: createDocumentTitle(normalizedFilePath, title),
|
|
1074
1249
|
warnings: [],
|
|
1075
1250
|
};
|
|
@@ -1082,13 +1257,78 @@ export async function prepareExportSnapshot({
|
|
|
1082
1257
|
});
|
|
1083
1258
|
const container = document.createElement('div');
|
|
1084
1259
|
container.className = 'preview-content export-content';
|
|
1085
|
-
container
|
|
1260
|
+
replaceChildrenFromHtml(container, compiled.html);
|
|
1086
1261
|
|
|
1087
1262
|
await resolveExportAssets(snapshot, { container });
|
|
1263
|
+
if (directoryFileList.length > 0) {
|
|
1264
|
+
rewriteDirectoryDocumentLinks(container, {
|
|
1265
|
+
fileList: directoryFileList,
|
|
1266
|
+
sourceFilePath: normalizedFilePath,
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1088
1269
|
snapshot.html = container.innerHTML;
|
|
1089
1270
|
return snapshot;
|
|
1090
1271
|
}
|
|
1091
1272
|
|
|
1273
|
+
export async function prepareDirectoryExportSnapshot({
|
|
1274
|
+
currentFilePath = '',
|
|
1275
|
+
currentMarkdownText = '',
|
|
1276
|
+
directoryPath,
|
|
1277
|
+
fileList = [],
|
|
1278
|
+
theme = 'light',
|
|
1279
|
+
} = {}) {
|
|
1280
|
+
const normalizedDirectoryPath = String(directoryPath ?? '').replace(/\\/gu, '/').replace(/^\/+|\/+$/gu, '').trim();
|
|
1281
|
+
const directoryPrefix = `${normalizedDirectoryPath}/`;
|
|
1282
|
+
const markdownPaths = fileList
|
|
1283
|
+
.filter((filePath) => isMarkdownFilePath(filePath) && String(filePath).startsWith(directoryPrefix))
|
|
1284
|
+
.sort((left, right) => left.localeCompare(right, undefined, { sensitivity: 'base' }));
|
|
1285
|
+
if (markdownPaths.length === 0) {
|
|
1286
|
+
throw new Error('The selected folder contains no markdown notes');
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const documents = [];
|
|
1290
|
+
const warnings = [];
|
|
1291
|
+
for (const filePath of markdownPaths) {
|
|
1292
|
+
const markdownText = filePath === currentFilePath
|
|
1293
|
+
? String(currentMarkdownText ?? '')
|
|
1294
|
+
: await fetchTextFile(filePath);
|
|
1295
|
+
const documentSnapshot = await prepareExportSnapshot({
|
|
1296
|
+
directoryFileList: markdownPaths,
|
|
1297
|
+
fileList,
|
|
1298
|
+
filePath,
|
|
1299
|
+
markdownText,
|
|
1300
|
+
theme,
|
|
1301
|
+
});
|
|
1302
|
+
warnings.push(...documentSnapshot.warnings.map((warning) => `${filePath}: ${warning}`));
|
|
1303
|
+
documents.push(documentSnapshot);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const title = createDocumentTitle(normalizedDirectoryPath);
|
|
1307
|
+
const tableOfContents = documents.map((documentSnapshot) => (
|
|
1308
|
+
`<li><a href="#${createDocumentAnchorId(documentSnapshot.filePath)}">${escapeHtml(documentSnapshot.filePath)}</a></li>`
|
|
1309
|
+
)).join('');
|
|
1310
|
+
const html = [
|
|
1311
|
+
`<nav class="export-directory-toc" aria-label="Exported notes"><h1>${escapeHtml(title)}</h1><ol>${tableOfContents}</ol></nav>`,
|
|
1312
|
+
...documents.map((documentSnapshot) => [
|
|
1313
|
+
`<section class="export-document-section" id="${createDocumentAnchorId(documentSnapshot.filePath)}">`,
|
|
1314
|
+
`<header class="export-document-heading"><h1>${escapeHtml(documentSnapshot.title)}</h1><p>${escapeHtml(documentSnapshot.filePath)}</p></header>`,
|
|
1315
|
+
documentSnapshot.html,
|
|
1316
|
+
'</section>',
|
|
1317
|
+
].join('')),
|
|
1318
|
+
].join('');
|
|
1319
|
+
|
|
1320
|
+
return {
|
|
1321
|
+
assets: {},
|
|
1322
|
+
filePath: normalizedDirectoryPath,
|
|
1323
|
+
generatedAt: new Date().toISOString(),
|
|
1324
|
+
html,
|
|
1325
|
+
sourceMarkdown: '',
|
|
1326
|
+
theme: normalizeExportTheme(theme),
|
|
1327
|
+
title,
|
|
1328
|
+
warnings,
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1092
1332
|
function applyDocxTableStyles(table, {
|
|
1093
1333
|
borderColor = '#d1d5db',
|
|
1094
1334
|
cellPadding = '8px 10px',
|
|
@@ -1224,12 +1464,7 @@ function createDocxCodeBlockTable(pre) {
|
|
|
1224
1464
|
paragraph.style.color = '#111827';
|
|
1225
1465
|
|
|
1226
1466
|
const text = (line || '').replace(/\t/g, ' ');
|
|
1227
|
-
|
|
1228
|
-
paragraph.innerHTML = ' ';
|
|
1229
|
-
} else {
|
|
1230
|
-
paragraph.textContent = text;
|
|
1231
|
-
paragraph.innerHTML = paragraph.innerHTML.replace(/ /g, ' ');
|
|
1232
|
-
}
|
|
1467
|
+
paragraph.textContent = text ? text.replace(/ /gu, '\u00a0') : '\u00a0';
|
|
1233
1468
|
|
|
1234
1469
|
contentNodes.push(paragraph);
|
|
1235
1470
|
});
|
|
@@ -1244,7 +1479,7 @@ function createDocxCodeBlockTable(pre) {
|
|
|
1244
1479
|
|
|
1245
1480
|
export function buildDocxHtmlDocument(snapshot) {
|
|
1246
1481
|
const template = document.createElement('template');
|
|
1247
|
-
template.
|
|
1482
|
+
replaceChildrenFromHtml(template.content, snapshot.html);
|
|
1248
1483
|
|
|
1249
1484
|
Array.from(template.content.querySelectorAll('.table-wrapper')).forEach((wrapper) => {
|
|
1250
1485
|
const table = wrapper.querySelector(':scope > table');
|
|
@@ -1411,20 +1646,21 @@ export function buildDocxHtmlDocument(snapshot) {
|
|
|
1411
1646
|
|
|
1412
1647
|
export function buildHtmlDocument(snapshot) {
|
|
1413
1648
|
const template = document.createElement('template');
|
|
1414
|
-
template.
|
|
1649
|
+
replaceChildrenFromHtml(template.content, snapshot.html);
|
|
1415
1650
|
Array.from(template.content.querySelectorAll('[data-export-docx-src]')).forEach((element) => {
|
|
1416
1651
|
element.removeAttribute('data-export-docx-src');
|
|
1417
1652
|
});
|
|
1418
1653
|
|
|
1654
|
+
const theme = normalizeExportTheme(snapshot.theme);
|
|
1419
1655
|
return `<!DOCTYPE html>
|
|
1420
|
-
<html lang="en" data-theme="
|
|
1656
|
+
<html lang="en" data-theme="${theme}">
|
|
1421
1657
|
<head>
|
|
1422
1658
|
<meta charset="UTF-8">
|
|
1423
1659
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1424
1660
|
<title>${escapeHtml(snapshot.title)}</title>
|
|
1425
1661
|
<style>${HTML_EXPORT_STYLES.replace(/<\/style/giu, '<\\/style')}</style>
|
|
1426
1662
|
</head>
|
|
1427
|
-
<body data-theme="
|
|
1663
|
+
<body data-theme="${theme}">
|
|
1428
1664
|
<main class="export-page-shell">
|
|
1429
1665
|
<article class="preview-content export-content">${template.innerHTML}</article>
|
|
1430
1666
|
</main>
|
|
@@ -1535,11 +1771,11 @@ export function postExportPageMessage(type, payload = {}) {
|
|
|
1535
1771
|
}
|
|
1536
1772
|
|
|
1537
1773
|
export async function waitForBootstrapPayload() {
|
|
1538
|
-
const
|
|
1539
|
-
const requestedAction =
|
|
1774
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
1775
|
+
const requestedAction = searchParams.get('action');
|
|
1540
1776
|
const action = ['html', 'pdf'].includes(requestedAction) ? requestedAction : 'docx';
|
|
1541
|
-
const filePath =
|
|
1542
|
-
const jobId =
|
|
1777
|
+
const filePath = searchParams.get('file') || '';
|
|
1778
|
+
const jobId = searchParams.get('job') || window.name || '';
|
|
1543
1779
|
|
|
1544
1780
|
if (jobId && window.opener && !window.opener.closed) {
|
|
1545
1781
|
postExportPageMessage('ready', { jobId });
|
|
@@ -1567,11 +1803,15 @@ export async function waitForBootstrapPayload() {
|
|
|
1567
1803
|
|
|
1568
1804
|
cleanup();
|
|
1569
1805
|
resolve({
|
|
1570
|
-
action,
|
|
1806
|
+
action: ['html', 'pdf'].includes(payload.action) ? payload.action : action,
|
|
1807
|
+
currentFilePath: payload.currentFilePath || '',
|
|
1808
|
+
currentMarkdownText: payload.currentMarkdownText || '',
|
|
1809
|
+
directoryPath: payload.directoryPath || '',
|
|
1571
1810
|
fileList: Array.isArray(payload.fileList) ? payload.fileList : [],
|
|
1572
1811
|
filePath: payload.filePath || filePath,
|
|
1573
|
-
title: payload.title || '',
|
|
1574
1812
|
markdownText: payload.markdownText || '',
|
|
1813
|
+
theme: normalizeExportTheme(payload.theme),
|
|
1814
|
+
title: payload.title || '',
|
|
1575
1815
|
});
|
|
1576
1816
|
};
|
|
1577
1817
|
|
|
@@ -1584,6 +1824,7 @@ export async function waitForBootstrapPayload() {
|
|
|
1584
1824
|
fileList: [],
|
|
1585
1825
|
filePath,
|
|
1586
1826
|
markdownText: filePath ? await fetchTextFile(filePath) : '',
|
|
1827
|
+
theme: 'light',
|
|
1587
1828
|
title: '',
|
|
1588
1829
|
};
|
|
1589
1830
|
}
|
|
@@ -274,6 +274,10 @@ export class EditorSession {
|
|
|
274
274
|
this.viewAdapter.requestMeasure();
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
formatDocument(filePath) {
|
|
278
|
+
return this.viewAdapter.formatDocument(filePath);
|
|
279
|
+
}
|
|
280
|
+
|
|
277
281
|
runEditorCommand(commandId) {
|
|
278
282
|
return this.viewAdapter.runEditorCommand(commandId);
|
|
279
283
|
}
|
|
@@ -2,14 +2,11 @@ import { vim } from '@replit/codemirror-vim';
|
|
|
2
2
|
import { autocompletion, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
|
3
3
|
import {
|
|
4
4
|
defaultKeymap,
|
|
5
|
-
history,
|
|
6
|
-
historyKeymap,
|
|
7
5
|
indentLess,
|
|
8
6
|
indentMore,
|
|
9
7
|
indentWithTab,
|
|
10
|
-
redo,
|
|
11
|
-
undo,
|
|
12
8
|
} from '@codemirror/commands';
|
|
9
|
+
import { html } from '@codemirror/lang-html';
|
|
13
10
|
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
|
14
11
|
import { yaml } from '@codemirror/lang-yaml';
|
|
15
12
|
import {
|
|
@@ -44,15 +41,17 @@ import {
|
|
|
44
41
|
lineNumbers,
|
|
45
42
|
rectangularSelection,
|
|
46
43
|
} from '@codemirror/view';
|
|
47
|
-
import { yCollab } from 'y-codemirror.next';
|
|
44
|
+
import { yCollab, yUndoManagerKeymap } from 'y-codemirror.next';
|
|
48
45
|
|
|
49
46
|
import {
|
|
50
47
|
isBaseFilePath,
|
|
48
|
+
isHtmlFilePath,
|
|
51
49
|
isMermaidFilePath,
|
|
52
50
|
isPlantUmlFilePath,
|
|
53
51
|
isStructurizrFilePath,
|
|
54
52
|
} from '../../domain/file-kind.js';
|
|
55
53
|
import { normalizeCommentQuote } from '../../domain/comment-threads.js';
|
|
54
|
+
import { formatDocumentText } from '../domain/document-formatter.js';
|
|
56
55
|
import { createMarkdownToolbarEdit } from '../domain/markdown-formatting.js';
|
|
57
56
|
import { mermaidLanguage, mermaidLanguageDescription } from '../domain/mermaid-language.js';
|
|
58
57
|
import { wikiLinkCompletions } from '../domain/wiki-link-completions.js';
|
|
@@ -149,8 +148,6 @@ const EDITOR_COMMANDS = Object.freeze({
|
|
|
149
148
|
indentLess,
|
|
150
149
|
indentMore,
|
|
151
150
|
openSearch: openSearchPanelPreservingViewport,
|
|
152
|
-
redo,
|
|
153
|
-
undo,
|
|
154
151
|
});
|
|
155
152
|
|
|
156
153
|
class RemoteUpdateCaretWidget extends WidgetType {
|
|
@@ -391,6 +388,10 @@ function createEditorTheme(theme) {
|
|
|
391
388
|
}
|
|
392
389
|
|
|
393
390
|
export function createLanguageExtension(filePath) {
|
|
391
|
+
if (isHtmlFilePath(filePath)) {
|
|
392
|
+
return html();
|
|
393
|
+
}
|
|
394
|
+
|
|
394
395
|
if (isMermaidFilePath(filePath)) {
|
|
395
396
|
return mermaidLanguage;
|
|
396
397
|
}
|
|
@@ -441,6 +442,7 @@ export class EditorViewAdapter {
|
|
|
441
442
|
this.onSelectionChanged = onSelectionChanged;
|
|
442
443
|
this.onViewportChanged = onViewportChanged;
|
|
443
444
|
this.editorView = null;
|
|
445
|
+
this.undoManager = null;
|
|
444
446
|
this.themeCompartment = new Compartment();
|
|
445
447
|
this.syntaxThemeCompartment = new Compartment();
|
|
446
448
|
this.lineWrappingCompartment = new Compartment();
|
|
@@ -477,11 +479,11 @@ export class EditorViewAdapter {
|
|
|
477
479
|
}
|
|
478
480
|
|
|
479
481
|
getBaseExtensions(filePath, { readOnly = false } = {}) {
|
|
482
|
+
const wikiLinkCompletionSource = wikiLinkCompletions(this.getFileList);
|
|
480
483
|
const extensions = [
|
|
481
484
|
lineNumbers(),
|
|
482
485
|
highlightActiveLineGutter(),
|
|
483
486
|
highlightSpecialChars(),
|
|
484
|
-
history(),
|
|
485
487
|
foldGutter(),
|
|
486
488
|
drawSelection(),
|
|
487
489
|
EditorState.allowMultipleSelections.of(true),
|
|
@@ -489,9 +491,10 @@ export class EditorViewAdapter {
|
|
|
489
491
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
490
492
|
bracketMatching({ renderMatch: renderBracketMatch }),
|
|
491
493
|
closeBrackets(),
|
|
492
|
-
autocompletion(
|
|
493
|
-
|
|
494
|
-
|
|
494
|
+
autocompletion(),
|
|
495
|
+
EditorState.languageData.of(() => [{
|
|
496
|
+
autocomplete: wikiLinkCompletionSource,
|
|
497
|
+
}]),
|
|
495
498
|
rectangularSelection(),
|
|
496
499
|
crosshairCursor(),
|
|
497
500
|
highlightActiveLine(),
|
|
@@ -501,7 +504,6 @@ export class EditorViewAdapter {
|
|
|
501
504
|
keymap.of([
|
|
502
505
|
...closeBracketsKeymap,
|
|
503
506
|
...defaultKeymap,
|
|
504
|
-
...historyKeymap,
|
|
505
507
|
...searchKeymap.map((binding) => (
|
|
506
508
|
binding.run === openSearchPanel
|
|
507
509
|
? { ...binding, run: openSearchPanelPreservingViewport }
|
|
@@ -593,10 +595,12 @@ export class EditorViewAdapter {
|
|
|
593
595
|
|
|
594
596
|
initialize({ awareness, filePath, undoManager, ytext }) {
|
|
595
597
|
const preserveScrollTop = this.editorView?.scrollDOM?.scrollTop ?? 0;
|
|
598
|
+
this.undoManager = undoManager;
|
|
596
599
|
const state = EditorState.create({
|
|
597
600
|
doc: ytext.toString(),
|
|
598
601
|
extensions: [
|
|
599
602
|
...this.getBaseExtensions(filePath),
|
|
603
|
+
keymap.of(yUndoManagerKeymap),
|
|
600
604
|
yCollab(ytext, awareness, { undoManager }),
|
|
601
605
|
],
|
|
602
606
|
});
|
|
@@ -609,6 +613,7 @@ export class EditorViewAdapter {
|
|
|
609
613
|
|
|
610
614
|
initializeProvisional({ content = '', filePath }) {
|
|
611
615
|
const preserveScrollTop = this.editorView?.scrollDOM?.scrollTop ?? 0;
|
|
616
|
+
this.undoManager = null;
|
|
612
617
|
const state = EditorState.create({
|
|
613
618
|
doc: String(content ?? ''),
|
|
614
619
|
extensions: this.getBaseExtensions(filePath, { readOnly: true }),
|
|
@@ -629,6 +634,7 @@ export class EditorViewAdapter {
|
|
|
629
634
|
clearTimeout(this.remoteUpdateFlashTimer);
|
|
630
635
|
this.remoteUpdateFlashTimer = 0;
|
|
631
636
|
}
|
|
637
|
+
this.undoManager = null;
|
|
632
638
|
this.teardownEditorView({ clearContainer: true });
|
|
633
639
|
}
|
|
634
640
|
|
|
@@ -709,18 +715,58 @@ export class EditorViewAdapter {
|
|
|
709
715
|
this.editorView?.requestMeasure();
|
|
710
716
|
}
|
|
711
717
|
|
|
718
|
+
async formatDocument(filePath) {
|
|
719
|
+
const view = this.editorView;
|
|
720
|
+
if (!view || view.state.readOnly) {
|
|
721
|
+
return 'unsupported';
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const source = view.state.doc.toString();
|
|
725
|
+
const formatted = await formatDocumentText(filePath, source);
|
|
726
|
+
if (formatted == null) {
|
|
727
|
+
return 'unsupported';
|
|
728
|
+
}
|
|
729
|
+
if (this.editorView !== view || view.state.doc.toString() !== source) {
|
|
730
|
+
return 'changed';
|
|
731
|
+
}
|
|
732
|
+
if (formatted === source) {
|
|
733
|
+
return 'unchanged';
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const selection = EditorSelection.create(view.state.selection.ranges.map(({ anchor, head }) => (
|
|
737
|
+
EditorSelection.range(Math.min(anchor, formatted.length), Math.min(head, formatted.length))
|
|
738
|
+
)));
|
|
739
|
+
view.dispatch({
|
|
740
|
+
changes: { from: 0, to: source.length, insert: formatted },
|
|
741
|
+
selection,
|
|
742
|
+
scrollIntoView: true,
|
|
743
|
+
userEvent: 'input.format',
|
|
744
|
+
});
|
|
745
|
+
view.focus();
|
|
746
|
+
return 'formatted';
|
|
747
|
+
}
|
|
748
|
+
|
|
712
749
|
runEditorCommand(commandId) {
|
|
713
750
|
if (!this.editorView) {
|
|
714
751
|
return false;
|
|
715
752
|
}
|
|
716
753
|
|
|
717
|
-
const
|
|
754
|
+
const normalizedCommandId = String(commandId ?? '');
|
|
755
|
+
if ((normalizedCommandId === 'undo' || normalizedCommandId === 'redo') && this.undoManager) {
|
|
756
|
+
const applied = this.undoManager[normalizedCommandId]() != null;
|
|
757
|
+
if (applied) {
|
|
758
|
+
this.editorView.focus();
|
|
759
|
+
}
|
|
760
|
+
return applied;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const command = EDITOR_COMMANDS[normalizedCommandId];
|
|
718
764
|
if (typeof command !== 'function') {
|
|
719
765
|
return false;
|
|
720
766
|
}
|
|
721
767
|
|
|
722
768
|
const applied = command(this.editorView);
|
|
723
|
-
if (applied &&
|
|
769
|
+
if (applied && normalizedCommandId !== 'openSearch') {
|
|
724
770
|
this.editorView.focus();
|
|
725
771
|
}
|
|
726
772
|
|