Compare commits

...

29 commits

Author SHA1 Message Date
Quorafind
16fcf917a6 chore: update for version.json 2026-03-23 16:39:37 +08:00
Quorafind
fd4bf87a1f chore: update version spec 2026-03-19 09:39:16 +08:00
Quorafind
1e1ee35f9d chore: bump version to 4.3.0 2026-03-18 16:48:35 +08:00
Quorafind
e3201d9ed6 feat(cmdk): add content search and preview scroll-to-position
Add Phase 3 progressive content search to CMDK modal using
prepareSimpleSearch API for substring matching (CJK-friendly).
Uses adaptive yielding (5ms threshold) and offset-based line
computation to avoid costly line splitting.

Also fix preview scroll: pass full result to showPreview so
heading and content results navigate to correct position via
setViewState + setEphemeralState.
2026-03-18 16:47:57 +08:00
Quorafind
8cb09f6345 chore: bump version to 4.2.0 2026-03-17 14:55:15 +08:00
Quorafind
e1b2a6e2db chore: bump version to 4.2.0 2026-03-17 14:55:15 +08:00
Quorafind
3896bcfc34 feat: add CMDK quick search modal with progressive rendering
- Double-tap configurable modifier key (default: Shift) to invoke
  a SuggestModal-based quick search with real-time file preview
- Progressive heading search via chooser.addSuggestion() and batched
  setTimeout, using metadataCache for zero I/O
- File navigation via setViewState with heading subpath support
- Settings tab for trigger key selection and double-tap interval
- Fix inputEl timing race with retry logic and optional chaining
- Fix extra view creation when defaultViewType is window
- Add auto-preview on ArrowUp/ArrowDown in FloatSearchModal
2026-03-17 14:55:15 +08:00
Boninall
8953f2175d
Merge pull request #71 from Quorafind/feat/4-1-0
feat: support pass view state
2025-02-27 16:27:36 +08:00
quorafind
c3d9e96b19 chore: bump version 2025-02-27 16:25:45 +08:00
quorafind
e56db18fae feat: support pass view state 2025-02-27 16:24:55 +08:00
Boninall
aa638ee588
Merge pull request #70 from Quorafind/feat/4-1-0
Feat/4 1 0
2025-02-27 16:06:09 +08:00
quorafind
4bb81fa23e chore: bump version 2025-02-27 16:05:26 +08:00
quorafind
81fb30c532 feat: support features like update window cleartimeout 2025-02-27 16:04:11 +08:00
quorafind
9875f56314 feat: support all kinds of search operator 2025-02-27 16:03:04 +08:00
quorafind
ab7c600b3c fix: should not trigger by float search itself twice 2025-02-27 15:25:39 +08:00
quorafind
9b7a38f0f2 fix: load window incorrectly 2025-02-27 15:12:41 +08:00
quorafind
97ff7789b7 chore: bump version 2024-10-31 16:19:18 +08:00
quorafind
5832628666 feat: support open search view directly 2024-10-31 16:17:09 +08:00
quorafind
f44be02007 chore: update readme 2024-05-11 22:38:49 +08:00
quorafind
912d213100 chore: update readme 2024-05-11 22:37:16 +08:00
Quorafind
460fe4edd0 chore: bump version 2024-04-09 15:45:17 +08:00
Quorafind
8c2817eb22 chore: bump version 2024-04-09 15:42:28 +08:00
Quorafind
207480dcbd Merge branch 'master' of github.com:Quorafind/Obsidian-Float-Search 2024-04-09 15:37:27 +08:00
Quorafind
430f592ed9 chore: bump version 2024-04-09 15:37:00 +08:00
Boninall
82824f4eb2
Merge pull request #50 from Quorafind/feat/file-path
Feat/file path
2024-04-09 15:33:08 +08:00
Quorafind
e07d858159 feat: support show file path 2024-04-09 15:31:16 +08:00
Quorafind
929f25c0bf feat: support show file path 2024-04-09 15:29:44 +08:00
Quorafind
a9913fd278 feat: support show file path 2024-04-09 15:26:28 +08:00
Quorafind
e5480ead33 feat: support toggle file path visibility 2024-04-09 15:13:48 +08:00
8 changed files with 3169 additions and 1339 deletions

View file

@ -1,15 +1,10 @@
{
"id": "float-search",
"name": "Floating Search",
"version": "3.4.10",
"minAppVersion": "0.15.0",
"version": "4.3.0",
"minAppVersion": "1.2.0",
"description": "You can use search view in modal/leaf/popout window now.",
"author": "Boninall",
"authorUrl": "https://github.com/Quorafind",
"fundingUrl": {
"Buy Me a Coffee": "https://www.buymeacoffee.com/boninall",
"爱发电": "https://afdian.net/a/boninall",
"支付宝": "https://cdn.jsdelivr.net/gh/Quorafind/.github@main/IMAGE/%E6%94%AF%E4%BB%98%E5%AE%9D%E4%BB%98%E6%AC%BE%E7%A0%81.jpg"
},
"isDesktopOnly": false
}

