diff --git a/packages/editor/src/extensions/search-replace/search-replace.ts b/packages/editor/src/extensions/search-replace/search-replace.ts
index 6acf330ef..5419cb454 100644
--- a/packages/editor/src/extensions/search-replace/search-replace.ts
+++ b/packages/editor/src/extensions/search-replace/search-replace.ts
@@ -18,7 +18,7 @@ along with this program. If not, see .
*/
import { Extension } from "@tiptap/core";
-import { Decoration, DecorationSet } from "prosemirror-view";
+import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
import {
EditorState,
Plugin,
@@ -28,6 +28,7 @@ import {
} from "prosemirror-state";
import { SearchSettings } from "../../toolbar/stores/search-store.js";
import { tiptapKeys } from "@notesnook/common";
+import { toggleNodesUnderPos } from "../heading/index.js";
type DispatchFn = (tr: Transaction) => void;
declare module "@tiptap/core" {
@@ -295,12 +296,11 @@ export const SearchReplace = Extension.create({
)
);
- const domNode = this.editor.view.domAtPos(from).node;
- scrollIntoView(domNode);
-
this.storage.selectedIndex = nextIndex;
+ tr.setMeta("isSearching", true);
tr.setMeta("selectedIndex", nextIndex);
if (dispatch) updateView(state, dispatch);
+
return true;
},
moveToPreviousResult:
@@ -322,10 +322,8 @@ export const SearchReplace = Extension.create({
)
);
- const domNode = this.editor.view.domAtPos(from).node;
- scrollIntoView(domNode);
-
this.storage.selectedIndex = prevIndex;
+ tr.setMeta("isSearching", true);
tr.setMeta("selectedIndex", prevIndex);
if (dispatch) updateView(state, dispatch);
@@ -470,14 +468,90 @@ export const SearchReplace = Extension.create({
decorations(state) {
return key.getState(state).results;
}
+ },
+ appendTransaction: (transactions, oldState, newState) => {
+ const isSearchTransaction = transactions.find((t) =>
+ t.getMeta("isSearching")
+ );
+ const selectedResult =
+ this.storage.results?.[this.storage.selectedIndex];
+ if (!isSearchTransaction || !selectedResult) return;
+
+ const tr = newState.tr;
+
+ scrollIntoView(this.editor.view, selectedResult.from);
+ if (expandCollapsedParents(tr, selectedResult.from)) {
+ return tr;
+ }
}
})
];
}
});
-function scrollIntoView(domNode: Node) {
+function expandCollapsedParents(tr: Transaction, pos: number) {
+ try {
+ let changed = false;
+
+ const $pos = tr.doc.resolve(pos);
+
+ for (let depth = 1; depth <= $pos.depth; depth++) {
+ const node = $pos.node(depth);
+ const nodePos = $pos.before(depth);
+
+ if (
+ (node.type.name === "callout" ||
+ node.type.name === "outlineListItem") &&
+ node.attrs.collapsed
+ ) {
+ tr.setNodeAttribute(nodePos, "collapsed", false);
+ changed = true;
+ }
+
+ // expand collapsed heading that hid this node via hidden attribute
+ if (node.attrs.hidden) {
+ const parentNode = $pos.node(depth - 1);
+ const parentContentStart = depth === 1 ? 0 : $pos.before(depth - 1) + 1;
+
+ let collapsedHeadingPos = -1;
+ let collapsedHeadingLevel = -1;
+
+ parentNode.forEach((child, offset) => {
+ const childAbsPos = parentContentStart + offset;
+ if (childAbsPos >= nodePos) return;
+ if (
+ child.type.name === "heading" &&
+ child.attrs.collapsed &&
+ !child.attrs.hidden
+ ) {
+ collapsedHeadingPos = childAbsPos;
+ collapsedHeadingLevel = child.attrs.level;
+ }
+ });
+
+ if (collapsedHeadingPos !== -1) {
+ tr.setNodeAttribute(collapsedHeadingPos, "collapsed", false);
+ toggleNodesUnderPos(
+ tr,
+ collapsedHeadingPos,
+ collapsedHeadingLevel,
+ false
+ );
+ changed = true;
+ }
+ }
+ }
+
+ if (changed) tr.setMeta("preventSave", true);
+ return changed;
+ } catch (e) {
+ console.error("Error expanding collapsed parents: ", e);
+ }
+}
+
+function scrollIntoView(view: EditorView, pos: number) {
setTimeout(() => {
+ const domNode = view.domAtPos(pos).node;
if ("scrollIntoView" in domNode) {
(domNode as Element).scrollIntoView({
behavior: "instant",
diff --git a/packages/editor/src/toolbar/popups/search-replace.tsx b/packages/editor/src/toolbar/popups/search-replace.tsx
index f2c463c9e..b55a55ea7 100644
--- a/packages/editor/src/toolbar/popups/search-replace.tsx
+++ b/packages/editor/src/toolbar/popups/search-replace.tsx
@@ -25,6 +25,7 @@ import { ToolButton } from "../components/tool-button.js";
import { Editor } from "../../types.js";
import { useEditorSearchStore } from "../stores/search-store.js";
import { strings } from "@notesnook/intl";
+import { inlineDebounce } from "@notesnook/common";
export type SearchReplacePopupProps = { editor: Editor };
export function SearchReplacePopup(props: SearchReplacePopupProps) {
@@ -94,7 +95,7 @@ export function SearchReplacePopup(props: SearchReplacePopupProps) {
sx={{ p: 0, fontFamily: "monospace" }}
value={searchTerm}
onChange={(e) => {
- search(e.target.value);
+ inlineDebounce("search", () => search(e.target.value), 100);
useEditorSearchStore.setState({ searchTerm: e.target.value });
}}
onKeyDown={(e) => {