mirror of
https://github.com/quorafind/Obsidian-Float-Search.git
synced 2026-07-22 07:30:25 +00:00
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
This commit is contained in:
parent
8953f2175d
commit
3896bcfc34
2 changed files with 511 additions and 21 deletions
|
|
@ -11,11 +11,16 @@ import {
|
|||
OpenViewState,
|
||||
PaneType,
|
||||
Plugin,
|
||||
prepareFuzzySearch,
|
||||
renderResults,
|
||||
SearchResult,
|
||||
requireApiVersion,
|
||||
Scope,
|
||||
SearchView,
|
||||
setIcon,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
SuggestModal,
|
||||
TAbstractFile,
|
||||
TFile,
|
||||
ViewStateResult,
|
||||
|
|
@ -53,11 +58,15 @@ interface searchState extends Record<string, unknown> {
|
|||
current?: boolean;
|
||||
}
|
||||
|
||||
type CmdkTriggerKey = "Shift" | "Control" | "Alt" | "Meta" | "none";
|
||||
|
||||
interface FloatSearchSettings {
|
||||
searchViewState: searchState;
|
||||
showFilePath: boolean;
|
||||
showInstructions: boolean;
|
||||
defaultViewType: searchType;
|
||||
cmdkTriggerKey: CmdkTriggerKey;
|
||||
cmdkDoubleTapInterval: number;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: FloatSearchSettings = {
|
||||
|
|
@ -72,6 +81,8 @@ const DEFAULT_SETTINGS: FloatSearchSettings = {
|
|||
showFilePath: false,
|
||||
showInstructions: true,
|
||||
defaultViewType: "modal",
|
||||
cmdkTriggerKey: "Shift",
|
||||
cmdkDoubleTapInterval: 300,
|
||||
};
|
||||
|
||||
const allViews: viewType[] = [
|
||||
|
|
@ -127,6 +138,7 @@ export default class FloatSearchPlugin extends Plugin {
|
|||
settings: FloatSearchSettings;
|
||||
private state: searchState;
|
||||
private modal: FloatSearchModal;
|
||||
private cmdkModal: FloatSearchCmdkModal;
|
||||
|
||||
allLoaded: boolean = false;
|
||||
queryLoaded: boolean = false;
|
||||
|
|
@ -168,6 +180,7 @@ export default class FloatSearchPlugin extends Plugin {
|
|||
this.patchSearchView();
|
||||
this.patchVchildren();
|
||||
this.patchDragManager();
|
||||
this.registerDoubleKeyHandler();
|
||||
});
|
||||
|
||||
this.registerObsidianURIHandler();
|
||||
|
|
@ -194,6 +207,7 @@ export default class FloatSearchPlugin extends Plugin {
|
|||
}
|
||||
);
|
||||
this.updateFilePathVisibility();
|
||||
this.addSettingTab(new FloatSearchSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
|
@ -201,6 +215,47 @@ export default class FloatSearchPlugin extends Plugin {
|
|||
this.modal?.close();
|
||||
}
|
||||
|
||||
registerDoubleKeyHandler() {
|
||||
let lastKeyUp = 0;
|
||||
let keyOnly = true;
|
||||
|
||||
this.registerDomEvent(document, "keydown", (e: KeyboardEvent) => {
|
||||
const triggerKey = this.settings.cmdkTriggerKey;
|
||||
if (triggerKey === "none" || e.key !== triggerKey) {
|
||||
keyOnly = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.registerDomEvent(document, "keyup", (e: KeyboardEvent) => {
|
||||
const triggerKey = this.settings.cmdkTriggerKey;
|
||||
if (triggerKey === "none") return;
|
||||
if (e.key === triggerKey) {
|
||||
const now = Date.now();
|
||||
if (
|
||||
keyOnly &&
|
||||
now - lastKeyUp < this.settings.cmdkDoubleTapInterval &&
|
||||
!document.querySelector(
|
||||
".float-search-cmdk-container"
|
||||
)
|
||||
) {
|
||||
lastKeyUp = 0;
|
||||
this.openCmdkModal();
|
||||
} else {
|
||||
lastKeyUp = now;
|
||||
keyOnly = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openCmdkModal() {
|
||||
if (this.cmdkModal) {
|
||||
this.cmdkModal.close();
|
||||
}
|
||||
this.cmdkModal = new FloatSearchCmdkModal(this);
|
||||
this.cmdkModal.open();
|
||||
}
|
||||
|
||||
updateFilePathVisibility() {
|
||||
const { showFilePath } = this.settings;
|
||||
document.body.toggleClass("show-file-path", showFilePath);
|
||||
|
|
@ -1265,6 +1320,66 @@ export default class FloatSearchPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
const TRIGGER_KEY_OPTIONS: Record<CmdkTriggerKey, string> = {
|
||||
Shift: "Double Shift",
|
||||
Control: "Double Ctrl",
|
||||
Alt: "Double Alt",
|
||||
Meta: "Double Meta (Cmd/Win)",
|
||||
none: "Disabled",
|
||||
};
|
||||
|
||||
class FloatSearchSettingTab extends PluginSettingTab {
|
||||
plugin: FloatSearchPlugin;
|
||||
|
||||
constructor(app: App, plugin: FloatSearchPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display() {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl("h2", { text: "Float Search Settings" });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Quick search trigger")
|
||||
.setDesc(
|
||||
"Double-tap this key to open the quick search modal (CMDK)."
|
||||
)
|
||||
.addDropdown((dropdown) => {
|
||||
for (const [value, label] of Object.entries(
|
||||
TRIGGER_KEY_OPTIONS
|
||||
)) {
|
||||
dropdown.addOption(value, label);
|
||||
}
|
||||
dropdown.setValue(this.plugin.settings.cmdkTriggerKey);
|
||||
dropdown.onChange(async (value) => {
|
||||
this.plugin.settings.cmdkTriggerKey =
|
||||
value as CmdkTriggerKey;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Double-tap interval (ms)")
|
||||
.setDesc(
|
||||
"Maximum time between two key presses to trigger quick search. Default: 300ms."
|
||||
)
|
||||
.addSlider((slider) => {
|
||||
slider
|
||||
.setLimits(150, 600, 50)
|
||||
.setValue(this.plugin.settings.cmdkDoubleTapInterval)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.cmdkDoubleTapInterval =
|
||||
value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createInstructionElement(
|
||||
parentEl: HTMLElement,
|
||||
divCls: string,
|
||||
|
|
@ -1305,6 +1420,10 @@ class FloatSearchModal extends Modal {
|
|||
|
||||
private focusdItem: any;
|
||||
|
||||
private debouncedAutoPreview = debounce(() => {
|
||||
this.autoPreviewFocusedItem();
|
||||
}, 150);
|
||||
|
||||
constructor(
|
||||
cb: (state: any) => void,
|
||||
plugin: FloatSearchPlugin,
|
||||
|
|
@ -1422,30 +1541,37 @@ class FloatSearchModal extends Modal {
|
|||
this.searchLeaf.setPinned(true);
|
||||
await this.searchLeaf.setViewState({
|
||||
type: "search",
|
||||
state: this.state,
|
||||
state: { ...this.state, triggerBySelf: true },
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
await this.searchLeaf.view.setState(
|
||||
{ ...this.state, triggerBySelf: true },
|
||||
{ history: false }
|
||||
);
|
||||
const searchComponent = (this.searchLeaf.view as SearchView)
|
||||
.searchComponent;
|
||||
if (searchComponent?.inputEl) {
|
||||
this.state?.current
|
||||
? searchComponent.inputEl.setSelectionRange(0, 0)
|
||||
: searchComponent.inputEl.setSelectionRange(
|
||||
0,
|
||||
this.state?.query?.length
|
||||
);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
initInput() {
|
||||
initInput(retries = 10) {
|
||||
const inputEl = this.contentEl.getElementsByTagName("input")[0];
|
||||
if (!inputEl) {
|
||||
if (retries > 0) {
|
||||
setTimeout(() => this.initInput(retries - 1), 50);
|
||||
}
|
||||
return;
|
||||
}
|
||||
inputEl.focus();
|
||||
inputEl.onkeydown = (e) => {
|
||||
const currentView = this.searchLeaf.view as SearchView;
|
||||
|
|
@ -1466,6 +1592,7 @@ class FloatSearchModal extends Modal {
|
|||
} else {
|
||||
currentView.onKeyArrowDownInFocus(e);
|
||||
this.focusdItem = currentView.dom.focusedItem;
|
||||
this.debouncedAutoPreview();
|
||||
}
|
||||
break;
|
||||
case "ArrowUp":
|
||||
|
|
@ -1487,6 +1614,7 @@ class FloatSearchModal extends Modal {
|
|||
if (!currentView.dom.focusedItem.content) {
|
||||
this.focusdItem = undefined;
|
||||
}
|
||||
this.debouncedAutoPreview();
|
||||
}
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
|
|
@ -1565,7 +1693,7 @@ class FloatSearchModal extends Modal {
|
|||
setTimeout(() => {
|
||||
(
|
||||
this.searchLeaf.view as SearchView
|
||||
).searchComponent.inputEl.focus();
|
||||
).searchComponent?.inputEl?.focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1589,6 +1717,30 @@ class FloatSearchModal extends Modal {
|
|||
};
|
||||
}
|
||||
|
||||
private autoPreviewFocusedItem() {
|
||||
const currentView = this.searchLeaf.view as SearchView;
|
||||
const item = currentView.dom?.focusedItem;
|
||||
if (!item) return;
|
||||
|
||||
const file =
|
||||
item.parent?.file instanceof TFile
|
||||
? item.parent.file
|
||||
: item.file;
|
||||
if (!(file instanceof TFile)) return;
|
||||
|
||||
const state =
|
||||
item.parent?.file instanceof TFile
|
||||
? {
|
||||
match: {
|
||||
content: item.content,
|
||||
matches: item.matches,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
this.initFileView(file, state);
|
||||
}
|
||||
|
||||
initContent() {
|
||||
const { contentEl } = this;
|
||||
contentEl.onclick = (e) => {
|
||||
|
|
@ -1652,7 +1804,7 @@ class FloatSearchModal extends Modal {
|
|||
this.initFileView(file, undefined);
|
||||
(
|
||||
this.searchLeaf.view as SearchView
|
||||
).searchComponent.inputEl.focus();
|
||||
).searchComponent?.inputEl?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1686,7 +1838,7 @@ class FloatSearchModal extends Modal {
|
|||
setTimeout(() => {
|
||||
(
|
||||
this.searchLeaf.view as SearchView
|
||||
).searchComponent.inputEl.focus();
|
||||
).searchComponent?.inputEl?.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
|
@ -1705,7 +1857,7 @@ class FloatSearchModal extends Modal {
|
|||
|
||||
(
|
||||
this.searchLeaf.view as SearchView
|
||||
).searchComponent.inputEl.focus();
|
||||
).searchComponent?.inputEl?.focus();
|
||||
}
|
||||
|
||||
if (e.key === "Tab" && e.ctrlKey) {
|
||||
|
|
@ -1714,7 +1866,7 @@ class FloatSearchModal extends Modal {
|
|||
|
||||
(
|
||||
this.searchLeaf.view as SearchView
|
||||
).searchComponent.inputEl.focus();
|
||||
).searchComponent?.inputEl?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1734,6 +1886,278 @@ class FloatSearchModal extends Modal {
|
|||
});
|
||||
this.fileState = state;
|
||||
|
||||
(this.searchLeaf.view as SearchView).searchComponent.inputEl.focus();
|
||||
(this.searchLeaf.view as SearchView).searchComponent?.inputEl?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
interface CmdkResult {
|
||||
file: TFile;
|
||||
nameMatch: SearchResult | null;
|
||||
pathMatch: SearchResult | null;
|
||||
heading?: string;
|
||||
headingMatch?: SearchResult | null;
|
||||
type: "file" | "heading";
|
||||
}
|
||||
|
||||
class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||
plugin: FloatSearchPlugin;
|
||||
private bodyEl: HTMLElement;
|
||||
private previewEl: HTMLElement | undefined;
|
||||
private fileLeaf: WorkspaceLeaf | undefined;
|
||||
private fileEmbeddedView: EmbeddedView | undefined;
|
||||
private searchAbort: AbortController | null = null;
|
||||
|
||||
constructor(plugin: FloatSearchPlugin) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
this.limit = 50;
|
||||
this.setPlaceholder("Search files and content...");
|
||||
this.setInstructions([
|
||||
{ command: "↑↓", purpose: "Navigate" },
|
||||
{ command: "↵", purpose: "Open" },
|
||||
{ command: "Shift ↵", purpose: "New tab" },
|
||||
{ command: "esc", purpose: "Close" },
|
||||
]);
|
||||
this.modalEl.addClass("float-search-cmdk");
|
||||
this.containerEl.addClass("float-search-cmdk-container");
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
super.onOpen();
|
||||
this.bodyEl = createDiv("float-search-cmdk-body");
|
||||
this.modalEl.insertBefore(
|
||||
this.bodyEl,
|
||||
(this as any).resultContainerEl
|
||||
);
|
||||
this.bodyEl.appendChild((this as any).resultContainerEl);
|
||||
}
|
||||
|
||||
// Required by SuggestModal but unused — we drive the chooser directly
|
||||
getSuggestions(_query: string): CmdkResult[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Override to use progressive rendering via chooser.addSuggestion
|
||||
updateSuggestions() {
|
||||
// Cancel previous in-flight search
|
||||
this.searchAbort?.abort();
|
||||
const abort = (this.searchAbort = new AbortController());
|
||||
const { signal } = abort;
|
||||
|
||||
const chooser = (this as any).chooser;
|
||||
const query = this.inputEl.value;
|
||||
|
||||
const files = this.app.vault.getFiles().filter(
|
||||
(f: TFile) =>
|
||||
f.extension === "md" ||
|
||||
f.extension === "canvas" ||
|
||||
f.extension === "pdf"
|
||||
);
|
||||
|
||||
// Empty query — show recent files
|
||||
if (!query.trim()) {
|
||||
const recent = files
|
||||
.sort((a: TFile, b: TFile) => b.stat.mtime - a.stat.mtime)
|
||||
.slice(0, this.limit)
|
||||
.map((file: TFile) => ({
|
||||
file,
|
||||
nameMatch: null,
|
||||
pathMatch: null,
|
||||
type: "file" as const,
|
||||
}));
|
||||
chooser.setSuggestions(recent);
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 1: file name/path fuzzy (sync, instant)
|
||||
const fuzzy = prepareFuzzySearch(query);
|
||||
const fileResults: CmdkResult[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const nameMatch = fuzzy(file.basename);
|
||||
const pathMatch = fuzzy(file.path);
|
||||
if (nameMatch || pathMatch) {
|
||||
fileResults.push({
|
||||
file,
|
||||
nameMatch,
|
||||
pathMatch,
|
||||
type: "file",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fileResults.sort((a, b) => {
|
||||
const sa = Math.max(
|
||||
a.nameMatch?.score ?? -Infinity,
|
||||
(a.pathMatch?.score ?? -Infinity) * 0.5
|
||||
);
|
||||
const sb = Math.max(
|
||||
b.nameMatch?.score ?? -Infinity,
|
||||
(b.pathMatch?.score ?? -Infinity) * 0.5
|
||||
);
|
||||
return sb - sa;
|
||||
});
|
||||
|
||||
// Render file results immediately
|
||||
chooser.setSuggestions(fileResults.slice(0, this.limit));
|
||||
|
||||
// Phase 2: heading search — progressive, batched via setTimeout
|
||||
if (query.trim().length >= 2) {
|
||||
this.progressiveHeadingSearch(
|
||||
files,
|
||||
fuzzy,
|
||||
chooser,
|
||||
signal
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private progressiveHeadingSearch(
|
||||
files: TFile[],
|
||||
fuzzy: (text: string) => SearchResult | null,
|
||||
chooser: any,
|
||||
signal: AbortSignal
|
||||
) {
|
||||
const BATCH_SIZE = 50; // files per tick
|
||||
let idx = 0;
|
||||
let added = 0;
|
||||
const MAX_HEADING_RESULTS = 20;
|
||||
|
||||
const processBatch = () => {
|
||||
if (signal.aborted) return;
|
||||
|
||||
const end = Math.min(idx + BATCH_SIZE, files.length);
|
||||
for (; idx < end; idx++) {
|
||||
if (added >= MAX_HEADING_RESULTS) return;
|
||||
const file = files[idx];
|
||||
const cache =
|
||||
this.app.metadataCache.getFileCache(file);
|
||||
if (!cache?.headings) continue;
|
||||
|
||||
for (const h of cache.headings) {
|
||||
const headingMatch = fuzzy(h.heading);
|
||||
if (headingMatch) {
|
||||
chooser.addSuggestion({
|
||||
file,
|
||||
nameMatch: null,
|
||||
pathMatch: null,
|
||||
heading: h.heading,
|
||||
headingMatch,
|
||||
type: "heading",
|
||||
});
|
||||
added++;
|
||||
if (added >= MAX_HEADING_RESULTS) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// More files to process — yield to event loop
|
||||
if (idx < files.length && added < MAX_HEADING_RESULTS) {
|
||||
setTimeout(processBatch, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// Start first batch on next microtask so file results render first
|
||||
setTimeout(processBatch, 0);
|
||||
}
|
||||
|
||||
renderSuggestion(result: CmdkResult, el: HTMLElement) {
|
||||
el.addClass("mod-complex");
|
||||
const contentEl = el.createDiv("suggestion-content");
|
||||
|
||||
if (result.type === "heading" && result.heading) {
|
||||
const titleEl = contentEl.createDiv("suggestion-title");
|
||||
if (result.headingMatch) {
|
||||
renderResults(titleEl, result.heading, result.headingMatch);
|
||||
} else {
|
||||
titleEl.setText(result.heading);
|
||||
}
|
||||
const noteEl = contentEl.createDiv("suggestion-note");
|
||||
noteEl.setText(result.file.path);
|
||||
} else {
|
||||
const titleEl = contentEl.createDiv("suggestion-title");
|
||||
if (result.nameMatch) {
|
||||
renderResults(
|
||||
titleEl,
|
||||
result.file.basename,
|
||||
result.nameMatch
|
||||
);
|
||||
} else {
|
||||
titleEl.setText(result.file.basename);
|
||||
}
|
||||
|
||||
const noteEl = contentEl.createDiv("suggestion-note");
|
||||
const parentPath = result.file.parent?.path || "/";
|
||||
if (result.pathMatch) {
|
||||
renderResults(noteEl, parentPath, result.pathMatch);
|
||||
} else {
|
||||
noteEl.setText(parentPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.file.extension !== "md") {
|
||||
el.createDiv("suggestion-aux")
|
||||
.createSpan("suggestion-flair")
|
||||
.setText(result.file.extension);
|
||||
}
|
||||
}
|
||||
|
||||
onChooseSuggestion(
|
||||
result: CmdkResult,
|
||||
evt: MouseEvent | KeyboardEvent
|
||||
) {
|
||||
const leaf = Keymap.isModEvent(evt)
|
||||
? this.app.workspace.getLeaf("tab")
|
||||
: this.app.workspace.getMostRecentLeaf() ??
|
||||
this.app.workspace.getLeaf();
|
||||
|
||||
const eState: Record<string, any> = {};
|
||||
if (result.type === "heading" && result.heading) {
|
||||
// Use subpath to jump to heading
|
||||
eState.subpath = "#" + result.heading;
|
||||
}
|
||||
|
||||
leaf.setViewState({
|
||||
type: result.file.extension === "pdf" ? "pdf" : "markdown",
|
||||
state: { file: result.file.path },
|
||||
active: true,
|
||||
}).then(() => {
|
||||
if (eState.subpath) {
|
||||
// Apply eState after view is ready
|
||||
leaf.setEphemeralState(eState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called by internal Chooser on selection change
|
||||
// @ts-ignore
|
||||
onSelectedChange = debounce(
|
||||
(result: CmdkResult, _evt: Event | null) => {
|
||||
if (result?.file) this.showPreview(result.file);
|
||||
},
|
||||
100
|
||||
);
|
||||
|
||||
async showPreview(file: TFile) {
|
||||
if (!this.previewEl) {
|
||||
this.previewEl = this.bodyEl.createDiv(
|
||||
"float-search-cmdk-preview"
|
||||
);
|
||||
this.modalEl.addClass("float-search-cmdk-expanded");
|
||||
const [leaf, view] = spawnLeafView(
|
||||
this.plugin,
|
||||
this.previewEl
|
||||
);
|
||||
this.fileLeaf = leaf;
|
||||
this.fileEmbeddedView = view;
|
||||
this.fileLeaf.setPinned(true);
|
||||
}
|
||||
await this.fileLeaf!.openFile(file, { active: false });
|
||||
this.inputEl.focus();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.fileLeaf?.detach();
|
||||
this.fileEmbeddedView?.unload();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
styles.css
66
styles.css
|
|
@ -9,6 +9,7 @@ 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 {
|
||||
|
|
@ -147,3 +148,68 @@ body:not(.show-file-path) .search-result-file-title .search-result-file-path {
|
|||
.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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue