diff --git a/assets/style.css b/assets/style.css index 93a901f..91f3b93 100644 --- a/assets/style.css +++ b/assets/style.css @@ -23,15 +23,3 @@ body { padding-left: 4px; padding-right: 4px; } - -.markmap-highlight-area { - --mm-highlight-scale: 1; - position: absolute; - width: var(--mm-highlight-width); - height: var(--mm-highlight-height); - left: var(--mm-highlight-x); - top: var(--mm-highlight-y); - z-index: -1; - background: #ff02; - transform: scale(var(--mm-highlight-scale)); -} diff --git a/src/app.ts b/src/app.ts index 502866e..45e6cc2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -66,7 +66,7 @@ const handlers = { if (!result) return; const { node, needRerender } = result; if (needRerender) await mm.renderData(); - if (node) highlightNode(node); + highlightNode(node); }, setCSS(data: string) { if (!style) { @@ -96,7 +96,7 @@ document.addEventListener('click', (e) => { const href = el.getAttribute('href'); if (href.startsWith('#')) { const node = findHeading(href.slice(1)); - if (node) highlightNode(node); + highlightNode(node); } else if (!href.includes('://')) { vscode.postMessage({ type: 'openFile', @@ -128,14 +128,12 @@ toolbar.setItems([ 'editAsText', 'export', ]); -const highlightEl = document.createElement('div'); -highlightEl.className = 'markmap-highlight-area'; + checkTheme(); setTimeout(() => { toolbar.attach(mm); document.body.append(toolbar.el); - checkHighlight(); }); function checkTheme() { @@ -208,30 +206,12 @@ function findActiveNode({ return best && { node: best, needRerender }; } -async function highlightNode(node: INode) { +async function highlightNode(node?: INode) { + await mm.setHighlight(node); + if (!node) return; await mm[ activeNodeOptions.placement === 'center' ? 'centerNode' : 'ensureVisible' ](node, { bottom: 80, }); - const g = mm.findElement(node)?.g; - const el = g?.querySelector('foreignObject'); - active = el && { el, node }; -} - -function checkHighlight() { - if (!active) { - highlightEl.remove(); - } else { - const rect = active.el.getBoundingClientRect(); - const padding = 4; - const { width } = active.node.state.rect; - const scale = (width + padding * 2) / width; - highlightEl.setAttribute( - 'style', - `--mm-highlight-x:${rect.x}px;--mm-highlight-y:${rect.y}px;--mm-highlight-width:${rect.width}px;--mm-highlight-height:${rect.height}px;--mm-highlight-scale:${scale}`, - ); - if (!highlightEl.parentNode) document.body.append(highlightEl); - } - requestAnimationFrame(checkHighlight); } diff --git a/src/extension.ts b/src/extension.ts index b1f62ee..62ad035 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,12 @@ import debounce from 'lodash.debounce'; -import { JSItem, type CSSItem } from 'markmap-common'; -import { fillTemplate } from 'markmap-render'; +import { + buildJSItem, + mergeAssets, + type CSSItem, + type JSItem, +} from 'markmap-common'; +import { Transformer, builtInPlugins } from 'markmap-lib'; +import { baseJsPaths, fillTemplate } from 'markmap-render'; import { type IMarkmapJSONOptions } from 'markmap-view'; import { CustomTextEditorProvider, @@ -19,10 +25,10 @@ import { import { Utils } from 'vscode-uri'; import localImage from './plugins/local-image'; import { - getAssets, - getLocalTransformer, - mergeAssets, + ASSETS_PREFIX, + appAssets, setExportMode, + toolbarAssets, transformerExport, } from './util'; @@ -69,44 +75,57 @@ class MarkmapEditor implements CustomTextEditorProvider { webviewPanel.webview.options = { enableScripts: true, }; - const resolveUrl = (relPath: string) => - webviewPanel.webview - .asWebviewUri(this.resolveAssetPath(relPath)) - .toString(); - const transformerLocal = getLocalTransformer([ + const transformerLocal = new Transformer([ + ...builtInPlugins, localImage((relPath) => webviewPanel.webview .asWebviewUri(Utils.joinPath(Utils.dirname(document.uri), relPath)) .toString(), ), ]); - const { allAssets } = getAssets(transformerLocal); - const resolvedAssets = { - ...allAssets, - styles: allAssets.styles?.map((item) => { - if (item.type === 'stylesheet') { - return { - ...item, - data: { - href: resolveUrl(item.data.href), - }, - }; - } - return item; - }), - scripts: allAssets.scripts?.map((item) => { - if (item.type === 'script' && item.data.src) { - return { - ...item, - data: { - ...item.data, - src: resolveUrl(item.data.src), - }, - }; - } - return item; - }), - }; + const resolveUrl = (path: string) => + webviewPanel.webview.asWebviewUri(this.resolveAssetPath(path)).toString(); + const providerName = 'local'; + transformerLocal.urlBuilder.providers[providerName] = (path: string) => + resolveUrl(`${ASSETS_PREFIX}${path}`); + transformerLocal.urlBuilder.provider = providerName; + const otherAssets = mergeAssets( + { + scripts: baseJsPaths.map(buildJSItem), + }, + toolbarAssets, + ); + const resolvedAssets = mergeAssets( + { + styles: otherAssets.styles?.map((item) => + transformerLocal.resolveCSS(item), + ), + scripts: otherAssets.scripts?.map((item) => + transformerLocal.resolveJS(item), + ), + }, + transformerLocal.getAssets(), + { + styles: appAssets.styles?.map((item) => + item.type === 'stylesheet' + ? { + ...item, + data: { + href: resolveUrl(item.data.href), + }, + } + : item, + ), + scripts: appAssets.scripts?.map((item) => + item.type === 'script' && item.data.src + ? { + ...item, + data: { src: resolveUrl(item.data.src) }, + } + : item, + ), + }, + ); webviewPanel.webview.html = fillTemplate(undefined, resolvedAssets, { baseJs: [], urlBuilder: transformerLocal.urlBuilder, @@ -185,31 +204,46 @@ class MarkmapEditor implements CustomTextEditorProvider { }; const { embedAssets } = jsonOptions as { embedAssets?: boolean }; setExportMode(embedAssets); - let assets = transformerExport.getUsedAssets(features); - const { baseAssets, toolbarAssets } = getAssets(transformerExport); - assets = mergeAssets(baseAssets, assets, toolbarAssets, { - styles: [ - ...(customCSS - ? [ - { - type: 'style', - data: customCSS, - } as CSSItem, - ] - : []), - ], - scripts: [ - { - type: 'iife', - data: { - fn: (r: typeof renderToolbar) => { - setTimeout(r); + const otherAssets = mergeAssets( + { + scripts: baseJsPaths.map(buildJSItem), + }, + toolbarAssets, + ); + let assets = mergeAssets( + { + styles: otherAssets.styles?.map((item) => + transformerExport.resolveCSS(item), + ), + scripts: otherAssets.scripts?.map((item) => + transformerExport.resolveJS(item), + ), + }, + transformerExport.getUsedAssets(features), + { + styles: [ + ...(customCSS + ? [ + { + type: 'style', + data: customCSS, + } as CSSItem, + ] + : []), + ], + scripts: [ + { + type: 'iife', + data: { + fn: (r: typeof renderToolbar) => { + setTimeout(r); + }, + getParams: () => [renderToolbar], }, - getParams: () => [renderToolbar], }, - }, - ], - }); + ], + }, + ); if (embedAssets) { const [styles, scripts] = await Promise.all([ Promise.all( diff --git a/src/postbuild.ts b/src/postbuild.ts index 0b53693..6cbdb61 100644 --- a/src/postbuild.ts +++ b/src/postbuild.ts @@ -1,37 +1,39 @@ import { createWriteStream } from 'fs'; import { mkdir, stat } from 'fs/promises'; +import { extractAssets } from 'markmap-common'; +import { Transformer } from 'markmap-lib'; +import { baseJsPaths } from 'markmap-render'; import { dirname, resolve } from 'path'; import { Readable } from 'stream'; -import { ReadableStream } from 'stream/web'; import { finished } from 'stream/promises'; -import { Transformer } from 'markmap-lib'; -import { ASSETS_PREFIX, getAssets, localProvider } from './util'; +import { ReadableStream } from 'stream/web'; +import { ASSETS_PREFIX, localProvider, toolbarAssets } from './util'; const providerName = 'local-hook'; -async function fetchAssets() { +async function fetchAssets(assetsDir: string) { const transformer = new Transformer(); - const { provider } = transformer.urlBuilder; - transformer.urlBuilder.setProvider(providerName, localProvider); + transformer.urlBuilder.providers[providerName] = localProvider; transformer.urlBuilder.provider = providerName; - const { allAssets: assets } = getAssets(transformer); + const assets = transformer.getAssets(); delete transformer.urlBuilder.providers[providerName]; - transformer.urlBuilder.provider = provider; - const paths = [ - ...(assets.scripts?.map( - (item) => (item.type === 'script' && item.data.src) || '', - ) || []), - ...(assets.styles?.map( - (item) => (item.type === 'stylesheet' && item.data.href) || '', - ) || []), - ] + const pluginPaths = extractAssets(assets) .filter((url) => url.startsWith(ASSETS_PREFIX)) .map((url) => url.slice(ASSETS_PREFIX.length)); + const resources = transformer.plugins.flatMap( + (plugin) => plugin.config?.resources || [], + ); + const paths = [ + ...baseJsPaths, + ...pluginPaths, + ...resources, + ...extractAssets(toolbarAssets), + ]; const fastest = await transformer.urlBuilder.getFastestProvider(); await Promise.all( paths.map((path) => downloadAsset( - resolve(ASSETS_PREFIX, path), + resolve(assetsDir, path), transformer.urlBuilder.getFullUrl(path, fastest), ), ), @@ -57,7 +59,7 @@ async function downloadAsset(fullPath: string, url: string) { ); } -fetchAssets().catch((err) => { +fetchAssets(ASSETS_PREFIX).catch((err) => { console.error(err); process.exitCode = 1; }); diff --git a/src/util.ts b/src/util.ts index f3c1cf7..bc545d2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,11 +1,5 @@ import { buildCSSItem, buildJSItem } from 'markmap-common'; -import { - builtInPlugins, - IAssets, - ITransformPlugin, - Transformer, -} from 'markmap-lib'; -import { baseJsPaths } from 'markmap-render'; +import { IAssets, Transformer } from 'markmap-lib'; const TOOLBAR_VERSION = process.env.TOOLBAR_VERSION; const TOOLBAR_CSS = `markmap-toolbar@${TOOLBAR_VERSION}/dist/style.css`; @@ -15,26 +9,22 @@ const APP_JS = 'dist/app.js'; export const ASSETS_PREFIX = 'dist/web_assets/'; +export const toolbarAssets: IAssets = { + styles: [buildCSSItem(TOOLBAR_CSS)], + scripts: [buildJSItem(TOOLBAR_JS)], +}; + +export const appAssets: IAssets = { + styles: [buildCSSItem(APP_CSS)], + scripts: [buildJSItem(APP_JS)], +}; + export function localProvider(path: string) { return `${ASSETS_PREFIX}${path}`; } -export function mergeAssets(...args: IAssets[]): IAssets { - return { - styles: args.flatMap((arg) => arg.styles || []), - scripts: args.flatMap((arg) => arg.scripts || []), - }; -} - const local = 'local'; -export function getLocalTransformer(plugins: ITransformPlugin[] = []) { - const transformerLocal = new Transformer([...builtInPlugins, ...plugins]); - transformerLocal.urlBuilder.setProvider(local, localProvider); - transformerLocal.urlBuilder.provider = local; - return transformerLocal; -} - export const transformerExport = new Transformer(); let bestProvider = transformerExport.urlBuilder.provider; transformerExport.urlBuilder.getFastestProvider().then((provider) => { @@ -49,25 +39,3 @@ export function setExportMode(offline: boolean) { transformerExport.urlBuilder.provider = bestProvider; } } - -export function getAssets(transformer: Transformer) { - const toolbarAssets = { - styles: [TOOLBAR_CSS] - .map((path) => transformer.urlBuilder.getFullUrl(path)) - .map((path) => buildCSSItem(path)), - scripts: [TOOLBAR_JS] - .map((path) => transformer.urlBuilder.getFullUrl(path)) - .map((path) => buildJSItem(path)), - }; - const baseAssets = { - scripts: baseJsPaths - .map((path) => transformer.urlBuilder.getFullUrl(path)) - .map((path) => buildJSItem(path)), - }; - let allAssets = transformer.getAssets(); - allAssets = mergeAssets(baseAssets, allAssets, toolbarAssets, { - styles: [APP_CSS].map((path) => buildCSSItem(path)), - scripts: [APP_JS].map((path) => buildJSItem(path)), - }); - return { toolbarAssets, baseAssets, allAssets }; -}