From 4bad19f8f168f8780c92b7c7854799ac14e9bd66 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 17 Jan 2024 13:44:22 +0800 Subject: [PATCH 01/12] fix: cannot use last state --- manifest.json | 2 +- package.json | 50 +- src/floatSearchIndex.ts | 1534 ++++++++++++++++++++------------------- versions.json | 3 +- 4 files changed, 806 insertions(+), 783 deletions(-) diff --git a/manifest.json b/manifest.json index 42ca387..885816e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "float-search", "name": "Floating Search", - "version": "3.4.5", + "version": "3.4.6", "minAppVersion": "0.15.0", "description": "You can use search view in modal/leaf/popout window now.", "author": "Boninall", diff --git a/package.json b/package.json index b865b46..b4454a4 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,27 @@ { - "name": "float-search", - "version": "3.4.5", - "description": "You can use search view in modal/leaf/popout window now.", - "main": "main.js", - "scripts": { - "dev": "node esbuild.config.mjs", - "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", - "version": "node version-bump.mjs && git add manifest.json versions.json" - }, - "keywords": [], - "author": "Boninall", - "license": "GPL-3.0", - "devDependencies": { - "@types/node": "^16.18.50", - "@typescript-eslint/eslint-plugin": "5.29.0", - "@typescript-eslint/parser": "5.29.0", - "builtin-modules": "3.3.0", - "esbuild": "0.14.47", - "obsidian": "latest", - "tslib": "2.4.0", - "typescript": "4.7.4" - }, - "dependencies": { - "monkey-around": "^2.3.0" - } + "name": "float-search", + "version": "3.4.6", + "description": "You can use search view in modal/leaf/popout window now.", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", + "version": "node version-bump.mjs && git add manifest.json versions.json" + }, + "keywords": [], + "author": "Boninall", + "license": "GPL-3.0", + "devDependencies": { + "@types/node": "^16.18.50", + "@typescript-eslint/eslint-plugin": "5.29.0", + "@typescript-eslint/parser": "5.29.0", + "builtin-modules": "3.3.0", + "esbuild": "0.14.47", + "obsidian": "latest", + "tslib": "2.4.0", + "typescript": "4.7.4" + }, + "dependencies": { + "monkey-around": "^2.3.0" + } } diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index 5be5f21..46b09c1 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -1,891 +1,913 @@ import { - addIcon, - App, - Editor, ExtraButtonComponent, - Menu, MenuItem, - Modal, OpenViewState, PaneType, - Plugin, SearchView, setIcon, - TAbstractFile, - TFile, ViewStateResult, - Workspace, - WorkspaceContainer, WorkspaceItem, - WorkspaceLeaf + addIcon, + App, + Editor, ExtraButtonComponent, + Menu, MenuItem, + Modal, OpenViewState, PaneType, + Plugin, Scope, SearchView, setIcon, + TAbstractFile, + TFile, ViewStateResult, + Workspace, + WorkspaceContainer, WorkspaceItem, + WorkspaceLeaf } from 'obsidian'; import { EmbeddedView, isEmebeddedLeaf, spawnLeafView } from "./leafView"; import { around } from "monkey-around"; type sortOrder = - "alphabetical" - | "alphabeticalReverse" - | "byModifiedTime" - | "byModifiedTimeReverse" - | "byCreatedTime" - | "byCreatedTimeReverse"; + "alphabetical" + | "alphabeticalReverse" + | "byModifiedTime" + | "byModifiedTimeReverse" + | "byCreatedTime" + | "byCreatedTimeReverse"; type searchType = "modal" | "sidebar" | PaneType; interface viewType { - type: searchType; - icon: string; + type: searchType; + icon: string; } interface searchState { - collapseAll?: boolean; - explainSearch?: boolean; - extraContext?: boolean; - matchingCase?: boolean; - query: string; - sortOrder?: sortOrder; - current?: boolean; + collapseAll?: boolean; + explainSearch?: boolean; + extraContext?: boolean; + matchingCase?: boolean; + query: string; + sortOrder?: sortOrder; + current?: boolean; } interface FloatSearchSettings { - searchViewState: searchState; + searchViewState: searchState; } const DEFAULT_SETTINGS: FloatSearchSettings = { - searchViewState: { - collapseAll: false, - explainSearch: false, - extraContext: false, - matchingCase: false, - query: "", - sortOrder: "alphabetical", - } -} + searchViewState: { + collapseAll: false, + explainSearch: false, + extraContext: false, + matchingCase: false, + query: "", + sortOrder: "alphabetical", + } +}; const allViews: viewType[] = [{ - type: "modal", - icon: "square-equal" + type: "modal", + icon: "square-equal" }, { - type: "sidebar", - icon: "panel-left-inactive" + type: "sidebar", + icon: "panel-left-inactive" }, { - type: "split", - icon: "split-square-horizontal" + type: "split", + icon: "split-square-horizontal" }, { - type: "tab", - icon: "panel-top" + type: "tab", + icon: "panel-top" }, { - type: "window", - icon: "app-window" + type: "window", + icon: "app-window" }]; const initSearchViewWithLeaf = async (app: App, type: PaneType | 'sidebar', state?: searchState) => { - const leaf = type === 'sidebar' ? app.workspace.getLeftLeaf(false) : app.workspace.getLeaf(type); - leaf.setPinned(type !== 'sidebar'); - await leaf.setViewState({ - type: "search", - active: true, - state: state - }); - setTimeout(() => { - const inputEl = leaf.containerEl.getElementsByTagName("input")[0]; - inputEl.focus(); - }, 0); -} + const leaf = type === 'sidebar' ? app.workspace.getLeftLeaf(false) : app.workspace.getLeaf(type); + leaf.setPinned(type !== 'sidebar'); + await leaf.setViewState({ + type: "search", + active: true, + state: state + }); + + setTimeout(() => { + const inputEl = leaf.containerEl.getElementsByTagName("input")[0]; + inputEl.focus(); + }, 0); +}; export default class FloatSearchPlugin extends Plugin { - settings: FloatSearchSettings; - private state: any; - private modal: FloatSearchModal; + settings: FloatSearchSettings; + private state: any; + private modal: FloatSearchModal; - private applyStateDebounceTimer = 0; - private applySettingsDebounceTimer = 0; + private applyStateDebounceTimer = 0; + private applySettingsDebounceTimer = 0; - public applySettingsUpdate() { - clearTimeout(this.applySettingsDebounceTimer); - this.applySettingsDebounceTimer = window.setTimeout(async () => { - await this.saveSettings(); - }, 1000); - } + public applySettingsUpdate() { + clearTimeout(this.applySettingsDebounceTimer); + this.applySettingsDebounceTimer = window.setTimeout(async () => { + await this.saveSettings(); + }, 1000); + } - private applyStateUpdate() { - this.applyStateDebounceTimer = window.setTimeout(() => { - clearTimeout(this.applyStateDebounceTimer); - this.state = { - ...this.state, - query: "", - }; - }, 30000); - } + private applyStateUpdate() { + this.applyStateDebounceTimer = window.setTimeout(() => { + clearTimeout(this.applyStateDebounceTimer); + this.state = { + ...this.state, + query: "", + }; + }, 30000); + } - async onload() { - await this.loadSettings(); - this.initState(); - this.registerIcons(); + async onload() { + await this.loadSettings(); + this.initState(); + this.registerIcons(); - this.patchWorkspace(); - this.patchWorkspaceLeaf(); - this.patchSearchView(); + this.patchWorkspace(); + this.patchWorkspaceLeaf(); + this.patchSearchView(); - this.registerObsidianURIHandler(); - this.registerObsidianCommands(); - this.registerEditorMenuHandler(); - this.registerContextMenuHandler(); + this.registerObsidianURIHandler(); + this.registerObsidianCommands(); + this.registerEditorMenuHandler(); + this.registerContextMenuHandler(); - this.addRibbonIcon('search', 'Search Obsidian In Modal', () => this.initModal(this.state, true, true)); - } + this.addRibbonIcon('search', 'Search Obsidian In Modal', () => this.initModal(this.state, true, true)); + } - onunload() { - this.state = undefined; - this.modal?.close(); - } + onunload() { + this.state = undefined; + this.modal?.close(); + } - registerIcons() { - addIcon('panel-left-inactive', ``) - addIcon('app-window', ``); - addIcon('panel-top', ``); - addIcon('square-equal', ``); - } + registerIcons() { + addIcon('panel-left-inactive', ``); + addIcon('app-window', ``); + addIcon('panel-top', ``); + addIcon('square-equal', ``); + } - initState() { - this.state = this.settings.searchViewState as searchState; - } + initState() { + this.state = this.settings.searchViewState as searchState; + } - initModal(state: searchState, stateSave: boolean = false, clearQuery: boolean = false) { - this.modal = new FloatSearchModal((state) => { - this.state = state; - if (stateSave) this.applyStateUpdate(); - this.settings.searchViewState = state as searchState; - this.applySettingsUpdate(); - }, this, {...state, query: clearQuery ? "" : state.query}); - this.modal.open(); - } + initModal(state: searchState, stateSave: boolean = false, clearQuery: boolean = false) { + this.modal = new FloatSearchModal((state) => { + this.state = state; + if (stateSave) this.applyStateUpdate(); + this.settings.searchViewState = state as searchState; + this.applySettingsUpdate(); + }, this, {...state, query: clearQuery ? "" : state.query}); + this.modal.open(); + } - patchWorkspace() { - let layoutChanging = false; - const uninstaller = around(Workspace.prototype, { - getLeaf: (next) => - function (...args) { - const activeLeaf = this.activeLeaf; - if (activeLeaf) { - const fsCtnEl = (activeLeaf.parent.containerEl as HTMLElement).parentElement; - if (fsCtnEl?.hasClass("fs-content")) { - if (activeLeaf.view.getViewType() === "markdown") { - return activeLeaf; - } - const newLeaf = app.workspace.getUnpinnedLeaf(); - if (newLeaf) { - this.setActiveLeaf(newLeaf); - } - } - return next.call(this, ...args); - } - return next.call(this, ...args); - }, - changeLayout(old) { - return async function (workspace: unknown) { - layoutChanging = true; - try { - // Don't consider hover popovers part of the workspace while it's changing - await old.call(this, workspace); - } finally { - layoutChanging = false; - } - }; - }, - iterateLeaves(old) { - type leafIterator = (item: WorkspaceLeaf) => boolean | void; - return function (arg1, arg2) { - // Fast exit if desired leaf found - if (old.call(this, arg1, arg2)) return true; + patchWorkspace() { + let layoutChanging = false; + const uninstaller = around(Workspace.prototype, { + getLeaf: (next) => + function (...args) { + const activeLeaf = this.activeLeaf; + if (activeLeaf) { + const fsCtnEl = (activeLeaf.parent.containerEl as HTMLElement).parentElement; + if (fsCtnEl?.hasClass("fs-content")) { + if (activeLeaf.view.getViewType() === "markdown") { + return activeLeaf; + } + const newLeaf = app.workspace.getUnpinnedLeaf(); + if (newLeaf) { + this.setActiveLeaf(newLeaf); + } + } + return next.call(this, ...args); + } + return next.call(this, ...args); + }, + changeLayout(old) { + return async function (workspace: unknown) { + layoutChanging = true; + try { + // Don't consider hover popovers part of the workspace while it's changing + await old.call(this, workspace); + } finally { + layoutChanging = false; + } + }; + }, + iterateLeaves(old) { + type leafIterator = (item: WorkspaceLeaf) => boolean | void; + return function (arg1, arg2) { + // Fast exit if desired leaf found + if (old.call(this, arg1, arg2)) return true; - // Handle old/new API parameter swap - const cb: leafIterator = (typeof arg1 === "function" ? arg1 : arg2) as leafIterator; - const parent: WorkspaceItem = (typeof arg1 === "function" ? arg2 : arg1) as WorkspaceItem; + // Handle old/new API parameter swap + const cb: leafIterator = (typeof arg1 === "function" ? arg1 : arg2) as leafIterator; + const parent: WorkspaceItem = (typeof arg1 === "function" ? arg2 : arg1) as WorkspaceItem; - if (!parent) return false; // <- during app startup, rootSplit can be null - if (layoutChanging) return false; // Don't let HEs close during workspace change + if (!parent) return false; // <- during app startup, rootSplit can be null + if (layoutChanging) return false; // Don't let HEs close during workspace change - // 0.14.x doesn't have WorkspaceContainer; this can just be an instanceof check once 15.x is mandatory: - if (parent === app.workspace.rootSplit || (WorkspaceContainer && parent instanceof WorkspaceContainer)) { - for (const popover of EmbeddedView.popoversForWindow((parent as WorkspaceContainer).win)) { - // Use old API here for compat w/0.14.x - if (old.call(this, cb, popover.rootSplit)) return true; - } - } - return false; - }; - }, - onDragLeaf(old) { - return function (event: MouseEvent, leaf: WorkspaceLeaf) { - return old.call(this, event, leaf); - }; - }, - pushUndoHistory(old: any) { - return function (leaf: WorkspaceLeaf, id: string, ...args: any[]) { - const viewState = leaf.getViewState(); - if (viewState.type === "search") { - return; - } - return old.call(this, leaf, id, ...args); - }; - } - }); - this.register(uninstaller); - } + // 0.14.x doesn't have WorkspaceContainer; this can just be an instanceof check once 15.x is mandatory: + if (parent === app.workspace.rootSplit || (WorkspaceContainer && parent instanceof WorkspaceContainer)) { + for (const popover of EmbeddedView.popoversForWindow((parent as WorkspaceContainer).win)) { + // Use old API here for compat w/0.14.x + if (old.call(this, cb, popover.rootSplit)) return true; + } + } + return false; + }; + }, + onDragLeaf(old) { + return function (event: MouseEvent, leaf: WorkspaceLeaf) { + return old.call(this, event, leaf); + }; + }, + pushUndoHistory(old: any) { + return function (leaf: WorkspaceLeaf, id: string, ...args: any[]) { + const viewState = leaf.getViewState(); + if (viewState.type === "search") { + return; + } + return old.call(this, leaf, id, ...args); + }; + } + }); + this.register(uninstaller); + } - // Used for patch workspaceleaf pinned behaviors - patchWorkspaceLeaf() { - this.register( - around(WorkspaceLeaf.prototype, { - getRoot(old) { - return function () { - const top = old.call(this); - return top?.getRoot === this.getRoot ? top : top?.getRoot(); - }; - }, - setPinned(old) { - return function (pinned: boolean) { - old.call(this, pinned); - if (isEmebeddedLeaf(this) && !pinned) this.setPinned(true); - } - }, - openFile(old) { - return function (file: TFile, openState?: OpenViewState) { - if (isEmebeddedLeaf(this)) { - setTimeout( - around(Workspace.prototype, { - recordMostRecentOpenedFile(old) { - return function (_file: TFile) { - // Don't update the quick switcher's recent list - if (_file !== file) { - return old.call(this, _file); - } - }; - }, - }), - 1, - ); - const recentFiles = this.app.plugins.plugins["recent-files-obsidian"]; - if (recentFiles) { - setTimeout( - around(recentFiles, { - shouldAddFile(old) { - return function (_file: TFile) { - // Don't update the Recent Files plugin - return _file !== file && old.call(this, _file); - }; - }, - }), - 1, - ); - } + // Used for patch workspaceleaf pinned behaviors + patchWorkspaceLeaf() { + this.register( + around(WorkspaceLeaf.prototype, { + getRoot(old) { + return function () { + const top = old.call(this); + return top?.getRoot === this.getRoot ? top : top?.getRoot(); + }; + }, + setPinned(old) { + return function (pinned: boolean) { + old.call(this, pinned); + if (isEmebeddedLeaf(this) && !pinned) this.setPinned(true); + }; + }, + openFile(old) { + return function (file: TFile, openState?: OpenViewState) { + if (isEmebeddedLeaf(this)) { + setTimeout( + around(Workspace.prototype, { + recordMostRecentOpenedFile(old) { + return function (_file: TFile) { + // Don't update the quick switcher's recent list + if (_file !== file) { + return old.call(this, _file); + } + }; + }, + }), + 1, + ); + const recentFiles = this.app.plugins.plugins["recent-files-obsidian"]; + if (recentFiles) { + setTimeout( + around(recentFiles, { + shouldAddFile(old) { + return function (_file: TFile) { + // Don't update the Recent Files plugin + return _file !== file && old.call(this, _file); + }; + }, + }), + 1, + ); + } - } + } - const view = old.call(this, file, openState); - setTimeout(() => { - const fsCtnEl = (this.parent.containerEl as HTMLElement).parentElement; - if (!(fsCtnEl?.classList.contains("fs-content"))) return; - if (file.extension != "canvas") return; + const view = old.call(this, file, openState); + setTimeout(() => { + const fsCtnEl = (this.parent.containerEl as HTMLElement).parentElement; + if (!(fsCtnEl?.classList.contains("fs-content"))) return; + if (file.extension != "canvas") return; - const canvas = this.view.canvas; - setTimeout(() => { - if (canvas && openState?.eState?.match) { - let node = canvas.data.nodes?.find((e: any) => e.text === openState.eState.match.content); - node = canvas.nodes.get(node.id); + const canvas = this.view.canvas; + setTimeout(() => { + if (canvas && openState?.eState?.match) { + let node = canvas.data.nodes?.find((e: any) => e.text === openState.eState.match.content); + node = canvas.nodes.get(node.id); - canvas.selectOnly(node); - canvas.zoomToSelection(); - } - }, 20); - }, 1); + canvas.selectOnly(node); + canvas.zoomToSelection(); + } + }, 20); + }, 1); - return view; - } - }, - }), - ); - } + return view; + }; + }, + }), + ); + } - patchSearchView() { - const updateCurrentState = (state: searchState) => { - this.state = state; - this.settings.searchViewState = state as searchState; - this.applySettingsUpdate(); - } + patchSearchView() { + const updateCurrentState = (state: searchState) => { + this.state = state; + this.settings.searchViewState = state as searchState; + this.applySettingsUpdate(); + }; - const checkCurrentViewType = (leaf: WorkspaceLeaf) => { - const isModal = document.querySelector('.float-search-modal') !== null; - const currentLeafRoot = leaf.getRoot(); - if (currentLeafRoot?.side && (currentLeafRoot?.side === "left" || currentLeafRoot?.side === 'right')) return "sidebar"; - if (leaf.getContainer()?.type === "window") return "window"; - return isModal ? "modal" : "split"; - } + const checkCurrentViewType = (leaf: WorkspaceLeaf) => { + const isModal = document.querySelector('.float-search-modal') !== null; + const currentLeafRoot = leaf.getRoot(); + if (currentLeafRoot?.side && (currentLeafRoot?.side === "left" || currentLeafRoot?.side === 'right')) return "sidebar"; + if (leaf.getContainer()?.type === "window") return "window"; + return isModal ? "modal" : "split"; + }; - const initViewMenu = (menu: Menu, current: searchType, originLeaf?: WorkspaceLeaf) => { - menu.dom.toggleClass("float-search-view-menu", true); - let availableViews = allViews.filter((view) => { - if (current === "split") { - return view.type !== "tab"; - } else { - return view.type !== current; - } - }); - for (const view of availableViews) { - menu.addItem((item: MenuItem) => { - item.setTitle(`${view.type} view`).setIcon(`${view.icon}`).onClick(async () => { - if (view.type === "modal") { - originLeaf?.detach(); - setTimeout(() => { - this.initModal(this.state, true, false); - }, 10); - } else if (view.type === "sidebar") { - await initSearchViewWithLeaf(this.app, view.type, this.state); - } else { - if (current === "window") { - originLeaf?.detach(); - setTimeout(async () => { - await initSearchViewWithLeaf(this.app, <"tab" | "split">view.type, this.state); - }, 10); - } else { - await initSearchViewWithLeaf(this.app, view.type, this.state); - } + const initViewMenu = (menu: Menu, current: searchType, originLeaf?: WorkspaceLeaf) => { + menu.dom.toggleClass("float-search-view-menu", true); + let availableViews = allViews.filter((view) => { + if (current === "split") { + return view.type !== "tab"; + } else { + return view.type !== current; + } + }); + for (const view of availableViews) { + menu.addItem((item: MenuItem) => { + item.setTitle(`${view.type} view`).setIcon(`${view.icon}`).onClick(async () => { + if (view.type === "modal") { + originLeaf?.detach(); + setTimeout(() => { + this.initModal(this.state, true, false); + }, 10); + } else if (view.type === "sidebar") { + await initSearchViewWithLeaf(this.app, view.type, this.state); + } else { + if (current === "window") { + originLeaf?.detach(); + setTimeout(async () => { + await initSearchViewWithLeaf(this.app, <"tab" | "split">view.type, this.state); + }, 10); + } else { + await initSearchViewWithLeaf(this.app, view.type, this.state); + } - } - if (current === "modal") { - this.modal.close(); - } else { - originLeaf?.detach(); - } - }); - }); - } - return menu; - } + } + if (current === "modal") { + this.modal.close(); + } else { + originLeaf?.detach(); + } + }); + }); + } + return menu; + }; - const patchSearch = () => { - const searchView = this.app.workspace.getLeavesOfType("search")[0]?.view as any; + const patchSearch = () => { + const searchView = this.app.workspace.getLeavesOfType("search")[0]?.view as any; - if (!searchView) return false; + if (!searchView) return false; - const searchViewConstructor = searchView.constructor; + const searchViewConstructor = searchView.constructor; - this.register( - around(searchViewConstructor.prototype, { - onOpen(old) { - return function () { - old.call(this); - const viewSwitchEl = createDiv({cls: "float-search-view-switch"}); - const targetEl = this.filterSectionToggleEl; - const viewSwitchButton = new ExtraButtonComponent(viewSwitchEl); - viewSwitchButton.setIcon('layout-template').setTooltip("Switch to File View"); - viewSwitchButton.onClick(() => { - const currentType = checkCurrentViewType(this.leaf); - const layoutMenu = initViewMenu(new Menu(), currentType, this.leaf); - const viewSwitchButtonPos = viewSwitchEl.getBoundingClientRect(); - layoutMenu.showAtPosition({x: viewSwitchButtonPos.x, y: viewSwitchButtonPos.y + 30}); - }); - targetEl.parentElement.insertBefore(viewSwitchEl, targetEl); - } - }, - startSearch(old) { - return function () { - old.call(this); - const viewState = this.getState(); - updateCurrentState({ - ...viewState, - query: this.searchComponent.getValue(), - }); - } - } - }) - ); - searchView.leaf?.rebuildView(); - console.log("Metadata-Style: all property view get patched"); - return true; - }; - this.app.workspace.onLayoutReady(() => { - if (!patchSearch()) { - const evt = this.app.workspace.on("layout-change", () => { - patchSearch() && this.app.workspace.offref(evt); - }); - this.registerEvent(evt); - } - }); - } + this.register( + around(searchViewConstructor.prototype, { + onOpen(old) { + return function () { + old.call(this); + const viewSwitchEl = createDiv({cls: "float-search-view-switch"}); + const targetEl = this.filterSectionToggleEl; + const viewSwitchButton = new ExtraButtonComponent(viewSwitchEl); + viewSwitchButton.setIcon('layout-template').setTooltip("Switch to File View"); + viewSwitchButton.onClick(() => { + const currentType = checkCurrentViewType(this.leaf); + const layoutMenu = initViewMenu(new Menu(), currentType, this.leaf); + const viewSwitchButtonPos = viewSwitchEl.getBoundingClientRect(); + layoutMenu.showAtPosition({x: viewSwitchButtonPos.x, y: viewSwitchButtonPos.y + 30}); + }); + targetEl.parentElement.insertBefore(viewSwitchEl, targetEl); + }; + }, + startSearch(old) { + return function () { + old.call(this); + const viewState = this.getState(); + updateCurrentState({ + ...viewState, + query: this.searchComponent.getValue(), + }); + }; + } + }) + ); + searchView.leaf?.rebuildView(); + console.log("Metadata-Style: all property view get patched"); + return true; + }; + this.app.workspace.onLayoutReady(() => { + if (!patchSearch()) { + const evt = this.app.workspace.on("layout-change", () => { + patchSearch() && this.app.workspace.offref(evt); + }); + this.registerEvent(evt); + } + }); + } - registerObsidianURIHandler() { - this.registerObsidianProtocolHandler("fs", (path) => this.initModal({ - ...this.state, - query: path.query, - current: false - }, true, true)); - } + registerObsidianURIHandler() { + this.registerObsidianProtocolHandler("fs", (path) => this.initModal({ + ...this.state, + query: path.query, + current: false + }, true, true)); + } - private createCommand(options: { - id: string; - name: string; - queryBuilder: (file: any) => string; - }): void { - this.addCommand({ - id: options.id, - name: options.name, - checkCallback: (checking: boolean) => { - const activeLeaf = this.app.workspace.activeLeaf; - if (!activeLeaf) return; + private createCommand(options: { + id: string; + name: string; + queryBuilder: (file: any) => string; + }): void { + this.addCommand({ + id: options.id, + name: options.name, + checkCallback: (checking: boolean) => { + const activeLeaf = this.app.workspace.activeLeaf; + if (!activeLeaf) return; - const viewType = activeLeaf.view.getViewType(); - if (viewType === "markdown" || viewType === "canvas") { - if (!checking) { - const currentFile = activeLeaf.view.file; - const query = options.queryBuilder(currentFile); - this.initModal({...this.state, query, current: true}, true, false); - } - return true; - } - } - }); - } + const viewType = activeLeaf.view.getViewType(); + if (viewType === "markdown" || viewType === "canvas") { + if (!checking) { + const currentFile = activeLeaf.view.file; + const query = options.queryBuilder(currentFile); + this.initModal({...this.state, query, current: true}, true, false); + } + return true; + } + } + }); + } - registerObsidianCommands() { - this.addCommand({ - id: 'search-obsidian-globally', - name: 'Search Obsidian Globally', - callback: () => this.initModal({...this.state, query: "", current: false}, false, true) - }); + registerObsidianCommands() { + this.addCommand({ + id: 'search-obsidian-globally', + name: 'Search Obsidian Globally', + callback: () => this.initModal({...this.state, query: "", current: false}, false, true) + }); - this.addCommand({ - id: 'search-obsidian-globally-state', - name: 'Search Obsidian Globally (With Last State)', - callback: () => this.initModal({...this.state, query: "", current: false}, true, false) - }); + this.addCommand({ + id: 'search-obsidian-globally-state', + name: 'Search Obsidian Globally (With Last State)', + callback: () => this.initModal({...this.state, query: this.state.query, current: false}, true, false) + }); - this.createCommand({ - id: 'search-in-backlink', - name: 'Search In Backlink Of Current File', - queryBuilder: (file) => { - return " /\\[\\[" + (file.extension === "canvas" ? file.name : file.basename) + "(\\|[^\\]]*)?\\]\\]/"; - } - }); + this.createCommand({ + id: 'search-in-backlink', + name: 'Search In Backlink Of Current File', + queryBuilder: (file) => { + return " /\\[\\[" + (file.extension === "canvas" ? file.name : file.basename) + "(\\|[^\\]]*)?\\]\\]/"; + } + }); - this.createCommand({ - id: 'search-in-current-file', - name: 'Search In Current File', - queryBuilder: (file) => { - return " path:" + `"${file.path}"`; - } - }); + this.createCommand({ + id: 'search-in-current-file', + name: 'Search In Current File', + queryBuilder: (file) => { + return " path:" + `"${file.path}"`; + } + }); - for (const type of ['split', 'tab', 'window'] as PaneType[]) { - this.addCommand({ - id: `open-search-view-${type}`, - name: `Open Search View (${type})`, - callback: async () => initSearchViewWithLeaf(this.app, type) - }); - } - } + for (const type of ['split', 'tab', 'window'] as PaneType[]) { + this.addCommand({ + id: `open-search-view-${type}`, + name: `Open Search View (${type})`, + callback: async () => { + const existingLeaf = this.app.workspace.getLeavesOfType("search"); + switch (type) { + case "window": + const isExistingWindowLeaf = existingLeaf.find((leaf) => leaf.parentSplit.parent.type === "window"); + if (isExistingWindowLeaf) { + this.app.workspace.revealLeaf(isExistingWindowLeaf); + return; + } + await initSearchViewWithLeaf(this.app, type); + break; + case "tab": + case "split": + const isExistingLeaf = existingLeaf.find((leaf) => !leaf.parentSplit.parent.side); + if (isExistingLeaf) { + this.app.workspace.revealLeaf(isExistingLeaf); + return; + } + await initSearchViewWithLeaf(this.app, type); + break; + } + } + }); + } + } - registerEditorMenuHandler() { - this.registerEvent( - this.app.workspace.on('editor-menu', (menu: Menu, editor: Editor) => { - if (!editor) { - return; - } - if (editor.getSelection().length === 0) { - return; - } - const selection = editor.getSelection().trim(); - let searchWord = selection; + registerEditorMenuHandler() { + this.registerEvent( + this.app.workspace.on('editor-menu', (menu: Menu, editor: Editor) => { + if (!editor) { + return; + } + if (editor.getSelection().length === 0) { + return; + } + const selection = editor.getSelection().trim(); + let searchWord = selection; - if (selection.length > 8) { - searchWord = selection.substring(0, 3) + "..." + selection.substring(selection.length - 3, selection.length); - } else { - searchWord = selection; - } + if (selection.length > 8) { + searchWord = selection.substring(0, 3) + "..." + selection.substring(selection.length - 3, selection.length); + } else { + searchWord = selection; + } - menu.addItem((item) => { - // Add sub menu - item.setTitle('Search "' + searchWord + '"' + " in Float Search").setIcon("search") - .onClick(() => this.initModal({...this.state, query: selection, current: false}, true, false)) - }) - })) - } + menu.addItem((item) => { + // Add sub menu + item.setTitle('Search "' + searchWord + '"' + " in Float Search").setIcon("search") + .onClick(() => this.initModal({...this.state, query: selection, current: false}, true, false)); + }); + })); + } - registerContextMenuHandler() { - this.registerEvent( - this.app.workspace.on("file-menu", (menu: Menu, file: TAbstractFile, source: string, leaf?: WorkspaceLeaf) => { - const popover = leaf ? EmbeddedView.forLeaf(leaf) : undefined; - if (file instanceof TFile && !popover && !leaf) { - menu.addItem(item => { - item - .setIcon("popup-open") - .setTitle("Open in Float Preview") - .onClick(async () => { - if (this.modal) { - await this.modal.initFileView(file, undefined); + registerContextMenuHandler() { + this.registerEvent( + this.app.workspace.on("file-menu", (menu: Menu, file: TAbstractFile, source: string, leaf?: WorkspaceLeaf) => { + const popover = leaf ? EmbeddedView.forLeaf(leaf) : undefined; + if (file instanceof TFile && !popover && !leaf) { + menu.addItem(item => { + item + .setIcon("popup-open") + .setTitle("Open in Float Preview") + .onClick(async () => { + if (this.modal) { + await this.modal.initFileView(file, undefined); - return; - } + return; + } - this.initModal({...this.state, current: false}, true, true); - setTimeout(async () => { - await this.modal.initFileView(file, undefined); - }, 20); - }) - .setSection?.("open"); - }); - } - }), - ); - } + this.initModal({...this.state, current: false}, true, true); + setTimeout(async () => { + await this.modal.initFileView(file, undefined); + }, 20); + }) + .setSection?.("open"); + }); + } + }), + ); + } - async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - } + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } - async saveSettings() { - await this.saveData(this.settings); - } + async saveSettings() { + await this.saveData(this.settings); + } } class FloatSearchModal extends Modal { - private readonly plugin: FloatSearchPlugin; - private searchEmbeddedView: EmbeddedView; - private fileEmbeddedView: EmbeddedView; + private readonly plugin: FloatSearchPlugin; + private searchEmbeddedView: EmbeddedView; + private fileEmbeddedView: EmbeddedView; - searchLeaf: WorkspaceLeaf; - fileLeaf: WorkspaceLeaf | undefined; + searchLeaf: WorkspaceLeaf; + fileLeaf: WorkspaceLeaf | undefined; - private cb: (state: any) => void; - private state: any; + private cb: (state: any) => void; + private state: any; - private fileState: any; + private fileState: any; - private searchCtnEl: HTMLElement; - private instructionsEl: HTMLElement; - private fileEl: HTMLElement; - private viewType: string; + private searchCtnEl: HTMLElement; + private instructionsEl: HTMLElement; + private fileEl: HTMLElement; + private viewType: string; - constructor(cb: (state: any) => void, plugin: FloatSearchPlugin, state: any, viewType: string = "search") { - super(plugin.app); - this.plugin = plugin; - this.cb = cb; - this.state = state; - this.viewType = viewType; - } + constructor(cb: (state: any) => void, plugin: FloatSearchPlugin, state: any, viewType: string = "search") { + super(plugin.app); + this.plugin = plugin; + this.cb = cb; + this.state = state; + this.viewType = viewType; + } - async onOpen() { - const {contentEl, containerEl, modalEl} = this; + async onOpen() { + const {contentEl, containerEl, modalEl} = this; - this.searchCtnEl = contentEl.createDiv({cls: "float-search-modal-search-ctn"}); - this.instructionsEl = modalEl.createDiv({cls: "float-search-modal-instructions"}); + this.searchCtnEl = contentEl.createDiv({cls: "float-search-modal-search-ctn"}); + this.instructionsEl = modalEl.createDiv({cls: "float-search-modal-instructions"}); - this.initInstructions(this.instructionsEl); - this.initCss(contentEl, modalEl, containerEl); - await this.initSearchView(this.searchCtnEl); - this.initInput(); - this.initContent(); - } + this.initInstructions(this.instructionsEl); + this.initCss(contentEl, modalEl, containerEl); + await this.initSearchView(this.searchCtnEl); + this.initInput(); + this.initContent(); + } - onClose() { - const {contentEl} = this; + onClose() { + const {contentEl} = this; - this.cb(this.searchLeaf.view.getState()); + this.cb(this.searchLeaf.view.getState()); - this.searchLeaf.detach(); - this.fileLeaf?.detach(); - this.searchEmbeddedView.unload(); - this.fileEmbeddedView?.unload(); - contentEl.empty(); - } + this.searchLeaf.detach(); + this.fileLeaf?.detach(); + this.searchEmbeddedView.unload(); + this.fileEmbeddedView?.unload(); + contentEl.empty(); + } - initInstructions(instructionsEl: HTMLElement) { - const navigateInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-navigate"}); - const collapseInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-collapse"}); - const enterInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-enter"}); - const altEnterInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-alt-enter"}); + initInstructions(instructionsEl: HTMLElement) { + const navigateInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-navigate"}); + const collapseInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-collapse"}); + const enterInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-enter"}); + const altEnterInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-alt-enter"}); - const tabInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-tab"}); - const switchInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-switch"}); + const tabInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-tab"}); + const switchInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-switch"}); - const navigateIconEl = navigateInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); - const navigateTextEl = navigateInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); - navigateIconEl.setText("↑↓"); - navigateTextEl.setText("Navigate"); + const navigateIconEl = navigateInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const navigateTextEl = navigateInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + navigateIconEl.setText("↑↓"); + navigateTextEl.setText("Navigate"); - const collapseIconEl = collapseInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); - const collapseTextEl = collapseInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); - collapseIconEl.setText("Shift+↑↓"); - collapseTextEl.setText("Collapse/Expand"); + const collapseIconEl = collapseInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const collapseTextEl = collapseInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + collapseIconEl.setText("Shift+↑↓"); + collapseTextEl.setText("Collapse/Expand"); - const enterIconEl = enterInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); - const enterTextEl = enterInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); - enterIconEl.setText("↵"); - enterTextEl.setText("Open in background"); + const enterIconEl = enterInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const enterTextEl = enterInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + enterIconEl.setText("↵"); + enterTextEl.setText("Open in background"); - const altEnterIconEl = altEnterInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); - const altEnterTextEl = altEnterInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); - altEnterIconEl.setText("Alt+↵"); - altEnterTextEl.setText("Open File and Close"); + const altEnterIconEl = altEnterInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const altEnterTextEl = altEnterInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + altEnterIconEl.setText("Alt+↵"); + altEnterTextEl.setText("Open File and Close"); - const tabIconEl = tabInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); - const tabTextEl = tabInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); - tabIconEl.setText("Tab/Shift+Tab"); - tabTextEl.setText("Preview/Close Preview"); + const tabIconEl = tabInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const tabTextEl = tabInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + tabIconEl.setText("Tab/Shift+Tab"); + tabTextEl.setText("Preview/Close Preview"); - const switchIconEl = switchInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); - const switchTextEl = switchInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); - switchIconEl.setText("Ctrl+G"); - switchTextEl.setText("Switch Between Search and File View"); + const switchIconEl = switchInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const switchTextEl = switchInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + switchIconEl.setText("Ctrl+G"); + switchTextEl.setText("Switch Between Search and File View"); - const clickInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-click"}); - const clickIconEl = clickInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); - const clickTextEl = clickInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); - clickIconEl.setText("Alt+Click"); - clickTextEl.setText("Close Modal While In File View"); - } + const clickInstructionsEl = instructionsEl.createDiv({cls: "float-search-modal-instructions-click"}); + const clickIconEl = clickInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const clickTextEl = clickInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + clickIconEl.setText("Alt+Click"); + clickTextEl.setText("Close Modal While In File View"); + } - initCss(contentEl: HTMLElement, modalEl: HTMLElement, containerEl: HTMLElement) { - contentEl.classList.add("float-search-modal-content"); - modalEl.classList.add("float-search-modal"); - containerEl.classList.add("float-search-modal-container"); - } + initCss(contentEl: HTMLElement, modalEl: HTMLElement, containerEl: HTMLElement) { + contentEl.classList.add("float-search-modal-content"); + modalEl.classList.add("float-search-modal"); + containerEl.classList.add("float-search-modal-container"); + } - async initSearchView(contentEl: HTMLElement) { - const [createdLeaf, embeddedView] = spawnLeafView(this.plugin, contentEl); - this.searchLeaf = createdLeaf; - this.searchEmbeddedView = embeddedView; + async initSearchView(contentEl: HTMLElement) { + const [createdLeaf, embeddedView] = spawnLeafView(this.plugin, contentEl); + this.searchLeaf = createdLeaf; + this.searchEmbeddedView = embeddedView; - this.searchLeaf.setPinned(true); - await this.searchLeaf.setViewState({ - type: "search", - }); + this.searchLeaf.setPinned(true); + await this.searchLeaf.setViewState({ + type: "search", + }); - setTimeout(async () => { - await this.searchLeaf.view.setState(this.state, { - history: false, - }); - this.state?.current ? (this.searchLeaf.view as SearchView).searchComponent.inputEl.setSelectionRange(0, 0) : (this.searchLeaf.view as SearchView).searchComponent.inputEl.setSelectionRange(0, this.state?.query?.length); - }, 0); + setTimeout(async () => { + await this.searchLeaf.view.setState(this.state, { + history: false, + }); + this.state?.current ? (this.searchLeaf.view as SearchView).searchComponent.inputEl.setSelectionRange(0, 0) : (this.searchLeaf.view as SearchView).searchComponent.inputEl.setSelectionRange(0, this.state?.query?.length); + }, 0); - return; - } + return; + } - initInput() { - const inputEl = this.contentEl.getElementsByTagName("input")[0]; - inputEl.focus(); - inputEl.onkeydown = (e) => { - const currentView = this.searchLeaf.view as SearchView; - switch (e.key) { - case "ArrowDown": - if (e.shiftKey) { - currentView.onKeyShowMoreAfter(e); - if (currentView.dom.focusedItem) { - if (currentView.dom.focusedItem.collapsible) { - currentView.dom.focusedItem.setCollapse(false); - } - } - break; - } else { - currentView.onKeyArrowDownInFocus(e); - break; - } - case "ArrowUp": - if (e.shiftKey) { - currentView.onKeyShowMoreBefore(e); - if (currentView.dom.focusedItem) { - if (currentView.dom.focusedItem.collapseEl) { - currentView.dom.focusedItem.setCollapse(true); - } - } - break; - } else { - currentView.onKeyArrowUpInFocus(e); - break; - } - case "ArrowLeft": - currentView.onKeyArrowLeftInFocus(e); - break; - case "ArrowRight": - currentView.onKeyArrowRightInFocus(e); - break; - case "Enter": - currentView.onKeyEnterInFocus(e); - if (e.altKey && currentView.dom.focusedItem) { - this.close(); - } - break; - case "Tab": - e.preventDefault(); - if (e.shiftKey) { - if (this.fileLeaf) { - this.fileLeaf?.detach(); - this.fileLeaf = undefined; - this.fileEmbeddedView?.unload(); - this.modalEl.toggleClass("float-search-width", false); - this.fileEl.detach(); + initInput() { + const inputEl = this.contentEl.getElementsByTagName("input")[0]; + inputEl.focus(); + inputEl.onkeydown = (e) => { + const currentView = this.searchLeaf.view as SearchView; + switch (e.key) { + case "ArrowDown": + if (e.shiftKey) { + currentView.onKeyShowMoreAfter(e); + if (currentView.dom.focusedItem) { + if (currentView.dom.focusedItem.collapsible) { + currentView.dom.focusedItem.setCollapse(false); + } + } + break; + } else { + currentView.onKeyArrowDownInFocus(e); + break; + } + case "ArrowUp": + if (e.shiftKey) { + currentView.onKeyShowMoreBefore(e); + if (currentView.dom.focusedItem) { + if (currentView.dom.focusedItem.collapseEl) { + currentView.dom.focusedItem.setCollapse(true); + } + } + break; + } else { + currentView.onKeyArrowUpInFocus(e); + break; + } + case "ArrowLeft": + currentView.onKeyArrowLeftInFocus(e); + break; + case "ArrowRight": + currentView.onKeyArrowRightInFocus(e); + break; + case "Enter": + currentView.onKeyEnterInFocus(e); + if (e.altKey && currentView.dom.focusedItem) { + this.close(); + } + break; + case "Tab": + e.preventDefault(); + if (e.shiftKey) { + if (this.fileLeaf) { + this.fileLeaf?.detach(); + this.fileLeaf = undefined; + this.fileEmbeddedView?.unload(); + this.modalEl.toggleClass("float-search-width", false); + this.fileEl.detach(); - break; - } - } + break; + } + } - if (currentView.dom.focusedItem) { - const item = currentView.dom.focusedItem; - const file = item.parent.file instanceof TFile ? item.parent.file : item.file; + if (currentView.dom.focusedItem) { + const item = currentView.dom.focusedItem; + const file = item.parent.file instanceof TFile ? item.parent.file : item.file; - item.parent.file instanceof TFile ? this.initFileView(file, { - match: { - content: item.content, - matches: item.matches - } - }) : this.initFileView(file, undefined); - } - break; - case "e": - if (e.ctrlKey) { - e.preventDefault(); - if (this.fileLeaf) { - const estate = this.fileLeaf.getViewState(); - estate.state.mode = "preview" === estate.state.mode ? "source" : "preview"; - this.fileLeaf.setViewState(estate, { - focus: !0 - }); - setTimeout(() => { - (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); - }, 0); - } - } - break; - case "g": - if (this.fileLeaf && e.ctrlKey) { - e.preventDefault(); - this.plugin.app.workspace.setActiveLeaf(this.fileLeaf, { - focus: true, - }); - } - break; - case "C": - if (e.ctrlKey && e.shiftKey) { - e.preventDefault(); - const text = currentView.dom.focusedItem.el.innerText; - navigator.clipboard.writeText(text); - } - break; + item.parent.file instanceof TFile ? this.initFileView(file, { + match: { + content: item.content, + matches: item.matches + } + }) : this.initFileView(file, undefined); + } + break; + case "e": + if (e.ctrlKey) { + e.preventDefault(); + if (this.fileLeaf) { + const estate = this.fileLeaf.getViewState(); + estate.state.mode = "preview" === estate.state.mode ? "source" : "preview"; + this.fileLeaf.setViewState(estate, { + focus: !0 + }); + setTimeout(() => { + (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); + }, 0); + } + } + break; + case "g": + if (this.fileLeaf && e.ctrlKey) { + e.preventDefault(); + this.plugin.app.workspace.setActiveLeaf(this.fileLeaf, { + focus: true, + }); + } + break; + case "C": + if (e.ctrlKey && e.shiftKey) { + e.preventDefault(); + const text = currentView.dom.focusedItem.el.innerText; + navigator.clipboard.writeText(text); + } + break; - } - } - } + } + }; + } - initContent() { - const {contentEl} = this; - contentEl.onclick = (e) => { - const resultElement = contentEl.getElementsByClassName('search-results-children')[0]; - if (resultElement.children.length < 2) { - return; - } + initContent() { + const {contentEl} = this; + contentEl.onclick = (e) => { + const resultElement = contentEl.getElementsByClassName('search-results-children')[0]; + if (resultElement.children.length < 2) { + return; + } - let targetElement = e.target as HTMLElement | null; + let targetElement = e.target as HTMLElement | null; - if (e.altKey || !this.fileLeaf) { - while (targetElement) { - if (targetElement.classList.contains('tree-item-icon')) { - break; - } - if (targetElement.classList.contains('tree-item')) { - this.close(); - break; - } - targetElement = targetElement.parentElement; - } - return; - } + if (e.altKey || !this.fileLeaf) { + while (targetElement) { + if (targetElement.classList.contains('tree-item-icon')) { + break; + } + if (targetElement.classList.contains('tree-item')) { + this.close(); + break; + } + targetElement = targetElement.parentElement; + } + return; + } - if (this.fileLeaf) { - const currentView = this.searchLeaf.view as SearchView; + if (this.fileLeaf) { + const currentView = this.searchLeaf.view as SearchView; - if ((this.searchCtnEl as Node).contains(targetElement as Node)) { - while (targetElement) { - if (targetElement.classList.contains('tree-item')) { - break; - } - targetElement = targetElement.parentElement; - } - if (!targetElement) return; + if ((this.searchCtnEl as Node).contains(targetElement as Node)) { + while (targetElement) { + if (targetElement.classList.contains('tree-item')) { + break; + } + targetElement = targetElement.parentElement; + } + if (!targetElement) return; - const fileInnerEl = targetElement?.getElementsByClassName("tree-item-inner")[0] as HTMLElement; - const innerText = fileInnerEl.innerText; - const file = this.plugin.app.metadataCache.getFirstLinkpathDest(innerText, ""); + const fileInnerEl = targetElement?.getElementsByClassName("tree-item-inner")[0] as HTMLElement; + const innerText = fileInnerEl.innerText; + const file = this.plugin.app.metadataCache.getFirstLinkpathDest(innerText, ""); - if (file) { - const item = currentView.dom.resultDomLookup.get(file); - currentView.dom.setFocusedItem(item); - this.initFileView(file, undefined); - (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); - } - } + if (file) { + const item = currentView.dom.resultDomLookup.get(file); + currentView.dom.setFocusedItem(item); + this.initFileView(file, undefined); + (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); + } + } - return; - } - } - } + return; + } + }; + } - async initFileView(file: TFile, state: any) { - if (this.fileLeaf) { - await this.fileLeaf.openFile(file, { - active: false, - eState: state - }); + async initFileView(file: TFile, state: any) { + if (this.fileLeaf) { + await this.fileLeaf.openFile(file, { + active: false, + eState: state + }); - if (this.fileState?.match?.matches[0] === state?.match?.matches[0] && state && this.fileState) { - setTimeout(() => { - if (this.fileLeaf) { - this.plugin.app.workspace.setActiveLeaf(this.fileLeaf, { - focus: true, - }); - } - }, 0); - } else { - this.fileState = state; - setTimeout(() => { - (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); - }, 0); - } + if (this.fileState?.match?.matches[0] === state?.match?.matches[0] && state && this.fileState) { + setTimeout(() => { + if (this.fileLeaf) { + this.plugin.app.workspace.setActiveLeaf(this.fileLeaf, { + focus: true, + }); + } + }, 0); + } else { + this.fileState = state; + setTimeout(() => { + (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); + }, 0); + } - return; - } + return; + } - const {contentEl} = this; - this.fileEl = contentEl.createDiv({cls: "float-search-modal-file-ctn"}); - this.modalEl.toggleClass("float-search-width", true); - this.fileEl.onkeydown = (e) => { - if (e.ctrlKey && e.key === "g") { - e.preventDefault(); - e.stopPropagation(); + const {contentEl} = this; + this.fileEl = contentEl.createDiv({cls: "float-search-modal-file-ctn"}); + this.modalEl.toggleClass("float-search-width", true); + this.fileEl.onkeydown = (e) => { + if (e.ctrlKey && e.key === "g") { + e.preventDefault(); + e.stopPropagation(); - (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); - } + (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); + } - if (e.key === "Tab" && e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); + if (e.key === "Tab" && e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); - (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); - } - } + (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); + } + }; - if (!this.fileEl) return; + if (!this.fileEl) return; - const [createdLeaf, embeddedView] = spawnLeafView(this.plugin, this.fileEl); - this.fileLeaf = createdLeaf; - this.fileEmbeddedView = embeddedView; + const [createdLeaf, embeddedView] = spawnLeafView(this.plugin, this.fileEl); + this.fileLeaf = createdLeaf; + this.fileEmbeddedView = embeddedView; - this.fileLeaf.setPinned(true); - await this.fileLeaf.openFile(file, { - active: false, - eState: state - }); - this.fileState = state; + this.fileLeaf.setPinned(true); + await this.fileLeaf.openFile(file, { + active: false, + eState: state + }); + this.fileState = state; - (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); + (this.searchLeaf.view as SearchView).searchComponent.inputEl.focus(); - } + } } diff --git a/versions.json b/versions.json index 259362b..09ed13a 100644 --- a/versions.json +++ b/versions.json @@ -22,5 +22,6 @@ "3.4.2": "0.15.0", "3.4.3": "0.15.0", "3.4.4": "0.15.0", - "3.4.5": "0.15.0" + "3.4.5": "0.15.0", + "3.4.6": "0.15.0" } \ No newline at end of file From 920801bc2541baa65ae6e59bcf1b918508b4edf3 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 17 Jan 2024 13:51:14 +0800 Subject: [PATCH 02/12] fix: cannot show more context --- .github/workflows/release.yml | 2 -- src/floatSearchIndex.ts | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 523d81d..3a3a67c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,8 +7,6 @@ on: env: PLUGIN_NAME: obsidian-float-search -permissions: write-all - jobs: build: runs-on: ubuntu-latest diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index 46b09c1..fa8fbde 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -811,10 +811,14 @@ class FloatSearchModal extends Modal { if (targetElement.classList.contains('tree-item-icon')) { break; } + if (targetElement.classList.contains('search-result-hover-button')) { + break; + } if (targetElement.classList.contains('tree-item')) { this.close(); break; } + targetElement = targetElement.parentElement; } return; From 2ba8f05d669d8a6a91bfbccd949017e4eb5d2e57 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 17 Jan 2024 14:00:10 +0800 Subject: [PATCH 03/12] fix: query issue --- src/floatSearchIndex.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index fa8fbde..f24e636 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -415,7 +415,7 @@ export default class FloatSearchPlugin extends Plugin { ...this.state, query: path.query, current: false - }, true, true)); + }, true, false)); } private createCommand(options: { From dc5d25f2b7c9231e1812b7abd27a421f370a3cf1 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 17 Jan 2024 14:14:07 +0800 Subject: [PATCH 04/12] fix: cannot close via hotkey --- src/floatSearchIndex.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index f24e636..e25bf1f 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -81,6 +81,8 @@ const initSearchViewWithLeaf = async (app: App, type: PaneType | 'sidebar', stat state: state }); + console.log(leaf.view); + setTimeout(() => { const inputEl = leaf.containerEl.getElementsByTagName("input")[0]; inputEl.focus(); @@ -371,6 +373,10 @@ export default class FloatSearchPlugin extends Plugin { onOpen(old) { return function () { old.call(this); + (this.scope as Scope).register(['Mod'], 'w', () => { + this.leaf?.detach(); + }); + const viewSwitchEl = createDiv({cls: "float-search-view-switch"}); const targetEl = this.filterSectionToggleEl; const viewSwitchButton = new ExtraButtonComponent(viewSwitchEl); @@ -482,6 +488,7 @@ export default class FloatSearchPlugin extends Plugin { const existingLeaf = this.app.workspace.getLeavesOfType("search"); switch (type) { case "window": + // @ts-ignore const isExistingWindowLeaf = existingLeaf.find((leaf) => leaf.parentSplit.parent.type === "window"); if (isExistingWindowLeaf) { this.app.workspace.revealLeaf(isExistingWindowLeaf); @@ -491,7 +498,9 @@ export default class FloatSearchPlugin extends Plugin { break; case "tab": case "split": + // @ts-ignore const isExistingLeaf = existingLeaf.find((leaf) => !leaf.parentSplit.parent.side); + console.log(isExistingLeaf); if (isExistingLeaf) { this.app.workspace.revealLeaf(isExistingLeaf); return; From 4467c23332a82fc97a9ed201b9825a980a18b180 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Sun, 21 Jan 2024 15:50:53 +0800 Subject: [PATCH 05/12] fix: cannot focus on search bar after init --- manifest.json | 2 +- package.json | 2 +- src/floatSearchIndex.ts | 7 +++++-- versions.json | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index 885816e..44130be 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "float-search", "name": "Floating Search", - "version": "3.4.6", + "version": "3.4.7", "minAppVersion": "0.15.0", "description": "You can use search view in modal/leaf/popout window now.", "author": "Boninall", diff --git a/package.json b/package.json index b4454a4..e3b6697 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "float-search", - "version": "3.4.6", + "version": "3.4.7", "description": "You can use search view in modal/leaf/popout window now.", "main": "main.js", "scripts": { diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index e25bf1f..5938653 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -81,8 +81,6 @@ const initSearchViewWithLeaf = async (app: App, type: PaneType | 'sidebar', stat state: state }); - console.log(leaf.view); - setTimeout(() => { const inputEl = leaf.containerEl.getElementsByTagName("input")[0]; inputEl.focus(); @@ -503,6 +501,11 @@ export default class FloatSearchPlugin extends Plugin { console.log(isExistingLeaf); if (isExistingLeaf) { this.app.workspace.revealLeaf(isExistingLeaf); + isExistingLeaf.setViewState({ + type: "search", + active: true, + state: this.state + }); return; } await initSearchViewWithLeaf(this.app, type); diff --git a/versions.json b/versions.json index 09ed13a..82f58ce 100644 --- a/versions.json +++ b/versions.json @@ -23,5 +23,6 @@ "3.4.3": "0.15.0", "3.4.4": "0.15.0", "3.4.5": "0.15.0", - "3.4.6": "0.15.0" + "3.4.6": "0.15.0", + "3.4.7": "0.15.0" } \ No newline at end of file From 1e8f1c672d130d5fcd67aa0736b67d11d9b2b016 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Tue, 23 Jan 2024 16:46:03 +0800 Subject: [PATCH 06/12] fix: cannot save settings --- src/floatSearchIndex.ts | 80 +-- src/leafView.ts | 1045 ++++++++++++++++++++------------------- 2 files changed, 565 insertions(+), 560 deletions(-) diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index 5938653..5137a77 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -1,14 +1,22 @@ import { addIcon, App, - Editor, ExtraButtonComponent, - Menu, MenuItem, - Modal, OpenViewState, PaneType, - Plugin, Scope, SearchView, setIcon, + debounce, + Editor, + ExtraButtonComponent, + Menu, + MenuItem, + Modal, + OpenViewState, + PaneType, + Plugin, + Scope, + SearchView, TAbstractFile, - TFile, ViewStateResult, + TFile, Workspace, - WorkspaceContainer, WorkspaceItem, + WorkspaceContainer, + WorkspaceItem, WorkspaceLeaf } from 'obsidian'; import { EmbeddedView, isEmebeddedLeaf, spawnLeafView } from "./leafView"; @@ -92,35 +100,27 @@ export default class FloatSearchPlugin extends Plugin { private state: any; private modal: FloatSearchModal; - private applyStateDebounceTimer = 0; - private applySettingsDebounceTimer = 0; + public applySettingsUpdate = debounce(async () => { + await this.saveSettings(); + }, 1000); - - public applySettingsUpdate() { - clearTimeout(this.applySettingsDebounceTimer); - this.applySettingsDebounceTimer = window.setTimeout(async () => { - await this.saveSettings(); - }, 1000); - } - - private applyStateUpdate() { - this.applyStateDebounceTimer = window.setTimeout(() => { - clearTimeout(this.applyStateDebounceTimer); - this.state = { - ...this.state, - query: "", - }; - }, 30000); - } + private applyStateUpdate = debounce(() => { + this.state = { + ...this.state, + query: "", + }; + }, 30000); async onload() { await this.loadSettings(); this.initState(); this.registerIcons(); - this.patchWorkspace(); - this.patchWorkspaceLeaf(); - this.patchSearchView(); + this.app.workspace.onLayoutReady(() => { + this.patchWorkspace(); + this.patchWorkspaceLeaf(); + this.patchSearchView(); + }); this.registerObsidianURIHandler(); this.registerObsidianCommands(); @@ -158,6 +158,7 @@ export default class FloatSearchPlugin extends Plugin { patchWorkspace() { let layoutChanging = false; + const self = this; const uninstaller = around(Workspace.prototype, { getLeaf: (next) => function (...args) { @@ -168,7 +169,7 @@ export default class FloatSearchPlugin extends Plugin { if (activeLeaf.view.getViewType() === "markdown") { return activeLeaf; } - const newLeaf = app.workspace.getUnpinnedLeaf(); + const newLeaf = self.app.workspace.getUnpinnedLeaf(); if (newLeaf) { this.setActiveLeaf(newLeaf); } @@ -202,7 +203,7 @@ export default class FloatSearchPlugin extends Plugin { if (layoutChanging) return false; // Don't let HEs close during workspace change // 0.14.x doesn't have WorkspaceContainer; this can just be an instanceof check once 15.x is mandatory: - if (parent === app.workspace.rootSplit || (WorkspaceContainer && parent instanceof WorkspaceContainer)) { + if (parent === self.app.workspace.rootSplit || (WorkspaceContainer && parent instanceof WorkspaceContainer)) { for (const popover of EmbeddedView.popoversForWindow((parent as WorkspaceContainer).win)) { // Use old API here for compat w/0.14.x if (old.call(this, cb, popover.rootSplit)) return true; @@ -306,6 +307,7 @@ export default class FloatSearchPlugin extends Plugin { patchSearchView() { const updateCurrentState = (state: searchState) => { this.state = state; + console.log(state); this.settings.searchViewState = state as searchState; this.applySettingsUpdate(); }; @@ -361,6 +363,7 @@ export default class FloatSearchPlugin extends Plugin { const patchSearch = () => { const searchView = this.app.workspace.getLeavesOfType("search")[0]?.view as any; + const self = this; if (!searchView) return false; @@ -388,14 +391,15 @@ export default class FloatSearchPlugin extends Plugin { targetEl.parentElement.insertBefore(viewSwitchEl, targetEl); }; }, - startSearch(old) { - return function () { - old.call(this); - const viewState = this.getState(); - updateCurrentState({ - ...viewState, - query: this.searchComponent.getValue(), - }); + setState(old) { + return async function (state: any, result: any) { + old.call(this, state, result); + if (self.app.workspace.layoutReady) { + updateCurrentState({ + ...state, + query: this.searchComponent.getValue(), + }); + } }; } }) diff --git a/src/leafView.ts b/src/leafView.ts index 3c22146..ea5301a 100644 --- a/src/leafView.ts +++ b/src/leafView.ts @@ -3,551 +3,552 @@ // Please rememeber if you want to use this file, you should patch the types-obsidian.d.ts file // And also monkey around the Obsidian original method. import { - Component, + Component, EphemeralState, - HoverPopover, - MarkdownEditView, - parseLinktext, - PopoverState, - requireApiVersion, - resolveSubpath, - TFile, - View, - Workspace, - WorkspaceLeaf, - WorkspaceSplit, - WorkspaceTabs, + HoverPopover, + MarkdownEditView, + parseLinktext, + PopoverState, + requireApiVersion, + resolveSubpath, + TFile, + View, + Workspace, + WorkspaceLeaf, + WorkspaceSplit, + WorkspaceTabs, } from "obsidian"; import type { OpenViewState } from "src/types/types-obsidian"; import type FloatSearchPlugin from "src/floatSearchIndex"; export interface EmbeddedViewParent { - hoverPopover: EmbeddedView | null; - containerEl?: HTMLElement; - view?: View; - dom?: HTMLElement; + hoverPopover: EmbeddedView | null; + containerEl?: HTMLElement; + view?: View; + dom?: HTMLElement; } + const popovers = new WeakMap(); -type ConstructableWorkspaceSplit = new (ws: Workspace, dir: "horizontal"|"vertical") => WorkspaceSplit; +type ConstructableWorkspaceSplit = new (ws: Workspace, dir: "horizontal" | "vertical") => WorkspaceSplit; export function isEmebeddedLeaf(leaf: WorkspaceLeaf) { - // Work around missing enhance.js API by checking match condition instead of looking up parent - return leaf.containerEl.matches(".fs-block.fs-leaf-view .workspace-leaf"); + // Work around missing enhance.js API by checking match condition instead of looking up parent + return leaf.containerEl.matches(".fs-block.fs-leaf-view .workspace-leaf"); } function genId(size: number): string { - const chars = []; - for (let n = 0; n < size; n++) chars.push(((16 * Math.random()) | 0).toString(16)); - return chars.join(""); + const chars = []; + for (let n = 0; n < size; n++) chars.push(((16 * Math.random()) | 0).toString(16)); + return chars.join(""); } function nosuper(base: new (...args: unknown[]) => T): new () => T { - const derived = function () { - return Object.setPrototypeOf(new Component, new.target.prototype); - }; - derived.prototype = base.prototype; - return Object.setPrototypeOf(derived, base); + const derived = function () { + return Object.setPrototypeOf(new Component, new.target.prototype); + }; + derived.prototype = base.prototype; + return Object.setPrototypeOf(derived, base); } export const spawnLeafView = (plugin: FloatSearchPlugin, initiatingEl?: HTMLElement, leaf?: WorkspaceLeaf, onShowCallback?: () => unknown): [WorkspaceLeaf, EmbeddedView] => { - // When Obsidian doesn't set any leaf active, use leaf instead. - let parent = app.workspace.activeLeaf as unknown as EmbeddedViewParent; - if (!parent) parent = leaf as unknown as EmbeddedViewParent; + // When Obsidian doesn't set any leaf active, use leaf instead. + let parent = app.workspace.activeLeaf as unknown as EmbeddedViewParent; + if (!parent) parent = leaf as unknown as EmbeddedViewParent; - if (!initiatingEl) initiatingEl = parent?.containerEl; + if (!initiatingEl) initiatingEl = parent?.containerEl; - const hoverPopover = new EmbeddedView(parent, initiatingEl!, plugin, undefined, onShowCallback); - return [hoverPopover.attachLeaf(), hoverPopover]; + const hoverPopover = new EmbeddedView(parent, initiatingEl!, plugin, undefined, onShowCallback); + return [hoverPopover.attachLeaf(), hoverPopover]; -} +}; export class EmbeddedView extends nosuper(HoverPopover) { - onTarget: boolean; - setActive: (event: MouseEvent) => void; - - lockedOut: boolean; - abortController? = this.addChild(new Component()); - detaching = false; - opening = false; - - rootSplit: WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(window.app.workspace, "vertical"); - - isPinned = true; - - titleEl: HTMLElement; - containerEl: HTMLElement; - - // It is currently not useful. - // leafInHoverEl: WorkspaceLeaf; - - oldPopover = this.parent?.hoverPopover; - document: Document = this.targetEl?.ownerDocument ?? window.activeDocument ?? window.document; - - id = genId(8); - bounce?: NodeJS.Timeout; - boundOnZoomOut: () => void; - - originalPath: string; // these are kept to avoid adopting targets w/a different link - originalLinkText: string; - static activePopover?: EmbeddedView; - - static activeWindows() { - const windows: Window[] = [window]; - const { floatingSplit } = app.workspace; - if (floatingSplit) { - for (const split of floatingSplit.children) { - if (split.win) windows.push(split.win); - } - } - return windows; - } - - static containerForDocument(doc: Document) { - if (doc !== document && app.workspace.floatingSplit) - for (const container of app.workspace.floatingSplit.children) { - if (container.doc === doc) return container; - } - return app.workspace.rootSplit; - } - - static activePopovers() { - return this.activeWindows().flatMap(this.popoversForWindow); - } - - static popoversForWindow(win?: Window) { - return (Array.prototype.slice.call(win?.document?.body.querySelectorAll(".fs-leaf-view") ?? []) as HTMLElement[]) - .map(el => popovers.get(el)!) - .filter(he => he); - } - - static forLeaf(leaf: WorkspaceLeaf | undefined) { - // leaf can be null such as when right clicking on an internal link - const el = leaf && document.body.matchParent.call(leaf.containerEl, ".fs-leaf-view"); // work around matchParent race condition - return el ? popovers.get(el) : undefined; - } - - static iteratePopoverLeaves(ws: Workspace, cb: (leaf: WorkspaceLeaf) => boolean | void) { - for (const popover of this.activePopovers()) { - if (popover.rootSplit && ws.iterateLeaves(cb, popover.rootSplit)) return true; - } - return false; - } - - hoverEl: HTMLElement = this.document.defaultView!.createDiv({ - cls: "fs-block fs-leaf-view", - attr: { id: "fs-" + this.id }, - }); - - constructor( - parent: EmbeddedViewParent, - public targetEl: HTMLElement, - public plugin: FloatSearchPlugin, - waitTime?: number, - public onShowCallback?: () => unknown, - ) { - // - super(); - - if (waitTime === undefined) { - waitTime = 300; - } - this.onTarget = true; - - this.parent = parent; - this.waitTime = waitTime; - this.state = PopoverState.Showing; - const { hoverEl } = this; - - this.abortController!.load(); - this.show(); - this.onShow(); - - this.setActive = this._setActive.bind(this); - // if (hoverEl) { - // hoverEl.addEventListener("mousedown", this.setActive); - // } - // custom logic begin - popovers.set(this.hoverEl, this); - this.hoverEl.addClass("fs-block"); - this.containerEl = this.hoverEl.createDiv("fs-content"); - this.buildWindowControls(); - this.setInitialDimensions(); - } - - _setActive(evt: MouseEvent) { - evt.preventDefault(); - evt.stopPropagation(); - this.plugin.app.workspace.setActiveLeaf(this.leaves()[0], {focus: true}) - } - - getDefaultMode() { - // return this.parent?.view?.getMode ? this.parent.view.getMode() : "source"; - return "source"; - } - - updateLeaves() { - if (this.onTarget && this.targetEl && !this.document.contains(this.targetEl)) { - this.onTarget = false; - this.transition(); - } - let leafCount = 0; - this.plugin.app.workspace.iterateLeaves(leaf => { - leafCount++; - }, this.rootSplit); - - if (leafCount === 0) { - this.hide(); // close if we have no leaves - } - this.hoverEl.setAttribute("data-leaf-count", leafCount.toString()); - } - - leaves() { - const leaves: WorkspaceLeaf[] = []; - this.plugin.app.workspace.iterateLeaves(leaf => { - leaves.push(leaf); - }, this.rootSplit); - return leaves; - } - - setInitialDimensions() { - - this.hoverEl.style.height = 'auto'; - this.hoverEl.style.width = "100%"; - } - - transition() { - if (this.shouldShow()) { - if (this.state === PopoverState.Hiding) { - this.state = PopoverState.Shown; - clearTimeout(this.timer); - } - } else { - if (this.state === PopoverState.Showing) { - this.hide(); - } else { - if (this.state === PopoverState.Shown) { - this.state = PopoverState.Hiding; - this.timer = window.setTimeout(() => { - if (this.shouldShow()) { - this.transition(); - } else { - this.hide(); - } - }, this.waitTime); - } - } - } - } - - - buildWindowControls() { - this.titleEl = this.document.defaultView!.createDiv("popover-titlebar"); - this.titleEl.createDiv("popover-title"); - - this.containerEl.prepend(this.titleEl); - - } - - attachLeaf(): WorkspaceLeaf { - this.rootSplit.getRoot = () => this.plugin.app.workspace[this.document === document ? "rootSplit" : "floatingSplit"]!; - this.rootSplit.getContainer = () => EmbeddedView.containerForDocument(this.document); - - this.titleEl.insertAdjacentElement("afterend", this.rootSplit.containerEl); - const leaf = this.plugin.app.workspace.createLeafInParent(this.rootSplit, 0); - - this.updateLeaves(); - return leaf; - } - - onload(): void { - super.onload(); - this.registerEvent(this.plugin.app.workspace.on("layout-change", this.updateLeaves, this)); - this.registerEvent(app.workspace.on("layout-change", () => { - // Ensure that top-level items in a popover are not tabbed - // @ts-ignore - this.rootSplit.children.forEach((item: any, index: any) => { - if (item instanceof WorkspaceTabs) { - this.rootSplit.replaceChild(index, item.children[0]); - } - }) - })); - } - - onShow() { - // Once we've been open for closeDelay, use the closeDelay as a hiding timeout - const closeDelay = 600; - setTimeout(() => (this.waitTime = closeDelay), closeDelay); - - this.oldPopover?.hide(); - this.oldPopover = null; - - this.hoverEl.toggleClass("is-new", true); - - this.document.body.addEventListener( - "click", - () => { - this.hoverEl.toggleClass("is-new", false); - }, - { once: true, capture: true }, - ); - - if (this.parent) { - this.parent.hoverPopover = this; - } - - // Remove original view header; - const viewHeaderEl = this.hoverEl.querySelector(".view-header"); - viewHeaderEl?.remove(); - - const sizer = this.hoverEl.querySelector(".workspace-leaf"); - if(sizer) this.hoverEl.appendChild(sizer); - - // Remove original inline tilte; - const inlineTitle = this.hoverEl.querySelector(".inline-title"); - if(inlineTitle) inlineTitle.remove(); - - this.onShowCallback?.(); - this.onShowCallback = undefined; // only call it once - } - - detect(el: HTMLElement) { - // TODO: may not be needed? the mouseover/out handers handle most detection use cases - const { targetEl } = this; - - if (targetEl) { - this.onTarget = el === targetEl || targetEl.contains(el); - } - } - - shouldShow() { - return this.shouldShowSelf() || this.shouldShowChild(); - } - - shouldShowChild(): boolean { - return EmbeddedView.activePopovers().some(popover => { - if (popover !== this && popover.targetEl && this.hoverEl.contains(popover.targetEl)) { - return popover.shouldShow(); - } - return false; - }); - } - - shouldShowSelf() { - // Don't let obsidian show() us if we've already started closing - // return !this.detaching && (this.onTarget || this.onHover); - return ( - !this.detaching && - !!( - this.onTarget || - (this.state == PopoverState.Shown) || - this.document.querySelector(`body>.modal-container, body > #he${this.id} ~ .menu, body > #he${this.id} ~ .suggestion-container`) - ) - ); - } - - show() { - // native obsidian logic start - // if (!this.targetEl || this.document.body.contains(this.targetEl)) { - this.state = PopoverState.Shown; - this.timer = 0; - - this.targetEl.appendChild(this.hoverEl); - this.onShow(); - app.workspace.onLayoutChange(); - - // initializingHoverPopovers.remove(this); - // activeHoverPopovers.push(this); - // initializePopoverChecker(); - this.load(); - // } - // native obsidian logic end - - // if this is an image view, set the dimensions to the natural dimensions of the image - // an interactjs reflow will be triggered to constrain the image to the viewport if it's - // too large - if (this.hoverEl.dataset.imgHeight && this.hoverEl.dataset.imgWidth) { - this.hoverEl.style.height = parseFloat(this.hoverEl.dataset.imgHeight) + this.titleEl.offsetHeight + "px"; - this.hoverEl.style.width = parseFloat(this.hoverEl.dataset.imgWidth) + "px"; - } - } - - onHide() { - this.oldPopover = null; - if (this.parent?.hoverPopover === this) { - this.parent.hoverPopover = null; - } - } - - hide() { - this.onTarget = false; - this.detaching = true; - // Once we reach this point, we're committed to closing - - // in case we didn't ever call show() - - - // A timer might be pending to call show() for the first time, make sure - // it doesn't bring us back up after we close - if (this.timer) { - clearTimeout(this.timer); - this.timer = 0; - } - - // Hide our HTML element immediately, even if our leaves might not be - // detachable yet. This makes things more responsive and improves the - // odds of not showing an empty popup that's just going to disappear - // momentarily. - this.hoverEl.hide(); - - // If a file load is in progress, we need to wait until it's finished before - // detaching leaves. Because we set .detaching, The in-progress openFile() - // will call us again when it finishes. - if (this.opening) return; - - // Leave this code here to observe the state of the leaves - const leaves = this.leaves(); - if (leaves.length) { - // Detach all leaves before we unload the popover and remove it from the DOM. - // Each leaf.detach() will trigger layout-changed and the updateLeaves() - // method will then call hide() again when the last one is gone. - // leaves[0].detach(); - // leaves[0].detach(); - this.targetEl.empty(); - } else { - this.parent = null; - this.abortController?.unload(); - this.abortController = undefined; - return this.nativeHide(); - } - } - - nativeHide() { - const { hoverEl, targetEl } = this; - this.state = PopoverState.Hidden; - hoverEl.detach(); - - if (targetEl) { - const parent = targetEl.matchParent(".fs-leaf-view"); - if (parent) popovers.get(parent)?.transition(); - } - - this.onHide(); - this.unload(); - } - - resolveLink(linkText: string, sourcePath: string): TFile | null { - const link = parseLinktext(linkText); - const tFile = link ? this.plugin.app.metadataCache.getFirstLinkpathDest(link.path, sourcePath) : null; - return tFile; - } - - async openLink(linkText: string, sourcePath: string, eState?: EphemeralState, createInLeaf?: WorkspaceLeaf) { - let file = this.resolveLink(linkText, sourcePath); - const link = parseLinktext(linkText); - if (!file && createInLeaf) { - const folder = this.plugin.app.fileManager.getNewFileParent(sourcePath); - file = await this.plugin.app.fileManager.createNewMarkdownFile(folder, link.path); - } - - if (!file) { - // this.displayCreateFileAction(linkText, sourcePath, eState); - return; - } - const { viewRegistry } = this.plugin.app; - const viewType = viewRegistry.typeByExtension[file.extension]; - if (!viewType || !viewRegistry.viewByType[viewType]) { - // this.displayOpenFileAction(file); - return; - } - - eState = Object.assign(this.buildEphemeralState(file, link), eState); - const parentMode = this.getDefaultMode(); - const state = this.buildState(parentMode, eState); - const leaf = await this.openFile(file, state, createInLeaf); - const leafViewType = leaf?.view?.getViewType(); - // console.log(leaf); - if (leafViewType === "image") { - // TODO: temporary workaround to prevent image popover from disappearing immediately when using live preview - if ( - this.parent?.hasOwnProperty("editorEl") && - (this.parent as unknown as MarkdownEditView).editorEl!.hasClass("is-live-preview") - ) { - this.waitTime = 3000; - } - const img = leaf!.view.contentEl.querySelector("img")!; - this.hoverEl.dataset.imgHeight = String(img.naturalHeight); - this.hoverEl.dataset.imgWidth = String(img.naturalWidth); - this.hoverEl.dataset.imgRatio = String(img.naturalWidth / img.naturalHeight); - } else if (leafViewType === "pdf") { - this.hoverEl.style.height = "800px"; - this.hoverEl.style.width = "600px"; - } - if (state.state?.mode === "source") { - this.whenShown(() => { - // Not sure why this is needed, but without it we get issue #186 - if (requireApiVersion("1.0"))(leaf?.view as any)?.editMode?.reinit?.(); - leaf?.view?.setEphemeralState(state.eState); - }); - } - } - - whenShown(callback: () => any) { - // invoke callback once the popover is visible - if (this.detaching) return; - const existingCallback = this.onShowCallback; - this.onShowCallback = () => { - if (this.detaching) return; - callback(); - if (typeof existingCallback === "function") existingCallback(); - }; - if (this.state === PopoverState.Shown) { - this.onShowCallback(); - this.onShowCallback = undefined; - } - } - - async openFile(file: TFile, openState?: OpenViewState, useLeaf?: WorkspaceLeaf) { - if (this.detaching) return; - const leaf = useLeaf ?? this.attachLeaf(); - this.opening = true; - - try { - await leaf.openFile(file, openState); - } catch (e) { - console.error(e); - } finally { - this.opening = false; - if (this.detaching) this.hide(); - } - this.plugin.app.workspace.setActiveLeaf(leaf); - - return leaf; - } - - buildState(parentMode: string, eState?: EphemeralState) { - return { - active: false, // Don't let Obsidian force focus if we have autofocus off - state: { mode: "source" }, // Don't set any state for the view, because this leaf is stayed on another view. - eState: eState, - }; - } - - buildEphemeralState( - file: TFile, - link?: { - path: string; - subpath: string; - }, - ) { - const cache = this.plugin.app.metadataCache.getFileCache(file); - const subpath = cache ? resolveSubpath(cache, link?.subpath || "") : undefined; - const eState: EphemeralState = { subpath: link?.subpath }; - if (subpath) { - eState.line = subpath.start.line; - eState.startLoc = subpath.start; - eState.endLoc = subpath.end || undefined; - } - return eState; - } + onTarget: boolean; + setActive: (event: MouseEvent) => void; + + lockedOut: boolean; + abortController? = this.addChild(new Component()); + detaching = false; + opening = false; + + rootSplit: WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(window.app.workspace, "vertical"); + + isPinned = true; + + titleEl: HTMLElement; + containerEl: HTMLElement; + + // It is currently not useful. + // leafInHoverEl: WorkspaceLeaf; + + oldPopover = this.parent?.hoverPopover; + document: Document = this.targetEl?.ownerDocument ?? window.activeDocument ?? window.document; + + id = genId(8); + bounce?: NodeJS.Timeout; + boundOnZoomOut: () => void; + + originalPath: string; // these are kept to avoid adopting targets w/a different link + originalLinkText: string; + static activePopover?: EmbeddedView; + + static activeWindows() { + const windows: Window[] = [window]; + const {floatingSplit} = app.workspace; + if (floatingSplit) { + for (const split of floatingSplit.children) { + if (split.win) windows.push(split.win); + } + } + return windows; + } + + static containerForDocument(doc: Document) { + if (doc !== document && app.workspace.floatingSplit) + for (const container of app.workspace.floatingSplit.children) { + if (container.doc === doc) return container; + } + return app.workspace.rootSplit; + } + + static activePopovers() { + return this.activeWindows().flatMap(this.popoversForWindow); + } + + static popoversForWindow(win?: Window) { + return (Array.prototype.slice.call(win?.document?.body.querySelectorAll(".fs-leaf-view") ?? []) as HTMLElement[]) + .map(el => popovers.get(el)!) + .filter(he => he); + } + + static forLeaf(leaf: WorkspaceLeaf | undefined) { + // leaf can be null such as when right clicking on an internal link + const el = leaf && document.body.matchParent.call(leaf.containerEl, ".fs-leaf-view"); // work around matchParent race condition + return el ? popovers.get(el) : undefined; + } + + static iteratePopoverLeaves(ws: Workspace, cb: (leaf: WorkspaceLeaf) => boolean | void) { + for (const popover of this.activePopovers()) { + if (popover.rootSplit && ws.iterateLeaves(cb, popover.rootSplit)) return true; + } + return false; + } + + hoverEl: HTMLElement = this.document.defaultView!.createDiv({ + cls: "fs-block fs-leaf-view", + attr: {id: "fs-" + this.id}, + }); + + constructor( + parent: EmbeddedViewParent, + public targetEl: HTMLElement, + public plugin: FloatSearchPlugin, + waitTime?: number, + public onShowCallback?: () => unknown, + ) { + // + super(); + + if (waitTime === undefined) { + waitTime = 300; + } + this.onTarget = true; + + this.parent = parent; + this.waitTime = waitTime; + this.state = PopoverState.Showing; + const {hoverEl} = this; + + this.abortController!.load(); + this.show(); + this.onShow(); + + this.setActive = this._setActive.bind(this); + // if (hoverEl) { + // hoverEl.addEventListener("mousedown", this.setActive); + // } + // custom logic begin + popovers.set(this.hoverEl, this); + this.hoverEl.addClass("fs-block"); + this.containerEl = this.hoverEl.createDiv("fs-content"); + this.buildWindowControls(); + this.setInitialDimensions(); + } + + _setActive(evt: MouseEvent) { + evt.preventDefault(); + evt.stopPropagation(); + this.plugin.app.workspace.setActiveLeaf(this.leaves()[0], {focus: true}); + } + + getDefaultMode() { + // return this.parent?.view?.getMode ? this.parent.view.getMode() : "source"; + return "source"; + } + + updateLeaves() { + if (this.onTarget && this.targetEl && !this.document.contains(this.targetEl)) { + this.onTarget = false; + this.transition(); + } + let leafCount = 0; + this.plugin.app.workspace.iterateLeaves(leaf => { + leafCount++; + }, this.rootSplit); + + if (leafCount === 0) { + this.hide(); // close if we have no leaves + } + this.hoverEl.setAttribute("data-leaf-count", leafCount.toString()); + } + + leaves() { + const leaves: WorkspaceLeaf[] = []; + this.plugin.app.workspace.iterateLeaves(leaf => { + leaves.push(leaf); + }, this.rootSplit); + return leaves; + } + + setInitialDimensions() { + + this.hoverEl.style.height = 'auto'; + this.hoverEl.style.width = "100%"; + } + + transition() { + if (this.shouldShow()) { + if (this.state === PopoverState.Hiding) { + this.state = PopoverState.Shown; + clearTimeout(this.timer); + } + } else { + if (this.state === PopoverState.Showing) { + this.hide(); + } else { + if (this.state === PopoverState.Shown) { + this.state = PopoverState.Hiding; + this.timer = window.setTimeout(() => { + if (this.shouldShow()) { + this.transition(); + } else { + this.hide(); + } + }, this.waitTime); + } + } + } + } + + + buildWindowControls() { + this.titleEl = this.document.defaultView!.createDiv("popover-titlebar"); + this.titleEl.createDiv("popover-title"); + + this.containerEl.prepend(this.titleEl); + + } + + attachLeaf(): WorkspaceLeaf { + this.rootSplit.getRoot = () => this.plugin.app.workspace[this.document === document ? "rootSplit" : "floatingSplit"]!; + this.rootSplit.getContainer = () => EmbeddedView.containerForDocument(this.document); + + this.titleEl.insertAdjacentElement("afterend", this.rootSplit.containerEl); + const leaf = this.plugin.app.workspace.createLeafInParent(this.rootSplit, 0); + + this.updateLeaves(); + return leaf; + } + + onload(): void { + super.onload(); + this.registerEvent(this.plugin.app.workspace.on("layout-change", this.updateLeaves, this)); + this.registerEvent(app.workspace.on("layout-change", () => { + // Ensure that top-level items in a popover are not tabbed + // @ts-ignore + this.rootSplit.children.forEach((item: any, index: any) => { + if (item instanceof WorkspaceTabs) { + this.rootSplit.replaceChild(index, item.children[0]); + } + }); + })); + } + + onShow() { + // Once we've been open for closeDelay, use the closeDelay as a hiding timeout + const closeDelay = 600; + setTimeout(() => (this.waitTime = closeDelay), closeDelay); + + this.oldPopover?.hide(); + this.oldPopover = null; + + this.hoverEl.toggleClass("is-new", true); + + this.document.body.addEventListener( + "click", + () => { + this.hoverEl.toggleClass("is-new", false); + }, + {once: true, capture: true}, + ); + + if (this.parent) { + this.parent.hoverPopover = this; + } + + // Remove original view header; + const viewHeaderEl = this.hoverEl.querySelector(".view-header"); + viewHeaderEl?.remove(); + + const sizer = this.hoverEl.querySelector(".workspace-leaf"); + if (sizer) this.hoverEl.appendChild(sizer); + + // Remove original inline tilte; + const inlineTitle = this.hoverEl.querySelector(".inline-title"); + if (inlineTitle) inlineTitle.remove(); + + this.onShowCallback?.(); + this.onShowCallback = undefined; // only call it once + } + + detect(el: HTMLElement) { + // TODO: may not be needed? the mouseover/out handers handle most detection use cases + const {targetEl} = this; + + if (targetEl) { + this.onTarget = el === targetEl || targetEl.contains(el); + } + } + + shouldShow() { + return this.shouldShowSelf() || this.shouldShowChild(); + } + + shouldShowChild(): boolean { + return EmbeddedView.activePopovers().some(popover => { + if (popover !== this && popover.targetEl && this.hoverEl.contains(popover.targetEl)) { + return popover.shouldShow(); + } + return false; + }); + } + + shouldShowSelf() { + // Don't let obsidian show() us if we've already started closing + // return !this.detaching && (this.onTarget || this.onHover); + return ( + !this.detaching && + !!( + this.onTarget || + (this.state == PopoverState.Shown) || + this.document.querySelector(`body>.modal-container, body > #he${this.id} ~ .menu, body > #he${this.id} ~ .suggestion-container`) + ) + ); + } + + show() { + // native obsidian logic start + // if (!this.targetEl || this.document.body.contains(this.targetEl)) { + this.state = PopoverState.Shown; + this.timer = 0; + + this.targetEl.appendChild(this.hoverEl); + this.onShow(); + app.workspace.onLayoutChange(); + + // initializingHoverPopovers.remove(this); + // activeHoverPopovers.push(this); + // initializePopoverChecker(); + this.load(); + // } + // native obsidian logic end + + // if this is an image view, set the dimensions to the natural dimensions of the image + // an interactjs reflow will be triggered to constrain the image to the viewport if it's + // too large + if (this.hoverEl.dataset.imgHeight && this.hoverEl.dataset.imgWidth) { + this.hoverEl.style.height = parseFloat(this.hoverEl.dataset.imgHeight) + this.titleEl.offsetHeight + "px"; + this.hoverEl.style.width = parseFloat(this.hoverEl.dataset.imgWidth) + "px"; + } + } + + onHide() { + this.oldPopover = null; + if (this.parent?.hoverPopover === this) { + this.parent.hoverPopover = null; + } + } + + hide() { + this.onTarget = false; + this.detaching = true; + // Once we reach this point, we're committed to closing + + // in case we didn't ever call show() + + + // A timer might be pending to call show() for the first time, make sure + // it doesn't bring us back up after we close + if (this.timer) { + clearTimeout(this.timer); + this.timer = 0; + } + + // Hide our HTML element immediately, even if our leaves might not be + // detachable yet. This makes things more responsive and improves the + // odds of not showing an empty popup that's just going to disappear + // momentarily. + this.hoverEl.hide(); + + // If a file load is in progress, we need to wait until it's finished before + // detaching leaves. Because we set .detaching, The in-progress openFile() + // will call us again when it finishes. + if (this.opening) return; + + // Leave this code here to observe the state of the leaves + const leaves = this.leaves(); + if (leaves.length) { + // Detach all leaves before we unload the popover and remove it from the DOM. + // Each leaf.detach() will trigger layout-changed and the updateLeaves() + // method will then call hide() again when the last one is gone. + // leaves[0].detach(); + // leaves[0].detach(); + this.targetEl.empty(); + } else { + this.parent = null; + this.abortController?.unload(); + this.abortController = undefined; + return this.nativeHide(); + } + } + + nativeHide() { + const {hoverEl, targetEl} = this; + this.state = PopoverState.Hidden; + hoverEl.detach(); + + if (targetEl) { + const parent = targetEl.matchParent(".fs-leaf-view"); + if (parent) popovers.get(parent)?.transition(); + } + + this.onHide(); + this.unload(); + } + + resolveLink(linkText: string, sourcePath: string): TFile | null { + const link = parseLinktext(linkText); + const tFile = link ? this.plugin.app.metadataCache.getFirstLinkpathDest(link.path, sourcePath) : null; + return tFile; + } + + async openLink(linkText: string, sourcePath: string, eState?: EphemeralState, createInLeaf?: WorkspaceLeaf) { + let file = this.resolveLink(linkText, sourcePath); + const link = parseLinktext(linkText); + if (!file && createInLeaf) { + const folder = this.plugin.app.fileManager.getNewFileParent(sourcePath); + file = await this.plugin.app.fileManager.createNewMarkdownFile(folder, link.path); + } + + if (!file) { + // this.displayCreateFileAction(linkText, sourcePath, eState); + return; + } + const {viewRegistry} = this.plugin.app; + const viewType = viewRegistry.typeByExtension[file.extension]; + if (!viewType || !viewRegistry.viewByType[viewType]) { + // this.displayOpenFileAction(file); + return; + } + + eState = Object.assign(this.buildEphemeralState(file, link), eState); + const parentMode = this.getDefaultMode(); + const state = this.buildState(parentMode, eState); + const leaf = await this.openFile(file, state, createInLeaf); + const leafViewType = leaf?.view?.getViewType(); + // console.log(leaf); + if (leafViewType === "image") { + // TODO: temporary workaround to prevent image popover from disappearing immediately when using live preview + if ( + this.parent?.hasOwnProperty("editorEl") && + (this.parent as unknown as MarkdownEditView).editorEl!.hasClass("is-live-preview") + ) { + this.waitTime = 3000; + } + const img = leaf!.view.contentEl.querySelector("img")!; + this.hoverEl.dataset.imgHeight = String(img.naturalHeight); + this.hoverEl.dataset.imgWidth = String(img.naturalWidth); + this.hoverEl.dataset.imgRatio = String(img.naturalWidth / img.naturalHeight); + } else if (leafViewType === "pdf") { + this.hoverEl.style.height = "800px"; + this.hoverEl.style.width = "600px"; + } + if (state.state?.mode === "source") { + this.whenShown(() => { + // Not sure why this is needed, but without it we get issue #186 + if (requireApiVersion("1.0")) (leaf?.view as any)?.editMode?.reinit?.(); + leaf?.view?.setEphemeralState(state.eState); + }); + } + } + + whenShown(callback: () => any) { + // invoke callback once the popover is visible + if (this.detaching) return; + const existingCallback = this.onShowCallback; + this.onShowCallback = () => { + if (this.detaching) return; + callback(); + if (typeof existingCallback === "function") existingCallback(); + }; + if (this.state === PopoverState.Shown) { + this.onShowCallback(); + this.onShowCallback = undefined; + } + } + + async openFile(file: TFile, openState?: OpenViewState, useLeaf?: WorkspaceLeaf) { + if (this.detaching) return; + const leaf = useLeaf ?? this.attachLeaf(); + this.opening = true; + + try { + await leaf.openFile(file, openState); + } catch (e) { + console.error(e); + } finally { + this.opening = false; + if (this.detaching) this.hide(); + } + this.plugin.app.workspace.setActiveLeaf(leaf); + + return leaf; + } + + buildState(parentMode: string, eState?: EphemeralState) { + return { + active: false, // Don't let Obsidian force focus if we have autofocus off + state: {mode: "source"}, // Don't set any state for the view, because this leaf is stayed on another view. + eState: eState, + }; + } + + buildEphemeralState( + file: TFile, + link?: { + path: string; + subpath: string; + }, + ) { + const cache = this.plugin.app.metadataCache.getFileCache(file); + const subpath = cache ? resolveSubpath(cache, link?.subpath || "") : undefined; + const eState: EphemeralState = {subpath: link?.subpath}; + if (subpath) { + eState.line = subpath.start.line; + eState.startLoc = subpath.start; + eState.endLoc = subpath.end || undefined; + } + return eState; + } } From 32b64ea6421e898438221af80b0d51c31cb6986b Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Tue, 23 Jan 2024 16:47:56 +0800 Subject: [PATCH 07/12] chore: bump version --- manifest.json | 2 +- package.json | 2 +- src/floatSearchIndex.ts | 3 ++- versions.json | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 44130be..69bcf86 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "float-search", "name": "Floating Search", - "version": "3.4.7", + "version": "3.4.8", "minAppVersion": "0.15.0", "description": "You can use search view in modal/leaf/popout window now.", "author": "Boninall", diff --git a/package.json b/package.json index e3b6697..3f7942f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "float-search", - "version": "3.4.7", + "version": "3.4.8", "description": "You can use search view in modal/leaf/popout window now.", "main": "main.js", "scripts": { diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index 5137a77..c58db40 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -162,8 +162,9 @@ export default class FloatSearchPlugin extends Plugin { const uninstaller = around(Workspace.prototype, { getLeaf: (next) => function (...args) { - const activeLeaf = this.activeLeaf; + const activeLeaf = (this as Workspace).activeLeaf; if (activeLeaf) { + // @ts-ignore const fsCtnEl = (activeLeaf.parent.containerEl as HTMLElement).parentElement; if (fsCtnEl?.hasClass("fs-content")) { if (activeLeaf.view.getViewType() === "markdown") { diff --git a/versions.json b/versions.json index 82f58ce..360822b 100644 --- a/versions.json +++ b/versions.json @@ -24,5 +24,6 @@ "3.4.4": "0.15.0", "3.4.5": "0.15.0", "3.4.6": "0.15.0", - "3.4.7": "0.15.0" + "3.4.7": "0.15.0", + "3.4.8": "0.15.0" } \ No newline at end of file From 5330b125f0def03d5e75c7e37ebbe150e9ca15ac Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 24 Jan 2024 17:18:54 +0800 Subject: [PATCH 08/12] fix: cannot open leaf when last leaf is pinned --- src/floatSearchIndex.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index c58db40..03c138e 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -170,7 +170,8 @@ export default class FloatSearchPlugin extends Plugin { if (activeLeaf.view.getViewType() === "markdown") { return activeLeaf; } - const newLeaf = self.app.workspace.getUnpinnedLeaf(); + + const newLeaf = self.app.workspace.getMostRecentLeaf(); if (newLeaf) { this.setActiveLeaf(newLeaf); } @@ -207,12 +208,21 @@ export default class FloatSearchPlugin extends Plugin { if (parent === self.app.workspace.rootSplit || (WorkspaceContainer && parent instanceof WorkspaceContainer)) { for (const popover of EmbeddedView.popoversForWindow((parent as WorkspaceContainer).win)) { // Use old API here for compat w/0.14.x - if (old.call(this, cb, popover.rootSplit)) return true; + if (old.call(this, cb, popover.rootSplit)) return false; } } return false; }; }, + setActiveLeaf(old) { + return function (leaf: any, params?: any) { + if (isEmebeddedLeaf(leaf)) { + old.call(this, leaf, params); + leaf.activeTime = 1700000000000; + } + return old.call(this, leaf, params); + }; + }, onDragLeaf(old) { return function (event: MouseEvent, leaf: WorkspaceLeaf) { return old.call(this, event, leaf); @@ -308,7 +318,6 @@ export default class FloatSearchPlugin extends Plugin { patchSearchView() { const updateCurrentState = (state: searchState) => { this.state = state; - console.log(state); this.settings.searchViewState = state as searchState; this.applySettingsUpdate(); }; @@ -406,7 +415,6 @@ export default class FloatSearchPlugin extends Plugin { }) ); searchView.leaf?.rebuildView(); - console.log("Metadata-Style: all property view get patched"); return true; }; this.app.workspace.onLayoutReady(() => { @@ -503,7 +511,6 @@ export default class FloatSearchPlugin extends Plugin { case "split": // @ts-ignore const isExistingLeaf = existingLeaf.find((leaf) => !leaf.parentSplit.parent.side); - console.log(isExistingLeaf); if (isExistingLeaf) { this.app.workspace.revealLeaf(isExistingLeaf); isExistingLeaf.setViewState({ From 667a956f87e5e6333542ce1a64227638d58f614c Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 24 Jan 2024 17:19:29 +0800 Subject: [PATCH 09/12] chore: bump version --- manifest.json | 2 +- package.json | 2 +- versions.json | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 69bcf86..a75561f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "float-search", "name": "Floating Search", - "version": "3.4.8", + "version": "3.4.9", "minAppVersion": "0.15.0", "description": "You can use search view in modal/leaf/popout window now.", "author": "Boninall", diff --git a/package.json b/package.json index 3f7942f..5f89682 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "float-search", - "version": "3.4.8", + "version": "3.4.9", "description": "You can use search view in modal/leaf/popout window now.", "main": "main.js", "scripts": { diff --git a/versions.json b/versions.json index 360822b..2c4a00e 100644 --- a/versions.json +++ b/versions.json @@ -25,5 +25,6 @@ "3.4.5": "0.15.0", "3.4.6": "0.15.0", "3.4.7": "0.15.0", - "3.4.8": "0.15.0" + "3.4.8": "0.15.0", + "3.4.9": "0.15.0" } \ No newline at end of file From 36e22c887d90b1c9b8966edd1f0660ddf96ba573 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 24 Jan 2024 17:43:38 +0800 Subject: [PATCH 10/12] chore: bump version --- manifest.json | 2 +- package.json | 2 +- src/floatSearchIndex.ts | 25 +++++++++++++++++++++++-- versions.json | 3 ++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index a75561f..1872520 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "float-search", "name": "Floating Search", - "version": "3.4.9", + "version": "3.4.10", "minAppVersion": "0.15.0", "description": "You can use search view in modal/leaf/popout window now.", "author": "Boninall", diff --git a/package.json b/package.json index 5f89682..c77738d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "float-search", - "version": "3.4.9", + "version": "3.4.10", "description": "You can use search view in modal/leaf/popout window now.", "main": "main.js", "scripts": { diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index 03c138e..a8ced2c 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -3,7 +3,7 @@ import { App, debounce, Editor, - ExtraButtonComponent, + ExtraButtonComponent, Keymap, Menu, MenuItem, Modal, @@ -610,6 +610,8 @@ class FloatSearchModal extends Modal { private fileEl: HTMLElement; private viewType: string; + private focusdItem: any; + constructor(cb: (state: any) => void, plugin: FloatSearchPlugin, state: any, viewType: string = "search") { super(plugin.app); this.plugin = plugin; @@ -672,6 +674,11 @@ class FloatSearchModal extends Modal { altEnterIconEl.setText("Alt+↵"); altEnterTextEl.setText("Open File and Close"); + const ctrlEnterIconEl = altEnterInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); + const ctrlEnterTextEl = altEnterInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); + altEnterIconEl.setText("Ctrl+↵"); + altEnterTextEl.setText("Create File When Not Exist"); + const tabIconEl = tabInstructionsEl.createSpan({cls: "float-search-modal-instructions-key"}); const tabTextEl = tabInstructionsEl.createSpan({cls: "float-search-modal-instructions-text"}); tabIconEl.setText("Tab/Shift+Tab"); @@ -728,10 +735,12 @@ class FloatSearchModal extends Modal { if (currentView.dom.focusedItem.collapsible) { currentView.dom.focusedItem.setCollapse(false); } + this.focusdItem = currentView.dom.focusedItem; } break; } else { currentView.onKeyArrowDownInFocus(e); + this.focusdItem = currentView.dom.focusedItem; break; } case "ArrowUp": @@ -741,10 +750,15 @@ class FloatSearchModal extends Modal { if (currentView.dom.focusedItem.collapseEl) { currentView.dom.focusedItem.setCollapse(true); } + this.focusdItem = currentView.dom.focusedItem; } break; } else { currentView.onKeyArrowUpInFocus(e); + this.focusdItem = currentView.dom.focusedItem; + if (!currentView.dom.focusedItem.content) { + this.focusdItem = undefined; + } break; } case "ArrowLeft": @@ -754,6 +768,14 @@ class FloatSearchModal extends Modal { currentView.onKeyArrowRightInFocus(e); break; case "Enter": + if (Keymap.isModifier(e, 'Mod') && !this.focusdItem) { + e.preventDefault(); + const fileName = inputEl.value; + const real = fileName.replace(/[/\\?%*:|"<>]/g, '-'); + this.plugin.app.workspace.openLinkText(real, "", true); + this.close(); + break; + } currentView.onKeyEnterInFocus(e); if (e.altKey && currentView.dom.focusedItem) { this.close(); @@ -815,7 +837,6 @@ class FloatSearchModal extends Modal { navigator.clipboard.writeText(text); } break; - } }; } diff --git a/versions.json b/versions.json index 2c4a00e..d3075e2 100644 --- a/versions.json +++ b/versions.json @@ -26,5 +26,6 @@ "3.4.6": "0.15.0", "3.4.7": "0.15.0", "3.4.8": "0.15.0", - "3.4.9": "0.15.0" + "3.4.9": "0.15.0", + "3.4.10": "0.15.0" } \ No newline at end of file From 31fdb1d40901d778e7fe734cc2b59c7c2a3063cd Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 24 Jan 2024 17:48:37 +0800 Subject: [PATCH 11/12] style: update modal style --- styles.css | 82 +++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/styles.css b/styles.css index 053e040..762d84b 100644 --- a/styles.css +++ b/styles.css @@ -7,107 +7,107 @@ If your plugin does not need CSS, delete this file. */ .float-search-modal { - width: 600px; + width: 700px; - padding-bottom: 0; + padding-bottom: 0; } .float-search-modal.float-search-width { - width: 1200px; + width: 1200px; } .float-search-modal-search-ctn, .float-search-modal-file-ctn { - height: 100%; - width: 100%; + height: 100%; + width: 100%; } .float-search-modal-file-ctn .view-header { - display: none; + display: none; } .float-search-modal-content { - height: 800px; - display: flex; - align-items: center; - flex-direction: row; + height: 800px; + display: flex; + align-items: center; + flex-direction: row; } .float-search-modal-file-ctn .view-content { - height: 100%; + height: 100%; } .float-search-modal .modal-close-button { - z-index: 40; + z-index: 40; } .fs-content .workspace-split.mod-vertical { - height: 100%; + height: 100%; } .fs-content { - height: 100%; + height: 100%; } .fs-content .cm-scroller { - margin-top: 20px; + margin-top: 20px; } .fs-block { - height: 100% !important; + height: 100% !important; } .fs-block .workspace-leaf-resize-handle { - display: none; + display: none; } .modal-container.float-search-modal-container.mod-dim { - z-index: 30; + z-index: 30; } .float-search-modal-instructions { - border-top: 1px solid var(--background-secondary); - user-select: none; - font-size: var(--font-ui-smaller); - color: var(--text-muted); - padding: var(--size-4-2); - text-align: center; - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: var(--size-4-3); + border-top: 1px solid var(--background-secondary); + user-select: none; + font-size: var(--font-ui-smaller); + color: var(--text-muted); + padding: var(--size-4-2); + text-align: center; + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--size-4-3); } .float-search-modal-instructions .float-search-modal-instructions-key { - font-weight: var(--font-extrabold); - margin-right: var(--size-2-2); + font-weight: var(--font-extrabold); + margin-right: var(--size-2-2); } .float-search-modal-content:has(.float-search-modal-file-ctn) .float-search-modal-search-ctn { - border-right: 1px solid var(--background-secondary); + border-right: 1px solid var(--background-secondary); } .float-search-modal-file-ctn:has(.mod-active) { - border-bottom: 3px solid var(--color-accent); + border-bottom: 3px solid var(--color-accent); } .float-search-modal-file-ctn:has(.mod-active) .cm-scroller { - background-color: var(--background-primary); + background-color: var(--background-primary); } .float-search-view-switch { - display: flex; - align-items: center; + display: flex; + align-items: center; } .float-search-view-menu .menu-item-icon { - color: var(--text-muted); + color: var(--text-muted); } .float-search-view-menu svg { - fill: none; - stroke: currentcolor; - stroke-width: 2; - stroke-linecap: round; - stroke-linejoin: round; + fill: none; + stroke: currentcolor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; } From 285693757c1023d96948f67c0ca5efe185cae1e8 Mon Sep 17 00:00:00 2001 From: quorafind <951011105@qq.com> Date: Wed, 24 Jan 2024 17:50:30 +0800 Subject: [PATCH 12/12] feat: support create via ctrl+shift+enter --- src/floatSearchIndex.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/floatSearchIndex.ts b/src/floatSearchIndex.ts index a8ced2c..ece99d3 100644 --- a/src/floatSearchIndex.ts +++ b/src/floatSearchIndex.ts @@ -768,9 +768,9 @@ class FloatSearchModal extends Modal { currentView.onKeyArrowRightInFocus(e); break; case "Enter": - if (Keymap.isModifier(e, 'Mod') && !this.focusdItem) { + if (Keymap.isModifier(e, 'Mod') && Keymap.isModifier(e, 'Shift') && !this.focusdItem) { e.preventDefault(); - const fileName = inputEl.value; + const fileName = inputEl.value.trim(); const real = fileName.replace(/[/\\?%*:|"<>]/g, '-'); this.plugin.app.workspace.openLinkText(real, "", true); this.close();