diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index 6a10cd2..e9e4a9d 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -33,6 +33,7 @@ import { import { EmbeddedView, isEmebeddedLeaf, spawnLeafView } from "./leafView"; import { around } from "monkey-around"; import { debounce } from "obsidian"; +import { EditorView } from "@codemirror/view"; type sortOrder = | "alphabetical" @@ -92,6 +93,40 @@ const DEFAULT_SETTINGS: FloatSearchSettings = { cmdkQuickCreateTitleFormat: "YYYYMMDDHHmmss", }; +/** + * Scroll matched text to the top of the main editor + * @param app - Obsidian app instance + * @param matchOffset - Character offset of the match + */ +function scrollMatchToTop(app: App, matchOffset: number): void { + setTimeout(() => { + const leaf = app.workspace.getMostRecentLeaf(); + if (!leaf) return; + + const view = leaf.view; + if (!view) return; + + const editor = (view as any).editor; + if (!editor) return; + + const cm = (editor as any).cm as EditorView; + if (!cm) { + // Fallback to Obsidian built-in API (scrolls to center) + const pos = editor.offsetToPos(matchOffset); + editor.scrollIntoView({ from: pos, to: pos }); + return; + } + + // Use CodeMirror 6 API to scroll to top + cm.dispatch({ + effects: EditorView.scrollIntoView(matchOffset, { + y: "start", + yMargin: 30, + }), + }); + }, 150); +} + const allViews: viewType[] = [ { type: "modal", @@ -285,7 +320,8 @@ export default class FloatSearchPlugin extends Plugin { initModal( state: searchState, stateSave: boolean = false, - clearQuery: boolean = false + clearQuery: boolean = false, + fromURI: boolean = false ) { if (this.modal) { this.modal.close(); @@ -306,6 +342,13 @@ export default class FloatSearchPlugin extends Plugin { { ...state, query: clearQuery ? "" : state.query } ); this.modal.open(); + + // 如果是通过URI协议打开且有查询内容,删除"建议容器" + if (fromURI && state.query) { + setTimeout(() => { + document.querySelector('.suggestion-container')?.remove(); + }, 10); + } } patchWorkspace() { @@ -917,7 +960,8 @@ export default class FloatSearchPlugin extends Plugin { current: false, }, true, - false + false, + true ); } else { await initSearchViewWithLeaf( @@ -1798,7 +1842,7 @@ class FloatSearchModal extends Modal { initContent() { const { contentEl } = this; - contentEl.onclick = (e) => { + contentEl.onclick = async (e) => { const resultElement = contentEl.getElementsByClassName( "search-results-children" )[0]; @@ -1821,7 +1865,54 @@ class FloatSearchModal extends Modal { break; } if (targetElement.classList.contains("tree-item")) { + // Get file info before closing + const fileInnerEl = targetElement?.getElementsByClassName( + "tree-item-inner" + )[0] as HTMLElement; + const innerText = fileInnerEl.innerText; + const file = this.plugin.app.metadataCache.getFirstLinkpathDest( + innerText, + "" + ); + + // Get match info + const currentView = this.searchLeaf.view as SearchView; + const item = currentView.dom.resultDomLookup.get(file); + + // Try to get match offset - find the specific clicked match + let matchOffset: number | undefined = undefined; + // Get the clicked element and find the match element + const clickedEl = e.target as HTMLElement; + // Try to get the actual match element (not file element) + const matchEl = clickedEl.closest(".search-result-file-match"); + + if (matchEl && item?.vChildren?.children) { + // Get index among siblings - filter to only match elements + const parent = matchEl.parentElement; + if (parent) { + // Filter to only .search-result-file-match elements + const siblings = Array.from(parent.children).filter( + (el) => el.classList.contains("search-result-file-match") + ); + const clickedIdx = siblings.indexOf(matchEl); + if (clickedIdx >= 0 && clickedIdx < item.vChildren.children.length) { + const child = item.vChildren.children[clickedIdx]; + const childMatch = child?.matches?.[0]; + if (Array.isArray(childMatch)) { + matchOffset = childMatch[0]; + } else if (typeof childMatch === "number") { + matchOffset = childMatch; + } + } + } + } + this.close(); + + // Scroll to top if we have a match offset + if (matchOffset !== undefined) { + scrollMatchToTop(this.plugin.app, matchOffset); + } break; } targetElement = targetElement.parentElement;