Skip to content

Commit

Permalink
fix: highlight node using native svg
Browse files Browse the repository at this point in the history
  • Loading branch information
gera2ld committed Jan 4, 2025
1 parent 4e454ba commit e48f834
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 159 deletions.
12 changes: 0 additions & 12 deletions assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
32 changes: 6 additions & 26 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
154 changes: 94 additions & 60 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
38 changes: 20 additions & 18 deletions src/postbuild.ts
Original file line number Diff line number Diff line change
@@ -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),
),
),
Expand All @@ -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;
});
Loading

0 comments on commit e48f834

Please sign in to comment.