@@ -16,27 +16,39 @@ import {
1616 workspace ,
1717} from 'vscode' ;
1818import { Utils } from 'vscode-uri' ;
19+ import localImage from './plugins/local-image' ;
1920import {
2021 getAssets ,
2122 getLocalTransformer ,
2223 mergeAssets ,
2324 setExportMode ,
2425 transformerExport ,
2526} from './util' ;
26- import localImage from './plugins/local-image' ;
2727
2828const PREFIX = 'markmap-vscode' ;
2929const 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
3850class 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