Skip to content

Commit e48f834

Browse files
committed
fix: highlight node using native svg
1 parent 4e454ba commit e48f834

File tree

5 files changed

+131
-159
lines changed

5 files changed

+131
-159
lines changed

assets/style.css

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,3 @@ body {
2323
padding-left: 4px;
2424
padding-right: 4px;
2525
}
26-
27-
.markmap-highlight-area {
28-
--mm-highlight-scale: 1;
29-
position: absolute;
30-
width: var(--mm-highlight-width);
31-
height: var(--mm-highlight-height);
32-
left: var(--mm-highlight-x);
33-
top: var(--mm-highlight-y);
34-
z-index: -1;
35-
background: #ff02;
36-
transform: scale(var(--mm-highlight-scale));
37-
}

src/app.ts

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const handlers = {
6666
if (!result) return;
6767
const { node, needRerender } = result;
6868
if (needRerender) await mm.renderData();
69-
if (node) highlightNode(node);
69+
highlightNode(node);
7070
},
7171
setCSS(data: string) {
7272
if (!style) {
@@ -96,7 +96,7 @@ document.addEventListener('click', (e) => {
9696
const href = el.getAttribute('href');
9797
if (href.startsWith('#')) {
9898
const node = findHeading(href.slice(1));
99-
if (node) highlightNode(node);
99+
highlightNode(node);
100100
} else if (!href.includes('://')) {
101101
vscode.postMessage({
102102
type: 'openFile',
@@ -128,14 +128,12 @@ toolbar.setItems([
128128
'editAsText',
129129
'export',
130130
]);
131-
const highlightEl = document.createElement('div');
132-
highlightEl.className = 'markmap-highlight-area';
131+
133132
checkTheme();
134133

135134
setTimeout(() => {
136135
toolbar.attach(mm);
137136
document.body.append(toolbar.el);
138-
checkHighlight();
139137
});
140138

141139
function checkTheme() {
@@ -208,30 +206,12 @@ function findActiveNode({
208206
return best && { node: best, needRerender };
209207
}
210208

211-
async function highlightNode(node: INode) {
209+
async function highlightNode(node?: INode) {
210+
await mm.setHighlight(node);
211+
if (!node) return;
212212
await mm[
213213
activeNodeOptions.placement === 'center' ? 'centerNode' : 'ensureVisible'
214214
](node, {
215215
bottom: 80,
216216
});
217-
const g = mm.findElement(node)?.g;
218-
const el = g?.querySelector('foreignObject');
219-
active = el && { el, node };
220-
}
221-
222-
function checkHighlight() {
223-
if (!active) {
224-
highlightEl.remove();
225-
} else {
226-
const rect = active.el.getBoundingClientRect();
227-
const padding = 4;
228-
const { width } = active.node.state.rect;
229-
const scale = (width + padding * 2) / width;
230-
highlightEl.setAttribute(
231-
'style',
232-
`--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}`,
233-
);
234-
if (!highlightEl.parentNode) document.body.append(highlightEl);
235-
}
236-
requestAnimationFrame(checkHighlight);
237217
}

src/extension.ts

Lines changed: 94 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import debounce from 'lodash.debounce';
2-
import { JSItem, type CSSItem } from 'markmap-common';
3-
import { fillTemplate } from 'markmap-render';
2+
import {
3+
buildJSItem,
4+
mergeAssets,
5+
type CSSItem,
6+
type JSItem,
7+
} from 'markmap-common';
8+
import { Transformer, builtInPlugins } from 'markmap-lib';
9+
import { baseJsPaths, fillTemplate } from 'markmap-render';
410
import { type IMarkmapJSONOptions } from 'markmap-view';
511
import {
612
CustomTextEditorProvider,
@@ -19,10 +25,10 @@ import {
1925
import { Utils } from 'vscode-uri';
2026
import localImage from './plugins/local-image';
2127
import {
22-
getAssets,
23-
getLocalTransformer,
24-
mergeAssets,
28+
ASSETS_PREFIX,
29+
appAssets,
2530
setExportMode,
31+
toolbarAssets,
2632
transformerExport,
2733
} from './util';
2834

@@ -69,44 +75,57 @@ class MarkmapEditor implements CustomTextEditorProvider {
6975
webviewPanel.webview.options = {
7076
enableScripts: true,
7177
};
72-
const resolveUrl = (relPath: string) =>
73-
webviewPanel.webview
74-
.asWebviewUri(this.resolveAssetPath(relPath))
75-
.toString();
76-
const transformerLocal = getLocalTransformer([
78+
const transformerLocal = new Transformer([
79+
...builtInPlugins,
7780
localImage((relPath) =>
7881
webviewPanel.webview
7982
.asWebviewUri(Utils.joinPath(Utils.dirname(document.uri), relPath))
8083
.toString(),
8184
),
8285
]);
83-
const { allAssets } = getAssets(transformerLocal);
84-
const resolvedAssets = {
85-
...allAssets,
86-
styles: allAssets.styles?.map((item) => {
87-
if (item.type === 'stylesheet') {
88-
return {
89-
...item,
90-
data: {
91-
href: resolveUrl(item.data.href),
92-
},
93-
};
94-
}
95-
return item;
96-
}),
97-
scripts: allAssets.scripts?.map((item) => {
98-
if (item.type === 'script' && item.data.src) {
99-
return {
100-
...item,
101-
data: {
102-
...item.data,
103-
src: resolveUrl(item.data.src),
104-
},
105-
};
106-
}
107-
return item;
108-
}),
109-
};
86+
const resolveUrl = (path: string) =>
87+
webviewPanel.webview.asWebviewUri(this.resolveAssetPath(path)).toString();
88+
const providerName = 'local';
89+
transformerLocal.urlBuilder.providers[providerName] = (path: string) =>
90+
resolveUrl(`${ASSETS_PREFIX}${path}`);
91+
transformerLocal.urlBuilder.provider = providerName;
92+
const otherAssets = mergeAssets(
93+
{
94+
scripts: baseJsPaths.map(buildJSItem),
95+
},
96+
toolbarAssets,
97+
);
98+
const resolvedAssets = mergeAssets(
99+
{
100+
styles: otherAssets.styles?.map((item) =>
101+
transformerLocal.resolveCSS(item),
102+
),
103+
scripts: otherAssets.scripts?.map((item) =>
104+
transformerLocal.resolveJS(item),
105+
),
106+
},
107+
transformerLocal.getAssets(),
108+
{
109+
styles: appAssets.styles?.map((item) =>
110+
item.type === 'stylesheet'
111+
? {
112+
...item,
113+
data: {
114+
href: resolveUrl(item.data.href),
115+
},
116+
}
117+
: item,
118+
),
119+
scripts: appAssets.scripts?.map((item) =>
120+
item.type === 'script' && item.data.src
121+
? {
122+
...item,
123+
data: { src: resolveUrl(item.data.src) },
124+
}
125+
: item,
126+
),
127+
},
128+
);
110129
webviewPanel.webview.html = fillTemplate(undefined, resolvedAssets, {
111130
baseJs: [],
112131
urlBuilder: transformerLocal.urlBuilder,
@@ -185,31 +204,46 @@ class MarkmapEditor implements CustomTextEditorProvider {
185204
};
186205
const { embedAssets } = jsonOptions as { embedAssets?: boolean };
187206
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+
const otherAssets = mergeAssets(
208+
{
209+
scripts: baseJsPaths.map(buildJSItem),
210+
},
211+
toolbarAssets,
212+
);
213+
let assets = mergeAssets(
214+
{
215+
styles: otherAssets.styles?.map((item) =>
216+
transformerExport.resolveCSS(item),
217+
),
218+
scripts: otherAssets.scripts?.map((item) =>
219+
transformerExport.resolveJS(item),
220+
),
221+
},
222+
transformerExport.getUsedAssets(features),
223+
{
224+
styles: [
225+
...(customCSS
226+
? [
227+
{
228+
type: 'style',
229+
data: customCSS,
230+
} as CSSItem,
231+
]
232+
: []),
233+
],
234+
scripts: [
235+
{
236+
type: 'iife',
237+
data: {
238+
fn: (r: typeof renderToolbar) => {
239+
setTimeout(r);
240+
},
241+
getParams: () => [renderToolbar],
207242
},
208-
getParams: () => [renderToolbar],
209243
},
210-
},
211-
],
212-
});
244+
],
245+
},
246+
);
213247
if (embedAssets) {
214248
const [styles, scripts] = await Promise.all([
215249
Promise.all(

src/postbuild.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
import { createWriteStream } from 'fs';
22
import { mkdir, stat } from 'fs/promises';
3+
import { extractAssets } from 'markmap-common';
4+
import { Transformer } from 'markmap-lib';
5+
import { baseJsPaths } from 'markmap-render';
36
import { dirname, resolve } from 'path';
47
import { Readable } from 'stream';
5-
import { ReadableStream } from 'stream/web';
68
import { finished } from 'stream/promises';
7-
import { Transformer } from 'markmap-lib';
8-
import { ASSETS_PREFIX, getAssets, localProvider } from './util';
9+
import { ReadableStream } from 'stream/web';
10+
import { ASSETS_PREFIX, localProvider, toolbarAssets } from './util';
911

1012
const providerName = 'local-hook';
1113

12-
async function fetchAssets() {
14+
async function fetchAssets(assetsDir: string) {
1315
const transformer = new Transformer();
14-
const { provider } = transformer.urlBuilder;
15-
transformer.urlBuilder.setProvider(providerName, localProvider);
16+
transformer.urlBuilder.providers[providerName] = localProvider;
1617
transformer.urlBuilder.provider = providerName;
17-
const { allAssets: assets } = getAssets(transformer);
18+
const assets = transformer.getAssets();
1819
delete transformer.urlBuilder.providers[providerName];
19-
transformer.urlBuilder.provider = provider;
20-
const paths = [
21-
...(assets.scripts?.map(
22-
(item) => (item.type === 'script' && item.data.src) || '',
23-
) || []),
24-
...(assets.styles?.map(
25-
(item) => (item.type === 'stylesheet' && item.data.href) || '',
26-
) || []),
27-
]
20+
const pluginPaths = extractAssets(assets)
2821
.filter((url) => url.startsWith(ASSETS_PREFIX))
2922
.map((url) => url.slice(ASSETS_PREFIX.length));
23+
const resources = transformer.plugins.flatMap(
24+
(plugin) => plugin.config?.resources || [],
25+
);
26+
const paths = [
27+
...baseJsPaths,
28+
...pluginPaths,
29+
...resources,
30+
...extractAssets(toolbarAssets),
31+
];
3032
const fastest = await transformer.urlBuilder.getFastestProvider();
3133
await Promise.all(
3234
paths.map((path) =>
3335
downloadAsset(
34-
resolve(ASSETS_PREFIX, path),
36+
resolve(assetsDir, path),
3537
transformer.urlBuilder.getFullUrl(path, fastest),
3638
),
3739
),
@@ -57,7 +59,7 @@ async function downloadAsset(fullPath: string, url: string) {
5759
);
5860
}
5961

60-
fetchAssets().catch((err) => {
62+
fetchAssets(ASSETS_PREFIX).catch((err) => {
6163
console.error(err);
6264
process.exitCode = 1;
6365
});

0 commit comments

Comments
 (0)