Skip to content

Commit 4ed2ac8

Browse files
committed
feat: support export as svg
close #17
1 parent cb5005b commit 4ed2ac8

File tree

2 files changed

+117
-88
lines changed

2 files changed

+117
-88
lines changed

src/app.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ const handlers = {
3636
setTheme(dark: boolean) {
3737
document.documentElement.classList[dark ? 'add' : 'remove']('markmap-dark');
3838
},
39+
downloadSvg(path: string) {
40+
const content = new XMLSerializer().serializeToString(mm.svg.node());
41+
vscode.postMessage({ type: 'downloadSvg', data: { content, path } });
42+
},
3943
};
4044
window.addEventListener('message', (e) => {
4145
const { type, data } = e.data;
@@ -67,18 +71,18 @@ toolbar.register({
6771
onClick: clickHandler('editAsText'),
6872
});
6973
toolbar.register({
70-
id: 'exportAsHtml',
71-
title: 'Export as HTML',
74+
id: 'export',
75+
title: 'Export',
7276
content: createButton('Export'),
73-
onClick: clickHandler('exportAsHtml'),
77+
onClick: clickHandler('export'),
7478
});
7579
toolbar.setItems([
7680
'zoomIn',
7781
'zoomOut',
7882
'fit',
7983
'recurse',
8084
'editAsText',
81-
'exportAsHtml',
85+
'export',
8286
]);
8387
const highlightEl = document.createElement('div');
8488
highlightEl.className = 'markmap-highlight-area';

src/extension.ts

Lines changed: 109 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,39 @@ import {
1616
workspace,
1717
} from 'vscode';
1818
import { Utils } from 'vscode-uri';
19+
import localImage from './plugins/local-image';
1920
import {
2021
getAssets,
2122
getLocalTransformer,
2223
mergeAssets,
2324
setExportMode,
2425
transformerExport,
2526
} from './util';
26-
import localImage from './plugins/local-image';
2727

2828
const PREFIX = 'markmap-vscode';
2929
const VIEW_TYPE = `${PREFIX}.markmap`;
3030

31-
const renderToolbar = () => {
31+
function renderToolbar() {
3232
const { markmap, mm } = window as any;
3333
const { el } = markmap.Toolbar.create(mm);
3434
el.setAttribute('style', 'position:absolute;bottom:20px;right:20px');
3535
document.body.append(el);
36-
};
36+
}
37+
38+
async function writeFile(targetUri: Uri, text: string) {
39+
const encoder = new TextEncoder();
40+
const data = encoder.encode(text);
41+
try {
42+
await workspace.fs.writeFile(targetUri, data);
43+
} catch (e) {
44+
vscodeWindow.showErrorMessage(
45+
`Cannot write file "${targetUri.toString()}"!`,
46+
);
47+
}
48+
}
3749

3850
class MarkmapEditor implements CustomTextEditorProvider {
39-
constructor(private context: ExtensionContext) { }
51+
constructor(private context: ExtensionContext) {}
4052

4153
private resolveAssetPath(relPath: string) {
4254
return Utils.joinPath(this.context.extensionUri, relPath);
@@ -62,7 +74,11 @@ class MarkmapEditor implements CustomTextEditorProvider {
6274
.asWebviewUri(this.resolveAssetPath(relPath))
6375
.toString();
6476
const transformerLocal = getLocalTransformer([
65-
localImage(relPath => webviewPanel.webview.asWebviewUri(Utils.joinPath(Utils.dirname(document.uri), relPath)).toString())
77+
localImage((relPath) =>
78+
webviewPanel.webview
79+
.asWebviewUri(Utils.joinPath(Utils.dirname(document.uri), relPath))
80+
.toString(),
81+
),
6682
]);
6783
const { allAssets } = getAssets(transformerLocal);
6884
const resolvedAssets = {
@@ -158,6 +174,81 @@ class MarkmapEditor implements CustomTextEditorProvider {
158174
const debouncedUpdate = debounce(update, 300);
159175

160176
const logger = vscodeWindow.createOutputChannel('Markmap');
177+
178+
const exportAsHtml = async (targetUri: Uri) => {
179+
const md = document.getText();
180+
const { root, features, frontmatter } = transformerExport.transform(md);
181+
const jsonOptions = {
182+
...globalOptions,
183+
...(frontmatter as any)?.markmap,
184+
};
185+
const { embedAssets } = jsonOptions as { embedAssets?: boolean };
186+
setExportMode(embedAssets);
187+
let assets = transformerExport.getUsedAssets(features);
188+
const { baseAssets, toolbarAssets } = getAssets(transformerExport);
189+
assets = mergeAssets(baseAssets, assets, toolbarAssets, {
190+
styles: [
191+
...(customCSS
192+
? [
193+
{
194+
type: 'style',
195+
data: customCSS,
196+
} as CSSItem,
197+
]
198+
: []),
199+
],
200+
scripts: [
201+
{
202+
type: 'iife',
203+
data: {
204+
fn: (r: typeof renderToolbar) => {
205+
setTimeout(r);
206+
},
207+
getParams: () => [renderToolbar],
208+
},
209+
},
210+
],
211+
});
212+
if (embedAssets) {
213+
const [styles, scripts] = await Promise.all([
214+
Promise.all(
215+
(assets.styles || []).map(async (item): Promise<CSSItem> => {
216+
if (item.type === 'stylesheet') {
217+
return {
218+
type: 'style',
219+
data: await this.loadAsset(item.data.href),
220+
};
221+
}
222+
return item;
223+
}),
224+
),
225+
Promise.all(
226+
(assets.scripts || []).map(async (item): Promise<JSItem> => {
227+
if (item.type === 'script' && item.data.src) {
228+
return {
229+
...item,
230+
data: {
231+
textContent: await this.loadAsset(item.data.src),
232+
},
233+
};
234+
}
235+
return item;
236+
}),
237+
),
238+
]);
239+
assets = {
240+
styles,
241+
scripts,
242+
};
243+
}
244+
const html = fillTemplate(root, assets, {
245+
baseJs: [],
246+
jsonOptions,
247+
urlBuilder: transformerExport.urlBuilder,
248+
});
249+
await writeFile(targetUri, html);
250+
};
251+
161252
const messageHandlers: { [key: string]: (data?: any) => void } = {
162253
refresh: () => {
163254
update();
@@ -169,94 +260,28 @@ class MarkmapEditor implements CustomTextEditorProvider {
169260
viewColumn: ViewColumn.Beside,
170261
});
171262
},
172-
exportAsHtml: async () => {
263+
async export() {
173264
const targetUri = await vscodeWindow.showSaveDialog({
174265
saveLabel: 'Export',
175266
filters: {
176267
HTML: ['html'],
268+
SVG: ['svg'],
177269
},
178270
});
179271
if (!targetUri) return;
180-
const md = document.getText();
181-
const { root, features, frontmatter } = transformerExport.transform(md);
182-
const jsonOptions = {
183-
...globalOptions,
184-
...(frontmatter as any)?.markmap,
185-
};
186-
const { embedAssets } = jsonOptions as { embedAssets?: boolean };
187-
setExportMode(embedAssets);
188-
let assets = transformerExport.getUsedAssets(features);
189-
const { baseAssets, toolbarAssets } = getAssets(transformerExport);
190-
assets = mergeAssets(baseAssets, assets, toolbarAssets, {
191-
styles: [
192-
...(customCSS
193-
? [
194-
{
195-
type: 'style',
196-
data: customCSS,
197-
} as CSSItem,
198-
]
199-
: []),
200-
],
201-
scripts: [
202-
{
203-
type: 'iife',
204-
data: {
205-
fn: (r: typeof renderToolbar) => {
206-
setTimeout(r);
207-
},
208-
getParams: () => [renderToolbar],
209-
},
210-
},
211-
],
212-
});
213-
if (embedAssets) {
214-
const [styles, scripts] = await Promise.all([
215-
Promise.all(
216-
(assets.styles || []).map(async (item): Promise<CSSItem> => {
217-
if (item.type === 'stylesheet') {
218-
return {
219-
type: 'style',
220-
data: await this.loadAsset(item.data.href),
221-
};
222-
}
223-
return item;
224-
}),
225-
),
226-
Promise.all(
227-
(assets.scripts || []).map(async (item): Promise<JSItem> => {
228-
if (item.type === 'script' && item.data.src) {
229-
return {
230-
...item,
231-
data: {
232-
textContent: await this.loadAsset(item.data.src),
233-
},
234-
};
235-
}
236-
return item;
237-
}),
238-
),
239-
]);
240-
assets = {
241-
styles,
242-
scripts,
243-
};
244-
}
245-
const html = fillTemplate(root, assets, {
246-
baseJs: [],
247-
jsonOptions,
248-
urlBuilder: transformerExport.urlBuilder,
249-
});
250-
const encoder = new TextEncoder();
251-
const data = encoder.encode(html);
252-
try {
253-
await workspace.fs.writeFile(targetUri, data);
254-
} catch (e) {
255-
vscodeWindow.showErrorMessage(
256-
`Cannot write file "${targetUri.toString()}"!`,
257-
);
272+
if (targetUri.path.endsWith('.html')) {
273+
await exportAsHtml(targetUri);
274+
} else if (targetUri.path.endsWith('.svg')) {
275+
webviewPanel.webview.postMessage({
276+
type: 'downloadSvg',
277+
data: targetUri.toString(),
278+
});
258279
}
259280
},
281+
async downloadSvg(data: { content: string; path: string }) {
282+
const targetUri = Uri.parse(data.path);
283+
await writeFile(targetUri, data.content);
284+
},
260285
openFile(relPath: string) {
261286
const filePath = Utils.joinPath(Utils.dirname(document.uri), relPath);
262287
commands.executeCommand('vscode.open', filePath);

0 commit comments

Comments
 (0)