mirror of
https://github.com/quorafind/Obsidian-Float-Search.git
synced 2026-07-22 07:30:25 +00:00
Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16fcf917a6 | ||
|
|
fd4bf87a1f | ||
|
|
1e1ee35f9d | ||
|
|
e3201d9ed6 | ||
|
|
8cb09f6345 | ||
|
|
e1b2a6e2db | ||
|
|
3896bcfc34 |
6 changed files with 831 additions and 39 deletions
|
|
@ -1,15 +1,10 @@
|
||||||
{
|
{
|
||||||
"id": "float-search",
|
"id": "float-search",
|
||||||
"name": "Floating Search",
|
"name": "Floating Search",
|
||||||
"version": "4.1.1",
|
"version": "4.3.0",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "1.2.0",
|
||||||
"description": "You can use search view in modal/leaf/popout window now.",
|
"description": "You can use search view in modal/leaf/popout window now.",
|
||||||
"author": "Boninall",
|
"author": "Boninall",
|
||||||
"authorUrl": "https://github.com/Quorafind",
|
"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
|
"isDesktopOnly": false
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "float-search",
|
"name": "float-search",
|
||||||
"version": "4.1.1",
|
"version": "4.3.0",
|
||||||
"description": "You can use search view in modal/leaf/popout window now.",
|
"description": "You can use search view in modal/leaf/popout window now.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
"@typescript-eslint/parser": "5.29.0",
|
"@typescript-eslint/parser": "5.29.0",
|
||||||
"builtin-modules": "3.3.0",
|
"builtin-modules": "3.3.0",
|
||||||
"esbuild": "0.14.47",
|
"esbuild": "0.14.47",
|
||||||
"obsidian": "^1.7.2",
|
"obsidian": "^1.12.3",
|
||||||
"tslib": "2.4.0",
|
"tslib": "2.4.0",
|
||||||
"typescript": "4.7.4"
|
"typescript": "4.7.4"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ importers:
|
||||||
specifier: 0.14.47
|
specifier: 0.14.47
|
||||||
version: 0.14.47
|
version: 0.14.47
|
||||||
obsidian:
|
obsidian:
|
||||||
specifier: ^1.7.2
|
specifier: ^1.12.3
|
||||||
version: 1.7.2(@codemirror/state@6.2.1)(@codemirror/view@6.18.1)
|
version: 1.12.3(@codemirror/state@6.2.1)(@codemirror/view@6.18.1)
|
||||||
tslib:
|
tslib:
|
||||||
specifier: 2.4.0
|
specifier: 2.4.0
|
||||||
version: 2.4.0
|
version: 2.4.0
|
||||||
|
|
@ -486,7 +486,7 @@ packages:
|
||||||
|
|
||||||
glob@7.2.3:
|
glob@7.2.3:
|
||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Glob versions prior to v9 are no longer supported
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
globals@13.24.0:
|
globals@13.24.0:
|
||||||
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
||||||
|
|
@ -602,11 +602,11 @@ packages:
|
||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
obsidian@1.7.2:
|
obsidian@1.12.3:
|
||||||
resolution: {integrity: sha512-k9hN9brdknJC+afKr5FQzDRuEFGDKbDjfCazJwpgibwCAoZNYHYV8p/s3mM8I6AsnKrPKNXf8xGuMZ4enWelZQ==}
|
resolution: {integrity: sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@codemirror/state': ^6.0.0
|
'@codemirror/state': 6.5.0
|
||||||
'@codemirror/view': ^6.0.0
|
'@codemirror/view': 6.38.6
|
||||||
|
|
||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
@ -1316,7 +1316,7 @@ snapshots:
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
obsidian@1.7.2(@codemirror/state@6.2.1)(@codemirror/view@6.18.1):
|
obsidian@1.12.3(@codemirror/state@6.2.1)(@codemirror/view@6.18.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/state': 6.2.1
|
'@codemirror/state': 6.2.1
|
||||||
'@codemirror/view': 6.18.1
|
'@codemirror/view': 6.18.1
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,17 @@ import {
|
||||||
OpenViewState,
|
OpenViewState,
|
||||||
PaneType,
|
PaneType,
|
||||||
Plugin,
|
Plugin,
|
||||||
|
prepareFuzzySearch,
|
||||||
|
prepareSimpleSearch,
|
||||||
|
renderResults,
|
||||||
|
SearchResult,
|
||||||
requireApiVersion,
|
requireApiVersion,
|
||||||
Scope,
|
Scope,
|
||||||
SearchView,
|
SearchView,
|
||||||
setIcon,
|
setIcon,
|
||||||
|
PluginSettingTab,
|
||||||
Setting,
|
Setting,
|
||||||
|
SuggestModal,
|
||||||
TAbstractFile,
|
TAbstractFile,
|
||||||
TFile,
|
TFile,
|
||||||
ViewStateResult,
|
ViewStateResult,
|
||||||
|
|
@ -53,11 +59,18 @@ interface searchState extends Record<string, unknown> {
|
||||||
current?: boolean;
|
current?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CmdkTriggerKey = "Shift" | "Control" | "Alt" | "Meta" | "none";
|
||||||
|
|
||||||
interface FloatSearchSettings {
|
interface FloatSearchSettings {
|
||||||
searchViewState: searchState;
|
searchViewState: searchState;
|
||||||
showFilePath: boolean;
|
showFilePath: boolean;
|
||||||
showInstructions: boolean;
|
showInstructions: boolean;
|
||||||
defaultViewType: searchType;
|
defaultViewType: searchType;
|
||||||
|
cmdkTriggerKey: CmdkTriggerKey;
|
||||||
|
cmdkDoubleTapInterval: number;
|
||||||
|
cmdkQuickCreate: boolean;
|
||||||
|
cmdkQuickCreateFolder: string;
|
||||||
|
cmdkQuickCreateTitleFormat: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: FloatSearchSettings = {
|
const DEFAULT_SETTINGS: FloatSearchSettings = {
|
||||||
|
|
@ -72,6 +85,11 @@ const DEFAULT_SETTINGS: FloatSearchSettings = {
|
||||||
showFilePath: false,
|
showFilePath: false,
|
||||||
showInstructions: true,
|
showInstructions: true,
|
||||||
defaultViewType: "modal",
|
defaultViewType: "modal",
|
||||||
|
cmdkTriggerKey: "Shift",
|
||||||
|
cmdkDoubleTapInterval: 300,
|
||||||
|
cmdkQuickCreate: false,
|
||||||
|
cmdkQuickCreateFolder: "",
|
||||||
|
cmdkQuickCreateTitleFormat: "YYYYMMDDHHmmss",
|
||||||
};
|
};
|
||||||
|
|
||||||
const allViews: viewType[] = [
|
const allViews: viewType[] = [
|
||||||
|
|
@ -127,6 +145,7 @@ export default class FloatSearchPlugin extends Plugin {
|
||||||
settings: FloatSearchSettings;
|
settings: FloatSearchSettings;
|
||||||
private state: searchState;
|
private state: searchState;
|
||||||
private modal: FloatSearchModal;
|
private modal: FloatSearchModal;
|
||||||
|
private cmdkModal: FloatSearchCmdkModal;
|
||||||
|
|
||||||
allLoaded: boolean = false;
|
allLoaded: boolean = false;
|
||||||
queryLoaded: boolean = false;
|
queryLoaded: boolean = false;
|
||||||
|
|
@ -168,6 +187,7 @@ export default class FloatSearchPlugin extends Plugin {
|
||||||
this.patchSearchView();
|
this.patchSearchView();
|
||||||
this.patchVchildren();
|
this.patchVchildren();
|
||||||
this.patchDragManager();
|
this.patchDragManager();
|
||||||
|
this.registerDoubleKeyHandler();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.registerObsidianURIHandler();
|
this.registerObsidianURIHandler();
|
||||||
|
|
@ -194,6 +214,7 @@ export default class FloatSearchPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.updateFilePathVisibility();
|
this.updateFilePathVisibility();
|
||||||
|
this.addSettingTab(new FloatSearchSettingTab(this.app, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
|
|
@ -201,6 +222,47 @@ export default class FloatSearchPlugin extends Plugin {
|
||||||
this.modal?.close();
|
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() {
|
updateFilePathVisibility() {
|
||||||
const { showFilePath } = this.settings;
|
const { showFilePath } = this.settings;
|
||||||
document.body.toggleClass("show-file-path", showFilePath);
|
document.body.toggleClass("show-file-path", showFilePath);
|
||||||
|
|
@ -1265,6 +1327,114 @@ 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
containerEl.createEl("h3", { text: "Quick Create" });
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Enable quick create")
|
||||||
|
.setDesc(
|
||||||
|
"When no exact match is found in quick search, show an option to create a new note with the search text as content."
|
||||||
|
)
|
||||||
|
.addToggle((toggle) => {
|
||||||
|
toggle
|
||||||
|
.setValue(this.plugin.settings.cmdkQuickCreate)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.cmdkQuickCreate = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Quick create folder")
|
||||||
|
.setDesc(
|
||||||
|
"Folder to create new notes in. Leave empty for vault root."
|
||||||
|
)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder("e.g. Inbox")
|
||||||
|
.setValue(this.plugin.settings.cmdkQuickCreateFolder)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.cmdkQuickCreateFolder =
|
||||||
|
value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Title format")
|
||||||
|
.setDesc(
|
||||||
|
"Timestamp format for the note title. Tokens: YYYY, MM, DD, HH, mm, ss."
|
||||||
|
)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder("YYYYMMDDHHmmss")
|
||||||
|
.setValue(
|
||||||
|
this.plugin.settings.cmdkQuickCreateTitleFormat
|
||||||
|
)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.cmdkQuickCreateTitleFormat =
|
||||||
|
value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createInstructionElement(
|
function createInstructionElement(
|
||||||
parentEl: HTMLElement,
|
parentEl: HTMLElement,
|
||||||
divCls: string,
|
divCls: string,
|
||||||
|
|
@ -1305,6 +1475,10 @@ class FloatSearchModal extends Modal {
|
||||||
|
|
||||||
private focusdItem: any;
|
private focusdItem: any;
|
||||||
|
|
||||||
|
private debouncedAutoPreview = debounce(() => {
|
||||||
|
this.autoPreviewFocusedItem();
|
||||||
|
}, 150);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
cb: (state: any) => void,
|
cb: (state: any) => void,
|
||||||
plugin: FloatSearchPlugin,
|
plugin: FloatSearchPlugin,
|
||||||
|
|
@ -1422,30 +1596,37 @@ class FloatSearchModal extends Modal {
|
||||||
this.searchLeaf.setPinned(true);
|
this.searchLeaf.setPinned(true);
|
||||||
await this.searchLeaf.setViewState({
|
await this.searchLeaf.setViewState({
|
||||||
type: "search",
|
type: "search",
|
||||||
state: this.state,
|
state: { ...this.state, triggerBySelf: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await this.searchLeaf.view.setState(this.state, {
|
await this.searchLeaf.view.setState(
|
||||||
history: false,
|
{ ...this.state, triggerBySelf: true },
|
||||||
});
|
{ history: false }
|
||||||
this.state?.current
|
);
|
||||||
? (
|
const searchComponent = (this.searchLeaf.view as SearchView)
|
||||||
this.searchLeaf.view as SearchView
|
.searchComponent;
|
||||||
).searchComponent.inputEl.setSelectionRange(0, 0)
|
if (searchComponent?.inputEl) {
|
||||||
: (
|
this.state?.current
|
||||||
this.searchLeaf.view as SearchView
|
? searchComponent.inputEl.setSelectionRange(0, 0)
|
||||||
).searchComponent.inputEl.setSelectionRange(
|
: searchComponent.inputEl.setSelectionRange(
|
||||||
0,
|
0,
|
||||||
this.state?.query?.length
|
this.state?.query?.length
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initInput() {
|
initInput(retries = 10) {
|
||||||
const inputEl = this.contentEl.getElementsByTagName("input")[0];
|
const inputEl = this.contentEl.getElementsByTagName("input")[0];
|
||||||
|
if (!inputEl) {
|
||||||
|
if (retries > 0) {
|
||||||
|
setTimeout(() => this.initInput(retries - 1), 50);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
inputEl.focus();
|
inputEl.focus();
|
||||||
inputEl.onkeydown = (e) => {
|
inputEl.onkeydown = (e) => {
|
||||||
const currentView = this.searchLeaf.view as SearchView;
|
const currentView = this.searchLeaf.view as SearchView;
|
||||||
|
|
@ -1466,6 +1647,7 @@ class FloatSearchModal extends Modal {
|
||||||
} else {
|
} else {
|
||||||
currentView.onKeyArrowDownInFocus(e);
|
currentView.onKeyArrowDownInFocus(e);
|
||||||
this.focusdItem = currentView.dom.focusedItem;
|
this.focusdItem = currentView.dom.focusedItem;
|
||||||
|
this.debouncedAutoPreview();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "ArrowUp":
|
case "ArrowUp":
|
||||||
|
|
@ -1487,6 +1669,7 @@ class FloatSearchModal extends Modal {
|
||||||
if (!currentView.dom.focusedItem.content) {
|
if (!currentView.dom.focusedItem.content) {
|
||||||
this.focusdItem = undefined;
|
this.focusdItem = undefined;
|
||||||
}
|
}
|
||||||
|
this.debouncedAutoPreview();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "ArrowLeft":
|
case "ArrowLeft":
|
||||||
|
|
@ -1565,7 +1748,7 @@ class FloatSearchModal extends Modal {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(
|
(
|
||||||
this.searchLeaf.view as SearchView
|
this.searchLeaf.view as SearchView
|
||||||
).searchComponent.inputEl.focus();
|
).searchComponent?.inputEl?.focus();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1589,6 +1772,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() {
|
initContent() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.onclick = (e) => {
|
contentEl.onclick = (e) => {
|
||||||
|
|
@ -1652,7 +1859,7 @@ class FloatSearchModal extends Modal {
|
||||||
this.initFileView(file, undefined);
|
this.initFileView(file, undefined);
|
||||||
(
|
(
|
||||||
this.searchLeaf.view as SearchView
|
this.searchLeaf.view as SearchView
|
||||||
).searchComponent.inputEl.focus();
|
).searchComponent?.inputEl?.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1686,7 +1893,7 @@ class FloatSearchModal extends Modal {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(
|
(
|
||||||
this.searchLeaf.view as SearchView
|
this.searchLeaf.view as SearchView
|
||||||
).searchComponent.inputEl.focus();
|
).searchComponent?.inputEl?.focus();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1705,7 +1912,7 @@ class FloatSearchModal extends Modal {
|
||||||
|
|
||||||
(
|
(
|
||||||
this.searchLeaf.view as SearchView
|
this.searchLeaf.view as SearchView
|
||||||
).searchComponent.inputEl.focus();
|
).searchComponent?.inputEl?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === "Tab" && e.ctrlKey) {
|
if (e.key === "Tab" && e.ctrlKey) {
|
||||||
|
|
@ -1714,7 +1921,7 @@ class FloatSearchModal extends Modal {
|
||||||
|
|
||||||
(
|
(
|
||||||
this.searchLeaf.view as SearchView
|
this.searchLeaf.view as SearchView
|
||||||
).searchComponent.inputEl.focus();
|
).searchComponent?.inputEl?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1734,6 +1941,512 @@ class FloatSearchModal extends Modal {
|
||||||
});
|
});
|
||||||
this.fileState = state;
|
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;
|
||||||
|
content?: string;
|
||||||
|
contentMatch?: SearchResult | null;
|
||||||
|
line?: number;
|
||||||
|
type: "file" | "heading" | "content" | "create";
|
||||||
|
createQuery?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there is an exact name match (for quick-create gating)
|
||||||
|
const queryLower = query.trim().toLowerCase();
|
||||||
|
const hasExactMatch = fileResults.some(
|
||||||
|
(r) => r.file.basename.toLowerCase() === queryLower
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultsToShow = fileResults.slice(0, this.limit);
|
||||||
|
|
||||||
|
// Append "quick create" option when enabled and no exact match
|
||||||
|
if (
|
||||||
|
this.plugin.settings.cmdkQuickCreate &&
|
||||||
|
!hasExactMatch &&
|
||||||
|
query.trim().length > 0
|
||||||
|
) {
|
||||||
|
resultsToShow.push({
|
||||||
|
file: null as any,
|
||||||
|
nameMatch: null,
|
||||||
|
pathMatch: null,
|
||||||
|
type: "create",
|
||||||
|
createQuery: query.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render file results immediately
|
||||||
|
chooser.setSuggestions(resultsToShow);
|
||||||
|
|
||||||
|
// Phase 2: heading search — progressive, batched via setTimeout
|
||||||
|
if (query.trim().length >= 2) {
|
||||||
|
this.progressiveHeadingSearch(
|
||||||
|
files,
|
||||||
|
fuzzy,
|
||||||
|
chooser,
|
||||||
|
signal
|
||||||
|
);
|
||||||
|
|
||||||
|
// Phase 3: content search — progressive, async (reads file content)
|
||||||
|
this.progressiveContentSearch(
|
||||||
|
files,
|
||||||
|
query,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private progressiveContentSearch(
|
||||||
|
files: TFile[],
|
||||||
|
query: string,
|
||||||
|
chooser: any,
|
||||||
|
signal: AbortSignal
|
||||||
|
) {
|
||||||
|
const BATCH_SIZE = 50;
|
||||||
|
const MAX_CONTENT_RESULTS = 20;
|
||||||
|
const DURATION_LIMIT = 5; // ms before yielding
|
||||||
|
const simpleSearch = prepareSimpleSearch(query);
|
||||||
|
let idx = 0;
|
||||||
|
let added = 0;
|
||||||
|
|
||||||
|
const processBatch = async () => {
|
||||||
|
if (signal.aborted) return;
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
for (; idx < files.length; idx++) {
|
||||||
|
if (signal.aborted || added >= MAX_CONTENT_RESULTS) return;
|
||||||
|
|
||||||
|
// Adaptive yielding: check time every BATCH_SIZE items
|
||||||
|
if (idx % BATCH_SIZE === 0 && idx > 0) {
|
||||||
|
if (performance.now() - start > DURATION_LIMIT) {
|
||||||
|
setTimeout(processBatch, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = files[idx];
|
||||||
|
if (file.extension !== "md") continue;
|
||||||
|
|
||||||
|
let text: string;
|
||||||
|
try {
|
||||||
|
text = await this.app.vault.cachedRead(file);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = simpleSearch(text);
|
||||||
|
if (!result) continue;
|
||||||
|
|
||||||
|
// Compute line number and context from first match offset
|
||||||
|
const matchStart = result.matches[0][0];
|
||||||
|
let line = 0;
|
||||||
|
let lineStart = 0;
|
||||||
|
for (let i = 0; i < matchStart; i++) {
|
||||||
|
if (text.charCodeAt(i) === 10) {
|
||||||
|
line++;
|
||||||
|
lineStart = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let lineEnd = text.indexOf("\n", lineStart);
|
||||||
|
if (lineEnd === -1) lineEnd = text.length;
|
||||||
|
const lineText = text.substring(lineStart, lineEnd).trim();
|
||||||
|
|
||||||
|
// Recompute match on the line for highlight offsets
|
||||||
|
const lineMatch = simpleSearch(lineText);
|
||||||
|
|
||||||
|
chooser.addSuggestion({
|
||||||
|
file,
|
||||||
|
nameMatch: null,
|
||||||
|
pathMatch: null,
|
||||||
|
content: lineText,
|
||||||
|
contentMatch: lineMatch,
|
||||||
|
line,
|
||||||
|
type: "content",
|
||||||
|
} as CmdkResult);
|
||||||
|
added++;
|
||||||
|
if (added >= MAX_CONTENT_RESULTS) return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start after heading search has a chance to render
|
||||||
|
setTimeout(processBatch, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSuggestion(result: CmdkResult, el: HTMLElement) {
|
||||||
|
el.addClass("mod-complex");
|
||||||
|
|
||||||
|
if (result.type === "create") {
|
||||||
|
el.addClass("float-search-cmdk-create-item");
|
||||||
|
const contentEl = el.createDiv("suggestion-content");
|
||||||
|
const titleEl = contentEl.createDiv("suggestion-title");
|
||||||
|
titleEl.setText("Create new note");
|
||||||
|
const noteEl = contentEl.createDiv("suggestion-note");
|
||||||
|
const folder =
|
||||||
|
this.plugin.settings.cmdkQuickCreateFolder || "/";
|
||||||
|
noteEl.setText(
|
||||||
|
`"${result.createQuery}" → ${folder}`
|
||||||
|
);
|
||||||
|
const auxEl = el.createDiv("suggestion-aux");
|
||||||
|
const flair = auxEl.createSpan("suggestion-flair");
|
||||||
|
setIcon(flair, "plus");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentEl = el.createDiv("suggestion-content");
|
||||||
|
|
||||||
|
if (result.type === "content" && result.content) {
|
||||||
|
const titleEl = contentEl.createDiv("suggestion-title");
|
||||||
|
if (result.contentMatch) {
|
||||||
|
renderResults(titleEl, result.content, result.contentMatch);
|
||||||
|
} else {
|
||||||
|
titleEl.setText(result.content);
|
||||||
|
}
|
||||||
|
const noteEl = contentEl.createDiv("suggestion-note");
|
||||||
|
noteEl.setText(result.file.path);
|
||||||
|
} else 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
|
||||||
|
) {
|
||||||
|
if (result.type === "create") {
|
||||||
|
this.quickCreateNote(result.createQuery ?? "", evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
eState.subpath = "#" + result.heading;
|
||||||
|
} else if (result.type === "content" && result.line != null) {
|
||||||
|
eState.line = result.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf.setViewState({
|
||||||
|
type: result.file.extension === "pdf" ? "pdf" : "markdown",
|
||||||
|
state: { file: result.file.path },
|
||||||
|
active: true,
|
||||||
|
}).then(() => {
|
||||||
|
if (eState.subpath || eState.line != null) {
|
||||||
|
leaf.setEphemeralState(eState);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async quickCreateNote(
|
||||||
|
content: string,
|
||||||
|
evt: MouseEvent | KeyboardEvent
|
||||||
|
) {
|
||||||
|
const settings = this.plugin.settings;
|
||||||
|
const fmt = settings.cmdkQuickCreateTitleFormat || "YYYYMMDDHHmmss";
|
||||||
|
const title = this.formatTimestamp(new Date(), fmt);
|
||||||
|
|
||||||
|
// Resolve target folder
|
||||||
|
let folderPath = settings.cmdkQuickCreateFolder.trim();
|
||||||
|
if (!folderPath || folderPath === "/") {
|
||||||
|
folderPath = "";
|
||||||
|
}
|
||||||
|
// Ensure folder exists
|
||||||
|
if (folderPath) {
|
||||||
|
const folder =
|
||||||
|
this.app.vault.getAbstractFileByPath(folderPath);
|
||||||
|
if (!folder) {
|
||||||
|
await this.app.vault.createFolder(folderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = folderPath
|
||||||
|
? `${folderPath}/${title}.md`
|
||||||
|
: `${title}.md`;
|
||||||
|
|
||||||
|
const file = await this.app.vault.create(filePath, content);
|
||||||
|
|
||||||
|
const leaf = Keymap.isModEvent(evt)
|
||||||
|
? this.app.workspace.getLeaf("tab")
|
||||||
|
: this.app.workspace.getMostRecentLeaf() ??
|
||||||
|
this.app.workspace.getLeaf();
|
||||||
|
|
||||||
|
await leaf.setViewState({
|
||||||
|
type: "markdown",
|
||||||
|
state: { file: file.path },
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatTimestamp(date: Date, fmt: string): string {
|
||||||
|
const pad = (n: number, len = 2) =>
|
||||||
|
String(n).padStart(len, "0");
|
||||||
|
const tokens: Record<string, string> = {
|
||||||
|
YYYY: String(date.getFullYear()),
|
||||||
|
YY: String(date.getFullYear()).slice(-2),
|
||||||
|
MM: pad(date.getMonth() + 1),
|
||||||
|
DD: pad(date.getDate()),
|
||||||
|
HH: pad(date.getHours()),
|
||||||
|
mm: pad(date.getMinutes()),
|
||||||
|
ss: pad(date.getSeconds()),
|
||||||
|
};
|
||||||
|
let result = fmt;
|
||||||
|
// Replace longest tokens first to avoid partial matches
|
||||||
|
for (const token of ["YYYY", "YY", "MM", "DD", "HH", "mm", "ss"]) {
|
||||||
|
result = result.split(token).join(tokens[token]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by internal Chooser on selection change
|
||||||
|
// @ts-ignore
|
||||||
|
onSelectedChange = debounce(
|
||||||
|
(result: CmdkResult, _evt: Event | null) => {
|
||||||
|
if (result?.type === "create" || !result?.file) {
|
||||||
|
this.hidePreview();
|
||||||
|
} else {
|
||||||
|
this.showPreview(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
|
private hidePreview() {
|
||||||
|
if (this.previewEl) {
|
||||||
|
this.previewEl.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async showPreview(result: CmdkResult) {
|
||||||
|
if (!this.previewEl) {
|
||||||
|
this.previewEl = this.bodyEl.createDiv(
|
||||||
|
"float-search-cmdk-preview"
|
||||||
|
);
|
||||||
|
const [leaf, view] = spawnLeafView(
|
||||||
|
this.plugin,
|
||||||
|
this.previewEl
|
||||||
|
);
|
||||||
|
this.fileLeaf = leaf;
|
||||||
|
this.fileEmbeddedView = view;
|
||||||
|
this.fileLeaf.setPinned(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previewEl.show();
|
||||||
|
this.modalEl.addClass("float-search-cmdk-expanded");
|
||||||
|
|
||||||
|
const file = result.file;
|
||||||
|
await this.fileLeaf!.openFile(file, { active: false });
|
||||||
|
|
||||||
|
const eState: Record<string, any> = {};
|
||||||
|
if (result.type === "heading" && result.heading) {
|
||||||
|
eState.subpath = "#" + result.heading;
|
||||||
|
} else if (result.type === "content" && result.line != null) {
|
||||||
|
eState.line = result.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eState.subpath || eState.line != null) {
|
||||||
|
this.fileLeaf!.setViewState({
|
||||||
|
type: file.extension === "pdf" ? "pdf" : "markdown",
|
||||||
|
state: { file: file.path },
|
||||||
|
}).then(() => {
|
||||||
|
this.fileLeaf?.setEphemeralState(eState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inputEl.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
this.fileLeaf?.detach();
|
||||||
|
this.fileEmbeddedView?.unload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
82
styles.css
82
styles.css
|
|
@ -9,6 +9,7 @@ If your plugin does not need CSS, delete this file.
|
||||||
.float-search-modal {
|
.float-search-modal {
|
||||||
width: 700px;
|
width: 700px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
transition: width 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-search-modal.float-search-width {
|
.float-search-modal.float-search-width {
|
||||||
|
|
@ -147,3 +148,84 @@ body:not(.show-file-path) .search-result-file-title .search-result-file-path {
|
||||||
.fs-leaf-view .workspace-split {
|
.fs-leaf-view .workspace-split {
|
||||||
background-color: var(--background-primary);
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,7 @@
|
||||||
"3.5.1": "0.15.0",
|
"3.5.1": "0.15.0",
|
||||||
"4.0.0": "0.15.0",
|
"4.0.0": "0.15.0",
|
||||||
"4.1.0": "0.15.0",
|
"4.1.0": "0.15.0",
|
||||||
"4.1.1": "0.15.0"
|
"4.1.1": "0.15.0",
|
||||||
|
"4.2.0": "1.2.0",
|
||||||
|
"4.3.0": "1.2.0"
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue