feat: support show file path

This commit is contained in:
Quorafind 2024-04-09 15:26:28 +08:00
commit a9913fd278
7 changed files with 693 additions and 591 deletions

View file

@ -7,8 +7,6 @@ on:
env:
PLUGIN_NAME: obsidian-float-search
permissions: write-all
jobs:
build:
runs-on: ubuntu-latest

View file

@ -1,7 +1,7 @@
{
"id": "float-search",
"name": "Floating Search",
"version": "3.4.5",
"version": "3.4.10",
"minAppVersion": "0.15.0",
"description": "You can use search view in modal/leaf/popout window now.",
"author": "Boninall",

View file

@ -1,6 +1,6 @@
{
"name": "float-search",
"version": "3.4.5",
"version": "3.4.10",
"description": "You can use search view in modal/leaf/popout window now.",
"main": "main.js",
"scripts": {

View file

@ -1,18 +1,26 @@
import {
addIcon,
App,
Editor, ExtraButtonComponent,
Menu, MenuItem,
Modal, OpenViewState, PaneType,
Plugin, SearchView, setIcon, Setting,
Editor,
ExtraButtonComponent, Keymap,
Menu,
MenuItem,
Modal,
OpenViewState,
PaneType,
Plugin,
Scope,
SearchView, setIcon, Setting,
TAbstractFile,
TFile, ViewStateResult,
TFile,
Workspace,
WorkspaceContainer, WorkspaceItem,
WorkspaceContainer,
WorkspaceItem,
WorkspaceLeaf
} from 'obsidian';
import { EmbeddedView, isEmebeddedLeaf, spawnLeafView } from "./leafView";
import { around } from "monkey-around";
import { debounce } from "obsidian";
type sortOrder =
"alphabetical"
@ -82,6 +90,7 @@ const initSearchViewWithLeaf = async (app: App, type: PaneType | 'sidebar', stat
active: true,
state: state
});
setTimeout(() => {
const inputEl = leaf.containerEl.getElementsByTagName("input")[0];
inputEl.focus();
@ -93,38 +102,30 @@ export default class FloatSearchPlugin extends Plugin {
private state: any;
private modal: FloatSearchModal;
private applyStateDebounceTimer = 0;
private applySettingsDebounceTimer = 0;
patchedDomChildren = false;
public applySettingsUpdate() {
clearTimeout(this.applySettingsDebounceTimer);
this.applySettingsDebounceTimer = window.setTimeout(async () => {
public applySettingsUpdate = debounce(async () => {
await this.saveSettings();
}, 1000);
}
private applyStateUpdate() {
this.applyStateDebounceTimer = window.setTimeout(() => {
clearTimeout(this.applyStateDebounceTimer);
private applyStateUpdate = debounce(() => {
this.state = {
...this.state,
query: "",
};
}, 30000);
}
async onload() {
await this.loadSettings();
this.initState();
this.registerIcons();
this.app.workspace.onLayoutReady(() => {
this.patchWorkspace();
this.patchWorkspaceLeaf();
this.patchSearchView();
this.patchVchildren();
});
this.registerObsidianURIHandler();
this.registerObsidianCommands();
@ -175,17 +176,21 @@ export default class FloatSearchPlugin extends Plugin {
patchWorkspace() {
let layoutChanging = false;
const self = this;
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") {
return activeLeaf;
}
const newLeaf = app.workspace.getUnpinnedLeaf();
const newLeaf = self.app.workspace.getMostRecentLeaf();
if (newLeaf) {
this.setActiveLeaf(newLeaf);
}
@ -219,15 +224,26 @@ 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;
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);
@ -378,17 +394,22 @@ 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;
const searchViewConstructor = searchView.constructor;
const self = this;
this.register(
around(searchViewConstructor.prototype, {
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);
@ -400,7 +421,6 @@ export default class FloatSearchPlugin extends Plugin {
layoutMenu.showAtPosition({x: viewSwitchButtonPos.x, y: viewSwitchButtonPos.y + 30});
});
targetEl.parentElement.insertBefore(viewSwitchEl, targetEl);
if (!this.hidePathToggle) {
this.hidePathToggle = new Setting(this.searchParamsContainerEl).setName('Show file path').addToggle((toggle) => {
toggle.toggleEl.toggleClass('mod-small', true);
@ -422,11 +442,21 @@ export default class FloatSearchPlugin extends Plugin {
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(),
});
}
};
}
})
);
searchView.leaf?.rebuildView();
console.log("Metadata-Style: all property view get patched");
return true;
};
this.app.workspace.onLayoutReady(() => {
@ -490,7 +520,7 @@ export default class FloatSearchPlugin extends Plugin {
...this.state,
query: path.query,
current: false
}, true, true));
}, true, false));
}
private createCommand(options: {
@ -537,8 +567,8 @@ export default class FloatSearchPlugin extends Plugin {
this.addCommand({
id: 'search-obsidian-globally-state',
name: 'Search obsidian globally (with last state)',
callback: () => this.initModal({...this.state, query: "", current: false}, true, false)
name: 'Search Obsidian Globally (With Last State)',
callback: () => this.initModal({...this.state, query: this.state.query, current: false}, true, false)
});
@ -562,7 +592,35 @@ export default class FloatSearchPlugin extends Plugin {
this.addCommand({
id: `open-search-view-${type}`,
name: `Open search view (${type})`,
callback: async () => initSearchViewWithLeaf(this.app, type)
callback: async () => {
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);
return;
}
await initSearchViewWithLeaf(this.app, type);
break;
case "tab":
case "split":
// @ts-ignore
const isExistingLeaf = existingLeaf.find((leaf) => !leaf.parentSplit.parent.side);
if (isExistingLeaf) {
this.app.workspace.revealLeaf(isExistingLeaf);
isExistingLeaf.setViewState({
type: "search",
active: true,
state: this.state
});
return;
}
await initSearchViewWithLeaf(this.app, type);
break;
}
}
});
}
}
@ -649,6 +707,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;
@ -711,6 +771,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");
@ -767,10 +832,16 @@ 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":
@ -780,10 +851,19 @@ 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":
@ -793,6 +873,14 @@ class FloatSearchModal extends Modal {
currentView.onKeyArrowRightInFocus(e);
break;
case "Enter":
if (Keymap.isModifier(e, 'Mod') && Keymap.isModifier(e, 'Shift') && !this.focusdItem) {
e.preventDefault();
const fileName = inputEl.value.trim();
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();
@ -874,6 +962,9 @@ 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;

View file

@ -28,8 +28,9 @@ export interface EmbeddedViewParent {
view?: View;
dom?: HTMLElement;
}
const popovers = new WeakMap<Element, EmbeddedView>();
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
@ -60,7 +61,7 @@ export const spawnLeafView = (plugin: FloatSearchPlugin, initiatingEl?: HTMLElem
const hoverPopover = new EmbeddedView(parent, initiatingEl!, plugin, undefined, onShowCallback);
return [hoverPopover.attachLeaf(), hoverPopover];
}
};
export class EmbeddedView extends nosuper(HoverPopover) {
onTarget: boolean;
@ -94,7 +95,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
static activeWindows() {
const windows: Window[] = [window];
const { floatingSplit } = app.workspace;
const {floatingSplit} = app.workspace;
if (floatingSplit) {
for (const split of floatingSplit.children) {
if (split.win) windows.push(split.win);
@ -136,7 +137,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
hoverEl: HTMLElement = this.document.defaultView!.createDiv({
cls: "fs-block fs-leaf-view",
attr: { id: "fs-" + this.id },
attr: {id: "fs-" + this.id},
});
constructor(
@ -157,7 +158,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
this.parent = parent;
this.waitTime = waitTime;
this.state = PopoverState.Showing;
const { hoverEl } = this;
const {hoverEl} = this;
this.abortController!.load();
this.show();
@ -178,7 +179,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
_setActive(evt: MouseEvent) {
evt.preventDefault();
evt.stopPropagation();
this.plugin.app.workspace.setActiveLeaf(this.leaves()[0], {focus: true})
this.plugin.app.workspace.setActiveLeaf(this.leaves()[0], {focus: true});
}
getDefaultMode() {
@ -270,7 +271,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
if (item instanceof WorkspaceTabs) {
this.rootSplit.replaceChild(index, item.children[0]);
}
})
});
}));
}
@ -289,7 +290,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
() => {
this.hoverEl.toggleClass("is-new", false);
},
{ once: true, capture: true },
{once: true, capture: true},
);
if (this.parent) {
@ -301,11 +302,11 @@ export class EmbeddedView extends nosuper(HoverPopover) {
viewHeaderEl?.remove();
const sizer = this.hoverEl.querySelector(".workspace-leaf");
if(sizer) this.hoverEl.appendChild(sizer);
if (sizer) this.hoverEl.appendChild(sizer);
// Remove original inline tilte;
const inlineTitle = this.hoverEl.querySelector(".inline-title");
if(inlineTitle) inlineTitle.remove();
if (inlineTitle) inlineTitle.remove();
this.onShowCallback?.();
this.onShowCallback = undefined; // only call it once
@ -313,7 +314,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
detect(el: HTMLElement) {
// TODO: may not be needed? the mouseover/out handers handle most detection use cases
const { targetEl } = this;
const {targetEl} = this;
if (targetEl) {
this.onTarget = el === targetEl || targetEl.contains(el);
@ -423,7 +424,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
}
nativeHide() {
const { hoverEl, targetEl } = this;
const {hoverEl, targetEl} = this;
this.state = PopoverState.Hidden;
hoverEl.detach();
@ -454,7 +455,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
// this.displayCreateFileAction(linkText, sourcePath, eState);
return;
}
const { viewRegistry } = this.plugin.app;
const {viewRegistry} = this.plugin.app;
const viewType = viewRegistry.typeByExtension[file.extension];
if (!viewType || !viewRegistry.viewByType[viewType]) {
// this.displayOpenFileAction(file);
@ -486,7 +487,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
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?.();
if (requireApiVersion("1.0")) (leaf?.view as any)?.editMode?.reinit?.();
leaf?.view?.setEphemeralState(state.eState);
});
}
@ -528,7 +529,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
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.
state: {mode: "source"}, // Don't set any state for the view, because this leaf is stayed on another view.
eState: eState,
};
}
@ -542,7 +543,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
) {
const cache = this.plugin.app.metadataCache.getFileCache(file);
const subpath = cache ? resolveSubpath(cache, link?.subpath || "") : undefined;
const eState: EphemeralState = { subpath: link?.subpath };
const eState: EphemeralState = {subpath: link?.subpath};
if (subpath) {
eState.line = subpath.start.line;
eState.startLoc = subpath.start;

View file

@ -7,7 +7,11 @@ If your plugin does not need CSS, delete this file.
*/
.float-search-modal {
<<<<<<< HEAD
width: 600px;
=======
width: 700px;
>>>>>>> 285693757c1023d96948f67c0ca5efe185cae1e8
padding-bottom: 0;
}
@ -110,6 +114,7 @@ If your plugin does not need CSS, delete this file.
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
<<<<<<< HEAD
}
body:not(.show-file-path) .search-result-file-title .search-result-file-path {
@ -142,4 +147,6 @@ body:not(.show-file-path) .search-result-file-title .search-result-file-path {
.show-file-path .search-result-file-title .search-result-file-path:hover {
background-color: var(--background-secondary);
=======
>>>>>>> 285693757c1023d96948f67c0ca5efe185cae1e8
}

View file

@ -22,5 +22,10 @@
"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",
"3.4.7": "0.15.0",
"3.4.8": "0.15.0",
"3.4.9": "0.15.0",
"3.4.10": "0.15.0"
}