mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Skip redundant search highlight rebuilds, lower debounce to 100ms
The highlight plugin rebuilt every match's DOM range via domAtPos on every editor view update while a search was active, forcing synchronous layout on cursor moves, selection changes, and collaboration cursors. Track the built ranges and, when the result set is unchanged, only rebuild when they are actually stale — a referenced node has detached or some matches were not yet resolved to ranges. isConnected checks are cheap property reads with no layout, versus domAtPos which forces reflow, so this is strictly less work than before and skips entirely in the common case where all matches are resolved and connected. Also lower the find debounce from 250ms to 100ms for snappier feedback.
This commit is contained in:
@@ -225,7 +225,7 @@ export default function FindAndReplace({
|
||||
}) => {
|
||||
editor.commands.find(attrs);
|
||||
},
|
||||
250
|
||||
100
|
||||
),
|
||||
[editor.commands]
|
||||
);
|
||||
|
||||
@@ -490,6 +490,7 @@ export default class FindAndReplaceExtension extends Extension<FindAndReplaceOpt
|
||||
}
|
||||
}
|
||||
|
||||
this.highlightRanges = allRanges;
|
||||
CSS.highlights.set("search-results", new Highlight(...allRanges));
|
||||
if (currentRanges.length) {
|
||||
CSS.highlights.set(
|
||||
@@ -502,6 +503,7 @@ export default class FindAndReplaceExtension extends Extension<FindAndReplaceOpt
|
||||
}
|
||||
|
||||
private clearHighlights() {
|
||||
this.highlightRanges = [];
|
||||
if (!supportsHighlightAPI) {
|
||||
return;
|
||||
}
|
||||
@@ -510,6 +512,25 @@ export default class FindAndReplaceExtension extends Extension<FindAndReplaceOpt
|
||||
this.currentHighlightRange = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the highlight ranges need to be rebuilt against the live
|
||||
* DOM. The CSS Custom Highlight API holds static ranges that detach when the
|
||||
* editor re-renders its DOM without changing the doc, so highlights are stale
|
||||
* when a built range's nodes have disconnected, or when some matches have not
|
||||
* yet been resolved to ranges (e.g. inside a node view that mounts later).
|
||||
*
|
||||
* @returns whether the highlights should be rebuilt.
|
||||
*/
|
||||
private highlightsStale() {
|
||||
if (this.highlightRanges.length < this.results.length) {
|
||||
return true;
|
||||
}
|
||||
return this.highlightRanges.some(
|
||||
(range) =>
|
||||
!range.startContainer.isConnected || !range.endContainer.isConnected
|
||||
);
|
||||
}
|
||||
|
||||
private handleEscape = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.has("q")) {
|
||||
@@ -543,6 +564,8 @@ export default class FindAndReplaceExtension extends Extension<FindAndReplaceOpt
|
||||
|
||||
private currentHighlightRange?: StaticRange;
|
||||
|
||||
private highlightRanges: StaticRange[] = [];
|
||||
|
||||
get allowInReadOnly() {
|
||||
return true;
|
||||
}
|
||||
@@ -611,16 +634,23 @@ export default class FindAndReplaceExtension extends Extension<FindAndReplaceOpt
|
||||
return {
|
||||
update: (view) => {
|
||||
const generation = pluginKey.getState(view.state) as number;
|
||||
// Rebuild highlights when the results change (generation bump) or,
|
||||
// while a search is active, on any view update. The CSS Custom
|
||||
// Highlight API relies on static DOM ranges that become detached
|
||||
// when the editor re-renders its DOM — e.g. content settling after
|
||||
// sync when navigating from search results, collaboration cursors,
|
||||
// or node views mounting — none of which bump the generation. This
|
||||
// keeps the highlights tracking the live DOM, as decorations do.
|
||||
if (generation !== lastGeneration || this.searchTerm) {
|
||||
// The results changed (search ran, doc changed, fold toggled), so
|
||||
// always rebuild.
|
||||
if (generation !== lastGeneration) {
|
||||
lastGeneration = generation;
|
||||
this.updateHighlights();
|
||||
return;
|
||||
}
|
||||
// The results are unchanged, but the CSS Custom Highlight API relies
|
||||
// on static DOM ranges that become detached when the editor
|
||||
// re-renders its DOM without bumping the generation — e.g. content
|
||||
// settling after sync when navigating from search results,
|
||||
// collaboration cursors, or node views mounting. Only pay for an
|
||||
// O(n) domAtPos rebuild when the ranges have actually gone stale;
|
||||
// otherwise the live highlights are still valid and we skip the work
|
||||
// (cheap isConnected checks only, no layout).
|
||||
if (this.searchTerm && this.highlightsStale()) {
|
||||
this.updateHighlights();
|
||||
}
|
||||
},
|
||||
destroy: () => {
|
||||
|
||||
Reference in New Issue
Block a user