View file

@ -1,6 +1,6 @@
{
"name": "float-search",
"version": "3.4.10",
"version": "4.3.0",
"description": "You can use search view in modal/leaf/popout window now.",
"main": "main.js",
"scripts": {
@ -17,7 +17,7 @@
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.14.47",
"obsidian": "latest",
"obsidian": "^1.12.3",
"tslib": "2.4.0",
"typescript": "4.7.4"
},

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -53,7 +53,7 @@ function nosuper<T>(base: new (...args: unknown[]) => T): new () => T {
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;
let parent = plugin.app.workspace.activeLeaf as unknown as EmbeddedViewParent;
if (!parent) parent = leaf as unknown as EmbeddedViewParent;
if (!initiatingEl) initiatingEl = parent?.containerEl;
@ -72,7 +72,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
detaching = false;
opening = false;
rootSplit: WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(window.app.workspace, "vertical");
rootSplit: WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(this.plugin.app.workspace, "vertical");
isPinned = true;
@ -86,16 +86,16 @@ export class EmbeddedView extends nosuper(HoverPopover) {
document: Document = this.targetEl?.ownerDocument ?? window.activeDocument ?? window.document;
id = genId(8);
bounce?: NodeJS.Timeout;
bounce?: number;
boundOnZoomOut: () => void;
originalPath: string; // these are kept to avoid adopting targets w/a different link
originalLinkText: string;
static activePopover?: EmbeddedView;
static activeWindows() {
static activeWindows(plugin: FloatSearchPlugin) {
const windows: Window[] = [window];
const {floatingSplit} = app.workspace;
const {floatingSplit} = plugin.app.workspace;
if (floatingSplit) {
for (const split of floatingSplit.children) {
if (split.win) windows.push(split.win);
@ -104,16 +104,16 @@ export class EmbeddedView extends nosuper(HoverPopover) {
return windows;
}
static containerForDocument(doc: Document) {
if (doc !== document && app.workspace.floatingSplit)
for (const container of app.workspace.floatingSplit.children) {
static containerForDocument(doc: Document, plugin: FloatSearchPlugin) {
if (doc !== document && plugin.app.workspace.floatingSplit)
for (const container of plugin.app.workspace.floatingSplit.children) {
if (container.doc === doc) return container;
}
return app.workspace.rootSplit;
return plugin.app.workspace.rootSplit;
}
static activePopovers() {
return this.activeWindows().flatMap(this.popoversForWindow);
static activePopovers(plugin: FloatSearchPlugin) {
return this.activeWindows(plugin).flatMap(this.popoversForWindow);
}
static popoversForWindow(win?: Window) {
@ -128,8 +128,8 @@ export class EmbeddedView extends nosuper(HoverPopover) {
return el ? popovers.get(el) : undefined;
}
static iteratePopoverLeaves(ws: Workspace, cb: (leaf: WorkspaceLeaf) => boolean | void) {
for (const popover of this.activePopovers()) {
static iteratePopoverLeaves(ws: Workspace, cb: (leaf: WorkspaceLeaf) => boolean | void, plugin: FloatSearchPlugin) {
for (const popover of this.activePopovers(plugin)) {
if (popover.rootSplit && ws.iterateLeaves(cb, popover.rootSplit)) return true;
}
return false;
@ -221,7 +221,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
if (this.shouldShow()) {
if (this.state === PopoverState.Hiding) {
this.state = PopoverState.Shown;
clearTimeout(this.timer);
window.clearTimeout(this.timer);
}
} else {
if (this.state === PopoverState.Showing) {
@ -252,7 +252,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
attachLeaf(): WorkspaceLeaf {
this.rootSplit.getRoot = () => this.plugin.app.workspace[this.document === document ? "rootSplit" : "floatingSplit"]!;
this.rootSplit.getContainer = () => EmbeddedView.containerForDocument(this.document);
this.rootSplit.getContainer = () => EmbeddedView.containerForDocument(this.document, this.plugin);
this.titleEl.insertAdjacentElement("afterend", this.rootSplit.containerEl);
const leaf = this.plugin.app.workspace.createLeafInParent(this.rootSplit, 0);
@ -264,7 +264,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
onload(): void {
super.onload();
this.registerEvent(this.plugin.app.workspace.on("layout-change", this.updateLeaves, this));
this.registerEvent(app.workspace.on("layout-change", () => {
this.registerEvent(this.plugin.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) => {
@ -326,7 +326,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
}
shouldShowChild(): boolean {
return EmbeddedView.activePopovers().some(popover => {
return EmbeddedView.activePopovers(this.plugin).some(popover => {
if (popover !== this && popover.targetEl && this.hoverEl.contains(popover.targetEl)) {
return popover.shouldShow();
}
@ -355,7 +355,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
this.targetEl.appendChild(this.hoverEl);
this.onShow();
app.workspace.onLayoutChange();
this.plugin.app.workspace.onLayoutChange();
// initializingHoverPopovers.remove(this);
// activeHoverPopovers.push(this);
@ -391,7 +391,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
// 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);
window.clearTimeout(this.timer);
this.timer = 0;
}
@ -465,7 +465,7 @@ export class EmbeddedView extends nosuper(HoverPopover) {
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 leaf = await this.openFile(file, state as OpenViewState, createInLeaf);
const leafViewType = leaf?.view?.getViewType();
// console.log(leaf);
if (leafViewType === "image") {

View file

@ -3,13 +3,16 @@ import {
EditorPosition,
EphemeralState,
Loc,
MarkdownPreviewRenderer, MarkdownSubView,
MarkdownPreviewRenderer,
MarkdownSubView,
Plugin,
PluginManifest,
SuggestModal,
TFile, TFolder,
View, WorkspaceItem,
WorkspaceLeaf
TFile,
TFolder,
View,
WorkspaceItem,
WorkspaceLeaf,
} from "obsidian";
import { EmbeddedViewParent } from "../leafView";
@ -20,7 +23,7 @@ interface InternalPlugins {
}
interface OpenViewState {
eState?: EphemeralState;
eState?: Record<string, unknown>;
state?: { mode: string };
active?: boolean;
}
@ -82,7 +85,9 @@ declare module "obsidian" {
};
internalPlugins: {
plugins: InternalPlugins;
getPluginById<T extends keyof InternalPlugins>(id: T): InternalPlugins[T];
getPluginById<T extends keyof InternalPlugins>(
id: T
): InternalPlugins[T];
};
plugins: {
manifests: Record<string, PluginManifest>;
@ -98,6 +103,8 @@ declare module "obsidian" {
viewRegistry: ViewRegistry;
openWithDefaultApp(path: string): void;
dragManager: any;
}
interface ViewRegistry {
@ -110,14 +117,23 @@ declare module "obsidian" {
}
interface WorkspaceParent {
insertChild(index: number, child: WorkspaceItem, resize?: boolean): void;
insertChild(
index: number,
child: WorkspaceItem,
resize?: boolean
): void;
replaceChild(index: number, child: WorkspaceItem, resize?: boolean): void;
replaceChild(
index: number,
child: WorkspaceItem,
resize?: boolean
): void;
removeChild(leaf: WorkspaceLeaf, resize?: boolean): void;
containerEl: HTMLElement;
children: any;
parent: any;
}
interface MarkdownEditView {
@ -125,11 +141,19 @@ declare module "obsidian" {
}
class MarkdownPreviewRendererStatic extends MarkdownPreviewRenderer {
static registerDomEvents(el: HTMLElement, handlerInstance: unknown, cb: (el: HTMLElement) => unknown): void;
static registerDomEvents(
el: HTMLElement,
handlerInstance: unknown,
cb: (el: HTMLElement) => unknown
): void;
}
interface WorkspaceLeaf {
openLinkText(linkText: string, path: string, state?: unknown): Promise<void>;
openLinkText(
linkText: string,
path: string,
state?: unknown
): Promise<void>;
updateHeader(): void;
@ -142,16 +166,25 @@ declare module "obsidian" {
interface Workspace {
recordHistory(leaf: WorkspaceLeaf, pushHistory: boolean): void;
iterateLeaves(callback: (item: WorkspaceLeaf) => boolean | void, item: WorkspaceItem | WorkspaceItem[]): boolean;
iterateLeaves(
callback: (item: WorkspaceLeaf) => boolean | void,
item: WorkspaceItem | WorkspaceItem[]
): boolean;
iterateLeaves(item: WorkspaceItem | WorkspaceItem[], callback: (item: WorkspaceLeaf) => boolean | void): boolean;
iterateLeaves(
item: WorkspaceItem | WorkspaceItem[],
callback: (item: WorkspaceLeaf) => boolean | void
): boolean;
getDropLocation(event: MouseEvent): {
target: WorkspaceItem;
sidedock: boolean;
};
recursiveGetTarget(event: MouseEvent, parent: WorkspaceParent): WorkspaceItem;
recursiveGetTarget(
event: MouseEvent,
parent: WorkspaceParent
): WorkspaceItem;
recordMostRecentOpenedFile(file: TFile): void;
@ -220,7 +253,10 @@ declare module "obsidian" {
}
interface FileManager {
createNewMarkdownFile(folder: TFolder, fileName: string): Promise<TFile>;
createNewMarkdownFile(
folder: TFolder,
fileName: string
): Promise<TFile>;
}
enum PopoverState {

View file

@ -8,15 +8,16 @@ If your plugin does not need CSS, delete this file.
*/
.float-search-modal {
width: 700px;
padding-bottom: 0;
transition: width 0.15s ease;
}
.float-search-modal.float-search-width {
width: 1200px;
}
.float-search-modal-search-ctn, .float-search-modal-file-ctn {
.float-search-modal-search-ctn,
.float-search-modal-file-ctn {
height: 100%;
width: 100%;
}
@ -64,7 +65,6 @@ If your plugin does not need CSS, delete this file.
z-index: 30;
}
.float-search-modal-instructions {
border-top: 1px solid var(--background-secondary);
user-select: none;
@ -83,7 +83,8 @@ If your plugin does not need CSS, delete this file.
margin-right: var(--size-2-2);
}
.float-search-modal-content:has(.float-search-modal-file-ctn) .float-search-modal-search-ctn {
.float-search-modal-content:has(.float-search-modal-file-ctn)
.float-search-modal-search-ctn {
border-right: 1px solid var(--background-secondary);
}
@ -111,3 +112,120 @@ If your plugin does not need CSS, delete this file.
stroke-linecap: round;
stroke-linejoin: round;
}
body:not(.show-file-path) .search-result-file-title .search-result-file-path {
display: none;
}
.show-file-path .search-result-file-title .search-result-file-path {
display: flex;
justify-content: center;
align-items: center;
color: var(--text-muted);
font-size: var(--font-ui-smaller);
font-weight: var(--font-bolder);
margin-right: var(--size-2-2);
padding: 1px;
padding-left: var(--size-4-2);
background-color: var(--background-secondary-alt);
padding-right: var(--size-4-2);
border-radius: var(--radius-s);
--icon-size: 12px;
}
.show-file-path .search-result-file-title .search-result-file-path-icon {
display: flex;
align-items: center;
justify-content: center;
margin-right: var(--size-2-2);
}
.show-file-path .search-result-file-title .search-result-file-path:hover {
background-color: var(--background-secondary);
}
.fs-leaf-view .workspace-split {
background-color: var(--background-primary);
}
/* ── CMDK Modal ── */
.prompt.float-search-cmdk {
width: 700px;
max-width: 90vw;
transition: width 0.15s ease;
}
.prompt.float-search-cmdk.float-search-cmdk-expanded {
width: 1100px;
}
.float-search-cmdk-body {
display: flex;
flex-direction: row;
height: 50vh;
max-height: 600px;
overflow: hidden;
}
.float-search-cmdk-body .prompt-results {
max-height: none;
height: 100%;
overflow-y: auto;
flex: 1 1 auto;
}
.float-search-cmdk-expanded .float-search-cmdk-body .prompt-results {
flex: 0 0 40%;
border-right: 1px solid var(--background-modifier-border);
}
.float-search-cmdk-preview {
flex: 1;
overflow: hidden;
height: 100%;
}
.float-search-cmdk-preview .view-header {
display: none;
}
.float-search-cmdk-preview .view-content {
height: 100%;
}
.float-search-cmdk-preview .fs-block {
height: 100% !important;
}
.float-search-cmdk-preview .fs-content {
height: 100%;
}
.float-search-cmdk-preview .fs-content .workspace-split.mod-vertical {
height: 100%;
}
.float-search-cmdk-preview .workspace-leaf-resize-handle {
display: none;
}
.modal-container.float-search-cmdk-container.mod-dim {
z-index: 30;
}
/* ── Quick Create item ── */
.float-search-cmdk-create-item {
border-top: 1px solid var(--background-modifier-border);
}
.float-search-cmdk-create-item .suggestion-title {
color: var(--text-accent);
font-weight: var(--font-semibold);
}
.float-search-cmdk-create-item .suggestion-flair {
display: flex;
align-items: center;
color: var(--text-accent);
}

View file

@ -27,5 +27,12 @@
"3.4.7": "0.15.0",
"3.4.8": "0.15.0",
"3.4.9": "0.15.0",
"3.4.10": "0.15.0"
"3.4.10": "0.15.0",
"3.5.0": "0.15.0",
"3.5.1": "0.15.0",
"4.0.0": "0.15.0",
"4.1.0": "0.15.0",
"4.1.1": "0.15.0",
"4.2.0": "1.2.0",
"4.3.0": "1.2.0"
}