import '@logseq/libs' ; import { Transformer } from 'markmap-lib' ; import * as markmap from 'markmap-view' ; import { Markmap } from 'markmap-view' ; import { Toolbar } from 'markmap-toolbar' ; import { INode } from 'markmap-common' ; import html2canvas from 'html2canvas' ; const transformer = new Transformer ( ) ; import * as d3 from 'd3' ; import org from 'org' ; import TurndownService from 'turndown' ; import cheerio from 'cheerio' ; import replaceAsync from 'string-replace-async' ; import ellipsis from 'text-ellipsis' ; import { BlockEntity , BlockUUIDTuple } from '@logseq/libs/dist/LSPlugin' ; function eventFire ( el , etype ) { if ( el . fireEvent ) { el . fireEvent ( 'on' + etype ) ; } else { var evObj = document . createEvent ( 'Events' ) ; evObj . initEvent ( etype , true , false ) ; el . dispatchEvent ( evObj ) ; } } let renderAsBlock = false ; let editingBlockUUID = '' ; const triggerMarkmap = async ( { uuid } ) => { const blocks = await logseq . Editor . getSelectedBlocks ( ) ; const editing = await logseq . Editor . checkEditing ( ) ; if ( uuid && ( editing || blocks && blocks . length > 0 ) ) { editingBlockUUID = uuid ; createModel ( ) . openMindMap ( true ) ; } else { createModel ( ) . openMindMap ( false ) ; } } ; const triggerMarkmapForceBlock = async ( { uuid } ) => { editingBlockUUID = uuid ; createModel ( ) . openMindMap ( true ) ; } ; const closeButtonHandler = ( ) => { logseq . hideMainUI ( { restoreEditingCursor : true } ) ; } ; const goBackButtonHandler = async ( ) => { await logseq . App . invokeExternalCommand ( 'logseq.go/backward' ) ; logseq . hideMainUI ( ) ; setTimeout ( ( ) => { logseq . showMainUI ( { autoFocus : true } ) ; } , 10 ) ; } ; /**
 * User model
 */ function createModel ( ) { return { openMindMap ( blockMode = false ) { Alpine . store ( 'showHelp' ) . close ( ) ; const closeButton = document . getElementById ( 'close-button' ) ; closeButton . removeEventListener ( 'click' , closeButtonHandler , false ) ; closeButton . addEventListener ( 'click' , closeButtonHandler , false ) ; const goBackButton = document . getElementById ( 'go-back-button' ) ; goBackButton . removeEventListener ( 'click' , goBackButtonHandler , false ) ; goBackButton . addEventListener ( 'click' , goBackButtonHandler , false ) ; if ( blockMode === true || blockMode === false ) { renderAsBlock = blockMode ; } else { renderAsBlock = false ; } logseq . showMainUI ( { autoFocus : true } ) ; } , } ; } async function main ( ) { logseq . setMainUIInlineStyle ( { position : 'fixed' , zIndex : 12 , } ) ; logseq . App . registerCommandPalette ( { key : 'mark-map-open-editing' , label : 'Open Markmap in editing' , keybinding : { mode : 'global' , binding : 'ctrl+m ctrl+m' } } , triggerMarkmap ) ; logseq . App . registerCommandPalette ( { key : 'mark-map-open' , label : 'Open Markmap' , keybinding : { mode : 'non-editing' , binding : 'm m' } } , triggerMarkmap ) ; logseq . Editor . registerSlashCommand ( 'Markmap' , triggerMarkmapForceBlock ) ; logseq . Editor . registerBlockContextMenuItem ( `Markmap` , triggerMarkmapForceBlock ) ; logseq . App . registerUIItem ( 'pagebar' , { key : 'logseq-mark-map' , template : `
     <a data-on-click="openMindMap" title="Open mindmap mode">
      <svg t="1627350023942" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="670" width="18" height="18"><path d="M840.533333 490.666667l-17.066666-85.333334L554.666667 460.8V170.666667h-85.333334v262.4l-296.533333-192-46.933333 72.533333 324.266666 209.066667L200.533333 849.066667l68.266667 51.2 241.066667-315.733334 179.2 270.933334 72.533333-46.933334-179.2-266.666666z" fill="#CFD8DC" p-id="671"></path><path d="M512 512m-149.333333 0a149.333333 149.333333 0 1 0 298.666666 0 149.333333 149.333333 0 1 0-298.666666 0Z" fill="#C435F3" p-id="672" data-spm-anchor-id="a313x.7781069.0.i0" class=""></path><path d="M512 170.666667m-106.666667 0a106.666667 106.666667 0 1 0 213.333334 0 106.666667 106.666667 0 1 0-213.333334 0Z" fill="#F48233" p-id="673" data-spm-anchor-id="a313x.7781069.0.i4" class="selected"></path><path d="M832 448m-106.666667 0a106.666667 106.666667 0 1 0 213.333334 0 106.666667 106.666667 0 1 0-213.333334 0Z" fill="#F48233" p-id="674" data-spm-anchor-id="a313x.7781069.0.i5" class="selected"></path><path d="M149.333333 277.333333m-106.666666 0a106.666667 106.666667 0 1 0 213.333333 0 106.666667 106.666667 0 1 0-213.333333 0Z" fill="#F48233" p-id="675" data-spm-anchor-id="a313x.7781069.0.i3" class="selected"></path><path d="M234.666667 874.666667m-106.666667 0a106.666667 106.666667 0 1 0 213.333333 0 106.666667 106.666667 0 1 0-213.333333 0Z" fill="#F48233" p-id="676" data-spm-anchor-id="a313x.7781069.0.i7" class="selected"></path><path d="M725.333333 832m-106.666666 0a106.666667 106.666667 0 1 0 213.333333 0 106.666667 106.666667 0 1 0-213.333333 0Z" fill="#F48233" p-id="677" data-spm-anchor-id="a313x.7781069.0.i6" class="selected"></path></svg>
     </a>
    ` , } ) ; const themeWorkflowTag = str => { return str . replace ( /^(TODO|DOING|DONE|LATER|NOW) / , ( match , p1 ) => { switch ( p1 ) { case 'TODO' : return '<code style="background: #845EC2; color: #eee">' + p1 + '</code> ' ; case 'DOING' : return '<code style="background: #FF8066; color: #eee">' + p1 + '</code> ' ; case 'DONE' : return '<code style="background: #008B74; color: #eee">' + p1 + '</code> ' ; case 'NOW' : return '<code style="background: #006C9A; color: #eee">' + p1 + '</code> ' ; case 'LATER' : return '<code style="background: #911F27; color: #eee">' + p1 + '</code> ' ; } } ) ; } ; const convertFlatBlocksToTree = async ( blocks ) : Promise < BlockEntity [ ] > => { const children = [ ] ; if ( blocks && blocks . length > 0 ) { for ( let item of blocks ) { if ( Array . isArray ( item ) ) { const block = await logseq . Editor . getBlock ( item [ 1 ] ) ; if ( block . children . length > 0 ) { block . children = await convertFlatBlocksToTree ( block . children as BlockUUIDTuple [ ] ) ; } children . push ( block ) ; } else { children . push ( item ) ; } } } return children ; } ; let mm ; let currentLevel ; let totalLevel ; let originalRoot ; let originalTotalLevel ; let config = await logseq . App . getUserConfigs ( ) ; logseq . App . onCurrentGraphChanged ( async ( ) => { config = await logseq . App . getUserConfigs ( ) ; } ) ; logseq . on ( 'ui:visible:changed' , async ( { visible } ) => { if ( ! visible ) { return ; } let blocks = await logseq . Editor . getCurrentPageBlocksTree ( ) ; let page = await logseq . Editor . getCurrentPage ( ) as any ; let title ; if ( renderAsBlock ) { let currentBlock ; if ( editingBlockUUID ) { currentBlock = await logseq . Editor . getBlock ( editingBlockUUID ) ; } else { currentBlock = await logseq . Editor . getCurrentBlock ( ) ; } if ( currentBlock ) { let content = currentBlock . content ; content = content ? content . split ( '\n' ) . filter ( ( line ) => line . indexOf ( '::' ) === - 1 ) . join ( '\n' ) . replace ( /^[#\s]+/ , '' ) . trim ( ) : '' ; title = content ; blocks = await convertFlatBlocksToTree ( currentBlock ?. children ) ; } } else { title = page ?. properties ?. markMapTitle || page ?. originalName || page ?. name ; } if ( page && ! page . originalName && page . content ) { let content = page . content ; content = content ? content . split ( '\n' ) . filter ( ( line ) => line . indexOf ( '::' ) === - 1 ) . join ( '\n' ) . replace ( /^[#\s]+/ , '' ) . trim ( ) : '' ; title = content ; blocks = await convertFlatBlocksToTree ( page ?. children ) ; } const collapsed = page ?. properties ?. markMapCollapsed ; currentLevel = - 1 ; const blockFilter = ( it ) => { const { children , uuid , title , content , properties } = it ; if ( properties ?. markMapDisplay === 'hidden' ) { return false ; } if ( ! content || content . startsWith ( '---\n' ) ) { return false ; } let contentFiltered = content . split ( '\n' ) . filter ( ( line ) => line . indexOf ( '::' ) === - 1 ) . join ( '\n' ) ; const topic = contentFiltered . replace ( /^[#\s]+/ , '' ) . trim ( ) ; if ( topic . length === 0 && ( ! children || children . length === 0 ) ) { return false ; } else { return true ; } } ; const walkTransformBlocksFilter = async ( blocks ) => { if ( blocks && blocks . length > 0 ) { for ( let it of blocks ) { let { children , uuid , title , content , properties } = it ; children = await children ; if ( children ) { it . children = await walkTransformBlocksFilter ( children ) ; } } return blocks . filter ( blockFilter ) ; } } ; let filteredBlocks = await walkTransformBlocksFilter ( blocks ) ; if ( page ?. properties ?. markMapLimitAll && filteredBlocks . length > page ?. properties ?. markMapLimitAll ) { const limitBlocks = filteredBlocks . splice ( 0 , page ?. properties ?. markMapLimitAll ) ; filteredBlocks = limitBlocks . concat ( { content : '...' , properties : { collapsed : true } , children : filteredBlocks } ) ; } else if ( page ?. properties ?. markMapLimit && filteredBlocks . length > page ?. properties ?. markMapLimit ) { const limitBlocks = filteredBlocks . splice ( 0 , page ?. properties ?. markMapLimit ) ; filteredBlocks = limitBlocks . concat ( { content : '...' , properties : { collapsed : true } , children : filteredBlocks } ) ; } const walkTransformBlocksLimit = ( blocks , limit = 0 ) => { if ( limit && blocks && blocks . length > limit ) { const limitBlocks = blocks . splice ( 0 , limit ) ; blocks = limitBlocks . concat ( { content : '...' , properties : { collapsed : true } , children : blocks } ) ; } if ( blocks && blocks . length > 0 ) { for ( let it of blocks ) { let { children , content , properties } = it ; if ( children ) { it . children = walkTransformBlocksLimit ( children , page ?. properties ?. markMapLimitAll || properties ?. markMapLimit ) ; } } } return blocks ; } ; filteredBlocks = walkTransformBlocksLimit ( filteredBlocks ) ; const walkTransformBlocks = async ( blocks , depth = 0 , config = { } ) : Promise < string [ ] > => { currentLevel = Math . min ( 5 , Math . max ( currentLevel , depth ) ) ; totalLevel = Math . min ( 5 , Math . max ( currentLevel , depth ) ) ; if ( ! blocks ) { return [ ] ; } let newBlocks = [ ] ; for ( let it of blocks ) { const { children , uuid , title , content , properties } = it ; let contentFiltered = content . split ( '\n' ) . filter ( ( line ) => line . indexOf ( '::' ) === - 1 ) . join ( '\n' ) ; let topic = contentFiltered ; let regexPageTag = /#([^#\s]+)/ig ; if ( regexPageTag . test ( topic ) ) { topic = topic . replace ( regexPageTag , ( match , p1 ) => { return `<a style="cursor: pointer; font-size: 60%; vertical-align:middle;" target="_blank" onclick="logseq.App.pushState('page', { name: '${ p1 }' }); logseq.hideMainUI(); logseq.showMainUI();">#${ p1 }</a>` ; } ) ; } topic = themeWorkflowTag ( topic ) ; let regexLinkBlockRef = /\[(.*?)\]\(\(\(([0-9A-F]{8}-[0-9A-F]{4}-[1-5][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12})\)\)\)/ig ; if ( regexLinkBlockRef . test ( topic ) ) { topic = await replaceAsync ( topic , regexLinkBlockRef , async ( match , p1 , p2 ) => { const block = await logseq . Editor . getBlock ( p2 ) ; return `<a style="cursor: pointer" target="_blank" onclick="logseq.App.pushState('page', { name: '${ block . uuid }' }); logseq.hideMainUI(); logseq.showMainUI();">${ p1 }</a>` ; } ) ; } let regexLinkPageRef = /\[(.*?)\]\(\[\[(.*?)\]\]\)/ig ; if ( regexLinkPageRef . test ( topic ) ) { topic = await replaceAsync ( topic , regexLinkPageRef , async ( match , p1 , p2 ) => { return `<a style="cursor: pointer" target="_blank" onclick="logseq.App.pushState('page', { name: '${ p2 }' }); logseq.hideMainUI(); logseq.showMainUI();">${ p1 }</a>` ; } ) ; } let regexBlockRef = /\(\(([0-9A-F]{8}-[0-9A-F]{4}-[1-5][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12})\)\)/ig ; let regexEmbedBlockRef = /\{\{embed\s+\(\(([0-9A-F]{8}-[0-9A-F]{4}-[1-5][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12})\)\)\}\}/ig ; if ( regexEmbedBlockRef . test ( topic ) ) { topic = await replaceAsync ( topic , regexEmbedBlockRef , async ( match , p1 ) => { const block = await logseq . Editor . getBlock ( p1 ) ; if ( block ) { return `<a style="cursor: pointer" target="_blank" onclick="logseq.App.pushState('page', { name: '${ block . uuid }' }); logseq.hideMainUI(); logseq.showMainUI();">${ themeWorkflowTag ( block . content ) }</a>` ; } return '[MISSING BLOCK]' ; } ) ; } if ( regexBlockRef . test ( topic ) ) { topic = await replaceAsync ( topic , regexBlockRef , async ( match , p1 ) => { const block = await logseq . Editor . getBlock ( p1 ) ; if ( block ) { return `<a style="cursor: pointer" target="_blank" onclick="logseq.App.pushState('page', { name: '${ block . uuid }' }); logseq.hideMainUI(); logseq.showMainUI();">${ themeWorkflowTag ( block . content ) }</a>` ; } return '[MISSING BLOCK]' ; } ) ; } let regexPageRef = /\[\[([^\[\]]*?)\]\]/ig ; let regexEmbedPageRef = /\{\{embed\s+\[\[([^\[\]]*?)\]\]\}\}/ig ; if ( regexEmbedPageRef . test ( topic ) ) { topic = topic . replace ( regexEmbedPageRef , ( match , p1 ) => { return `<a style="cursor: pointer" target="_blank" onclick="logseq.App.pushState('page', { name: '${ p1 }' }); logseq.hideMainUI(); logseq.showMainUI();">${ p1 }</a>` ; } ) ; } if ( regexPageRef . test ( topic ) ) { topic = topic . replace ( regexPageRef , ( match , p1 ) => { return `<a style="cursor: pointer" target="_blank" onclick="logseq.App.pushState('page', { name: '${ p1 }' }); logseq.hideMainUI(); logseq.showMainUI();">${ p1 }</a>` ; } ) ; } if ( config . preferredFormat === 'org' ) { const turndownService = new TurndownService ( { headingStyle : 'atx' , codeBlockStyle : 'fenced' , } ) ; turndownService . addRule ( 'strikethrough' , { filter : [ 'del' , 's' , 'strike' ] , replacement : function ( content ) { return '~~' + content + '~~' ; } } ) ; const parser = new org . Parser ( ) ; const orgDocument = parser . parse ( topic ) ; const orgHTMLDocument = orgDocument . convert ( org . ConverterHTML , { headerOffset : 1 , exportFromLineNumber : false , suppressSubScriptHandling : false , suppressAutoLink : false } ) ; topic = orgHTMLDocument . toString ( ) ; topic = turndownService . turndown ( topic ) ; topic = topic . replace ( /\^\^/g , '==' ) ; } topic = topic . replace ( /^[#\s]+/ , '' ) . trim ( ) ; const regexUrl = /(https?:\/\/[-a-zA-Z0-9@:%_\+.~#?&\/=]{2,256}\.[a-z]{2,4}(\/[-a-zA-Z0-9@:%_\+.~#?&\/=]*)?)(?=\s)/gi ; const regexUrlMatchStartEnd = /^(https?:\/\/[-a-zA-Z0-9@:%_\+.~#?&\/=]{2,256}\.[a-z]{2,4}(\/[-a-zA-Z0-9@:%_\+.~#?&\/=]*)?)$/gi ; topic = topic . replace ( regexUrl , '<$1>' ) ; topic = topic . replace ( regexUrlMatchStartEnd , '<$1>' ) ; if ( properties ?. markMapCut ) { const orgTopic = topic ; topic = ellipsis ( topic , parseInt ( properties ?. markMapCut ) ) ; topic = `<div style="cursor:pointer" title="${ orgTopic }">${ topic }</div>` ; } if ( topic . indexOf ( '```' ) === 0 || topic . indexOf ( '- ' ) === 0 ) { topic = '\n' + topic ; } let ret = ( depth < 5 ? '#' . repeat ( depth + 2 ) + ' ' : '' ) + topic ; if ( children && ( properties ?. collapsed !== true || collapsed !== 'hidden' ) ) { ret += '\n' + ( await walkTransformBlocks ( children , depth + 1 , config ) ) . join ( '\n' ) ; } newBlocks . push ( ret ) ; } ; return newBlocks ; } ; const md = '# ' + title + '\n\n' + ( await walkTransformBlocks ( filteredBlocks , 0 , config ) ) . join ( '\n' ) ; const defaultLinkRender = transformer . md . renderer . rules . link_open ; transformer . md . inline . ruler . enable ( [ 'mark' ] ) ; transformer . md . renderer . rules . link_open = function ( tokens , idx , ... args ) { let result = defaultLinkRender ( tokens , idx , ... args ) ; if ( tokens [ idx ] && tokens [ idx ] . href ) { result = result . replace ( '>' , ' target="_blank">' ) ; } return result ; } ; const defaultImageRender = transformer . md . renderer . rules . image ; transformer . md . renderer . rules . image = function ( tokens , idx , ... args ) { let result = defaultImageRender ( tokens , idx , ... args ) ; const $ = cheerio . load ( result ) ; let src = $ ( 'img' ) . attr ( 'src' ) || $ ( 'a' ) . attr ( 'href' ) ; const alt = $ ( 'img' ) . attr ( 'alt' ) || $ ( 'a' ) . attr ( 'title' ) || '' ; if ( src . indexOf ( 'http' ) !== 0 && src . indexOf ( '..' ) === 0 ) { src = config . currentGraph . substring ( 13 ) + '/' + src . replace ( /\.\.\//g , '' ) ; } result = `<a target="_blank" title="${ alt }"  data-lightbox="gallery" href="${ src }">${ alt } 🖼　</a>` ; return result ; } ; let { root , features } = transformer . transform ( md ) ; root . properties = page && page . properties ? page . properties : { } ; const walkTransformRoot = ( parent , blocks ) => { if ( parent . c ) { for ( let i in parent . c ) { parent . c [ i ] . properties = blocks [ i ] ?. properties || { } ; if ( root ?. properties ?. markMapCollapsed !== 'extend' && parent . c [ i ] ?. properties ?. collapsed ) { parent . c [ i ] . p = { ... parent . c [ i ] . p , f : true , } ; } walkTransformRoot ( parent ?. c [ i ] , blocks [ i ] ?. children || [ ] ) ; } } } ; walkTransformRoot ( root , filteredBlocks ) ; originalRoot = root ; originalTotalLevel = totalLevel ; window . root = root ; const { styles , scripts } = transformer . getUsedAssets ( features ) ; const { Markmap , loadCSS , loadJS } = markmap ; if ( styles ) loadCSS ( styles ) ; if ( scripts ) await loadJS ( scripts , { getMarkmap : ( ) => markmap } ) ; const hideAll = ( target ) => { target . p = { ... target . p , f : true , } ; target . c ?. forEach ( t => { hideAll ( t ) ; } ) ; } ; const showAll = ( target , depth = - 1 ) => { depth ++ ; if ( page ?. properties ?. markMapCollapsed !== 'extend' && target ?. properties ?. collapsed ) { target . p = { ... target . p , f : true , } ; currentLevel = depth ; } else { target . p = { ... target . p , f : false , } ; } target . c ?. forEach ( t => { showAll ( t , depth ) ; } ) ; } ; const expandStepByStep = ( target ) : boolean => { let find = false ; if ( target . p ?. f && target . c ) { target . p . f = false ; find = true ; } if ( ! find && target . c ) { for ( let t of target . c ) { find = expandStepByStep ( t ) ; if ( find ) { return find ; } } } return find ; } ; const collapseStepByStep = ( target ) : boolean => { let find = false ; if ( target . c ) { const length = target . c . length ; for ( let i = length - 1 ; i >= 0 ; i -- ) { const t = target . c [ i ] ; find = collapseStepByStep ( t ) ; if ( find ) { return find ; } } } if ( ! target . p ?. f && target . c ) { target . p . f = true ; find = true ; } return find ; } ; const expandLevel = ( target , level = 1 ) => { if ( level <= 0 ) { hideAll ( target ) ; return ; } level -- ; target . p = { ... target . p , f : false , } ; target . c ?. forEach ( t => { expandLevel ( t , level ) ; } ) ; } ; let stack = [ ] ; let pointerStack = [ ] ; let pointer ; const focusIn = ( root ) => { if ( root . c ) { pointerStack . push ( pointer ) ; pointer = 0 ; stack . push ( root ) ; root = root . c [ pointer ] ; window . root = root ; showAll ( root ) ; mm . setData ( root ) ; totalLevel -- ; currentLevel = totalLevel ; } } ; const focusOut = ( ) => { if ( stack . length > 0 ) { root = stack . pop ( ) as INode ; pointer = pointerStack . pop ( ) as number ; window . root = root ; showAll ( root ) ; mm . setData ( root ) ; totalLevel ++ ; currentLevel = totalLevel ; } } ; const focusNext = ( ) => { const top = stack [ stack . length - 1 ] ; if ( top && top . c && pointer + 1 <= top . c . length - 1 ) { root = top . c [ ++ pointer ] ; window . root = root ; mm . setData ( root ) ; } } ; const focusPrevious = ( ) => { const top = stack [ stack . length - 1 ] ; if ( top && top . c && pointer - 1 >= 0 ) { root = top . c [ -- pointer ] ; window . root = root ; mm . setData ( root ) ; } } ; const focusReset = ( ) => { root = originalRoot ; window . root = root ; stack = [ ] ; showAll ( root ) ; mm . setData ( root ) ; totalLevel = originalTotalLevel ; currentLevel = totalLevel ; } ; let svgNode ; const hotkeys = ( window as any ) ?. hotkeys ; const bindKeys = async function ( ) { if ( hotkeys ) { hotkeys ( '.' , function ( ) { const root = window . root ; focusIn ( root ) ; return false ; } ) ; hotkeys ( ',' , function ( ) { focusReset ( ) ; return false ; } ) ; hotkeys ( 'up,down,left,right,esc,space,z,r,h,j,k,l,n,p,b,q,-,=,0,9,1,2,3,4,5,/' , async function ( event , handler ) { const showHelp = Alpine . store ( 'showHelp' ) . get ( ) ; if ( showHelp && ! [ '/' , 'q' , 'esc' ] . includes ( handler . key ) ) { return ; } const jQuery = window ?. jQuery ; if ( jQuery ) { if ( jQuery ( '#lightboxOverlay' ) . css ( 'display' ) === 'block' && ! [ 'q' , 'esc' ] . includes ( handler . key ) ) { return false ; } } const root = window . root ; switch ( handler . key ) { case 'p' : focusPrevious ( ) ; break ; case 'n' : focusNext ( ) ; break ; case 'b' : focusOut ( ) ; break ; case '.' : focusReset ( ) ; break ; case ',' : focusIn ( root ) ; break ; case 'esc' : case 'q' : const jQuery = window ?. jQuery ; const lightbox = window ?. lightbox ; if ( jQuery ) { if ( jQuery ( '#lightboxOverlay' ) . css ( 'display' ) === 'block' ) { lightbox . end ( ) ; } } logseq . hideMainUI ( { restoreEditingCursor : true } ) ; break ; case 'space' : await mm ?. fit ( ) ; break ; case '0' : currentLevel = 0 ; hideAll ( root ) ; mm . setData ( root ) ; break ; case '9' : currentLevel = totalLevel ; showAll ( root ) ; mm . setData ( root ) ; break ; case '1' : hideAll ( root ) ; expandLevel ( root , 1 ) ; currentLevel = 1 ; mm . setData ( root ) ; break ; case '2' : hideAll ( root ) ; expandLevel ( root , 2 ) ; currentLevel = 2 ; mm . setData ( root ) ; break ; case '3' : hideAll ( root ) ; expandLevel ( root , 3 ) ; currentLevel = 3 ; mm . setData ( root ) ; break ; case '4' : hideAll ( root ) ; expandLevel ( root , 4 ) ; currentLevel = 4 ; mm . setData ( root ) ; break ; case '5' : hideAll ( root ) ; expandLevel ( root , 5 ) ; currentLevel = 5 ; mm . setData ( root ) ; break ; case 'h' : hideAll ( root ) ; expandLevel ( root , currentLevel > 0 ? -- currentLevel : 0 ) ; mm . setData ( root ) ; break ; case 'l' : hideAll ( root ) ; expandLevel ( root , currentLevel < totalLevel ? ++ currentLevel : totalLevel ) ; mm . setData ( root ) ; break ; case 'j' : expandStepByStep ( root ) ; mm . setData ( root ) ; break ; case 'k' : collapseStepByStep ( root ) ; mm . setData ( root ) ; break ; case '=' : await mm . rescale ( 1.25 ) ; break ; case '-' : await mm . rescale ( 0.8 ) ; break ; case 'z' : const elResetButton = document . getElementById ( 'reset-button' ) ; eventFire ( elResetButton , 'click' ) ; break ; case 'r' : const elRandomButton = document . getElementById ( 'random-button' ) ; eventFire ( elRandomButton , 'click' ) ; break ; case 'up' : svgNode = mm . svg . node ( ) ; if ( svgNode ) { const transform = d3 . zoomTransform ( mm . svg . node ( ) ) ; transform . y = transform . y - 100 ; mm . transition ( mm . g ) . attr ( 'transform' , `translate(${ transform . x }, ${ transform . y } ) scale(${ transform . k })` ) ; } break ; case 'down' : svgNode = mm . svg . node ( ) ; if ( svgNode ) { const transform = d3 . zoomTransform ( mm . svg . node ( ) ) ; transform . y = transform . y + 100 ; mm . transition ( mm . g ) . attr ( 'transform' , `translate(${ transform . x }, ${ transform . y } ) scale(${ transform . k })` ) ; } break ; case 'left' : svgNode = mm . svg . node ( ) ; if ( svgNode ) { const transform = d3 . zoomTransform ( mm . svg . node ( ) ) ; transform . x = transform . x - 100 ; mm . transition ( mm . g ) . attr ( 'transform' , `translate(${ transform . x }, ${ transform . y } ) scale(${ transform . k })` ) ; } break ; case 'right' : svgNode = mm . svg . node ( ) ; if ( svgNode ) { const transform = d3 . zoomTransform ( mm . svg . node ( ) ) ; transform . x = transform . x + 100 ; mm . transition ( mm . g ) . attr ( 'transform' , `translate(${ transform . x }, ${ transform . y } ) scale(${ transform . k })` ) ; } break ; case '/' : Alpine . store ( 'showHelp' ) . toggle ( ) ; break ; default : break ; } return false ; } ) ; } } ; if ( mm ) { showAll ( root ) ; mm . setData ( root ) ; } else { showAll ( root ) ; mm = Markmap . create ( '#markmap' , { autoFit : true , } , root ) ; bindKeys ( ) ; const toolbar = new Toolbar ( ) ; toolbar . setItems ( [ 'zoomIn' , 'zoomOut' , 'fit' , 'save' , 'help' ] ) ; toolbar . setBrand ( false ) ; toolbar . register ( { id : 'save' , title : 'Save as png' , content : '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>' , onClick : async ( ) => { let el = document . getElementById ( 'markmap-container' ) ; const page = await logseq . Editor . getCurrentPage ( ) ; if ( el ) { html2canvas ( el ) . then ( function ( canvas ) { const title = page ?. originalName ; let url = canvas . toDataURL ( 'image/png' ) ; var oA = document . createElement ( 'a' ) ; oA . download = title || '' ; oA . href = url ; document . body . appendChild ( oA ) ; oA . click ( ) ; oA . remove ( ) ; } ) ; } } , } ) ; toolbar . register ( { id : 'help' , title : 'Show shortcuts description' , content : '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /> </svg>' , onClick : async ( ) => { Alpine . store ( 'showHelp' ) . toggle ( ) ; } } ) ; toolbar . attach ( mm ) ; const el = toolbar . render ( ) ; el . style . position = 'absolute' ; el . style . bottom = '0.5rem' ; el . style . right = '0.5rem' ; document . getElementById ( 'markmap-toolbar' ) ?. append ( el ) ; } ; } ) ; } logseq . ready ( createModel ( ) , main ) . catch ( e => console . error ) ;