feat: support keyboard navigation

This commit is contained in:
Quorafind 2023-03-15 17:23:56 +08:00
parent 2d843bc32f
commit 4a13dbc005
11 changed files with 1031 additions and 11 deletions

View file

@ -5,7 +5,7 @@ on:
types: [ created ]
env:
PLUGIN_NAME: obsidian-canvas-presentation
PLUGIN_NAME: obsidian-float-search
jobs:
build:

3
.gitignore vendored
View file

@ -20,6 +20,3 @@ data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
src
.github

View file

@ -1,6 +1,8 @@
# Obsidian Float Search
Float search modal.
You can use search view in modal now.
- Set hotkey for open float search quickly.
## Support

View file

@ -15,7 +15,7 @@ esbuild.build({
banner: {
js: banner,
},
entryPoints: ['src/main.ts'],
entryPoints: ['src/floatSearchIndex.ts'],
bundle: true,
external: [
'obsidian',

View file

@ -1,9 +1,9 @@
{
"id": "float-search",
"name": "Float Search",
"version": "0.0.1",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "This is a sample plugin for Obsidian. This plugin demonstrates some of the capabilities of the Obsidian API.",
"description": "You can use search view in modal now.",
"author": "Boninall",
"authorUrl": "https://github.com/Quorafind",
"fundingUrl": {

View file

@ -1,7 +1,7 @@
{
"name": "float-search",
"version": "0.0.1",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"version": "1.0.0",
"description": "You can use search view in modal now.",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",

253
src/floatSearchIndex.ts Normal file
View file

@ -0,0 +1,253 @@
import {
App,
Modal, OpenViewState,
Plugin, SearchView,
TFile,
Workspace,
WorkspaceContainer, WorkspaceItem,
WorkspaceLeaf
} from 'obsidian';
import { EmbeddedView, isDailyNoteLeaf, spawnLeafView } from "./leafView";
import { around } from "monkey-around";
export default class FloatSearchPlugin extends Plugin {
async onload() {
this.patchWorkspace();
this.patchWorkspaceLeaf();
this.addCommand({
id: 'float-search',
name: 'Search Obsidian In Modal',
callback: () => {
new FloatSearchModal(this.app, this).open();
}
});
this.addRibbonIcon('search', 'Search Obsidian In Modal', () => {
new FloatSearchModal(this.app, this).open();
});
}
onunload() {
}
patchWorkspace() {
let layoutChanging = false;
const uninstaller = around(Workspace.prototype, {
getLeaf: (next) =>
function (...args) {
const activeLeaf = this.activeLeaf;
if(activeLeaf) {
if(activeLeaf.pinned === true && activeLeaf.view.getViewType() === "search") {
const newLeaf = app.workspace.getUnpinnedLeaf();
if(newLeaf) {
this.setActiveLeaf(newLeaf);
}
}
return next.call(this, ...args);
}
return next.call(this, ...args);
},
changeLayout(old) {
return async function (workspace: unknown) {
layoutChanging = true;
try {
// Don't consider hover popovers part of the workspace while it's changing
await old.call(this, workspace);
} finally {
layoutChanging = false;
}
};
},
iterateLeaves(old) {
type leafIterator = (item: WorkspaceLeaf) => boolean | void;
return function (arg1, arg2) {
// Fast exit if desired leaf found
if (old.call(this, arg1, arg2)) return true;
// Handle old/new API parameter swap
const cb: leafIterator = (typeof arg1 === "function" ? arg1 : arg2) as leafIterator;
const parent: WorkspaceItem = (typeof arg1 === "function" ? arg2 : arg1) as WorkspaceItem;
if (!parent) return false; // <- during app startup, rootSplit can be null
if (layoutChanging) return false; // Don't let HEs close during workspace change
// 0.14.x doesn't have WorkspaceContainer; this can just be an instanceof check once 15.x is mandatory:
if (parent === app.workspace.rootSplit || (WorkspaceContainer && parent instanceof WorkspaceContainer)) {
for(const popover of EmbeddedView.popoversForWindow((parent as WorkspaceContainer).win)) {
// Use old API here for compat w/0.14.x
if (old.call(this, cb, popover.rootSplit)) return true;
}
}
return false;
};
},
onDragLeaf(old) {
return function (event: MouseEvent, leaf: WorkspaceLeaf) {
return old.call(this, event, leaf);
};
}
});
this.register(uninstaller);
}
// Used for patch workspaceleaf pinned behaviors
patchWorkspaceLeaf() {
this.register(
around(WorkspaceLeaf.prototype, {
getRoot(old) {
return function () {
const top = old.call(this);
return top?.getRoot === this.getRoot ? top : top?.getRoot();
};
},
setPinned(old) {
return function (pinned: boolean) {
old.call(this, pinned);
if(isDailyNoteLeaf(this) && !pinned) this.setPinned(true);
}
},
openFile(old) {
return function (file: TFile, openState?: OpenViewState) {
if (isDailyNoteLeaf(this)) {
setTimeout(
around(Workspace.prototype, {
recordMostRecentOpenedFile(old) {
return function (_file: TFile) {
// Don't update the quick switcher's recent list
if (_file !== file) {
return old.call(this, _file);
}
};
},
}),
1,
);
const recentFiles = this.app.plugins.plugins["recent-files-obsidian"];
if (recentFiles)
setTimeout(
around(recentFiles, {
shouldAddFile(old) {
return function (_file: TFile) {
// Don't update the Recent Files plugin
return _file !== file && old.call(this, _file);
};
},
}),
1,
);
}
return old.call(this, file, openState);
}
}
}),
);
}
}
class FloatSearchModal extends Modal {
private readonly plugin: FloatSearchPlugin;
private embeddedView: EmbeddedView;
private leaf: WorkspaceLeaf;
constructor(app: App, plugin: FloatSearchPlugin) {
super(app);
this.plugin = plugin;
}
async onOpen() {
const { contentEl } = this;
this.initCss(contentEl);
await this.initView(contentEl);
this.initInput();
this.initContent();
}
onClose() {
const { contentEl } = this;
this.leaf.detach();
this.embeddedView.unload();
contentEl.empty();
}
initCss(contentEl: HTMLElement) {
contentEl.classList.add("float-search-modal");
contentEl.parentElement?.classList.add("float-search-modal-parent");
contentEl.parentElement?.parentElement?.classList.add("float-search-modal-container");
}
async initView(contentEl: HTMLElement) {
const [createdLeaf, embeddedView] = spawnLeafView(this.plugin, contentEl);
this.leaf = createdLeaf;
this.embeddedView = embeddedView;
this.leaf.setPinned(true);
await this.leaf.setViewState({
type: "search",
});
}
initInput() {
const inputEl = this.contentEl.getElementsByTagName("input")[0];
inputEl.focus();
inputEl.onkeydown = (e) => {
const currentView = this.leaf.view as SearchView;
switch (e.key) {
case "ArrowDown":
if (e.shiftKey) {
currentView.onKeyShowMoreAfter(e);
break;
} else {
currentView.onKeyArrowDownInFocus(e);
break;
}
case "ArrowUp":
if (e.shiftKey) {
currentView.onKeyShowMoreBefore(e);
break;
} else {
currentView.onKeyArrowUpInFocus(e);
break;
}
case "ArrowLeft":
currentView.onKeyArrowLeftInFocus(e);
break;
case "ArrowRight":
currentView.onKeyArrowRightInFocus(e);
break;
case "Enter":
currentView.onKeyEnterInFocus(e);
if(e.altKey && currentView.dom.focusedItem) {
this.close();
}
break;
}
}
}
initContent() {
const { contentEl } = this;
contentEl.onclick = (e) => {
const resultElement = contentEl.getElementsByClassName('search-results-children')[0];
if(resultElement.children.length < 2) {
return;
}
const target = e.target as HTMLElement;
const classList = target.classList;
const navElement = contentEl.getElementsByClassName('nav-header')[0];
if (e.target !== navElement && navElement.contains(e.target as Node)) {
return;
}
if(!(classList.contains("tree-item-icon") || classList.contains("float-search-modal") || classList.contains("right-triangle") || target.parentElement?.classList.contains("right-triangle") || classList.contains("search-input-container") || target?.parentElement?.classList.contains("search-input-container") || classList.contains("search-result-hover-button"))) {
this.close();
}
}
}
}

553
src/leafView.ts Normal file
View file

@ -0,0 +1,553 @@
// Original code from https://github.com/nothingislost/obsidian-hover-editor/blob/9ec3449be9ab3433dc46c4c3acfde1da72ff0261/src/popover.ts
// You can use this file as a basic leaf view create method in anywhere
// Please rememeber if you want to use this file, you should patch the types-obsidian.d.ts file
// And also monkey around the Obsidian original method.
import {
Component,
EphemeralState,
HoverPopover,
MarkdownEditView,
parseLinktext,
PopoverState,
requireApiVersion,
resolveSubpath,
TFile,
View,
Workspace,
WorkspaceLeaf,
WorkspaceSplit,
WorkspaceTabs,
} from "obsidian";
import type { OpenViewState } from "src/types/types-obsidian";
import type FloatSearchPlugin from "src/floatSearchIndex";
export interface EmbeddedViewParent {
hoverPopover: EmbeddedView | null;
containerEl?: HTMLElement;
view?: View;
dom?: HTMLElement;
}
const popovers = new WeakMap<Element, EmbeddedView>();
type ConstructableWorkspaceSplit = new (ws: Workspace, dir: "horizontal"|"vertical") => WorkspaceSplit;
export function isDailyNoteLeaf(leaf: WorkspaceLeaf) {
// Work around missing enhance.js API by checking match condition instead of looking up parent
return leaf.containerEl.matches(".fs-block.fs-leaf-view .workspace-leaf");
}
function genId(size: number): string {
const chars = [];
for (let n = 0; n < size; n++) chars.push(((16 * Math.random()) | 0).toString(16));
return chars.join("");
}
function nosuper<T>(base: new (...args: unknown[]) => T): new () => T {
const derived = function () {
return Object.setPrototypeOf(new Component, new.target.prototype);
};
derived.prototype = base.prototype;
return Object.setPrototypeOf(derived, base);
}
export const spawnLeafView = (plugin: FloatSearchPlugin, initiatingEl?: HTMLElement, leaf?: WorkspaceLeaf, onShowCallback?: () => unknown): [WorkspaceLeaf, EmbeddedView] => {
// When Obsidian doesn't set any leaf active, use leaf instead.
let parent = app.workspace.activeLeaf as unknown as EmbeddedViewParent;
if (!parent) parent = leaf as unknown as EmbeddedViewParent;
if (!initiatingEl) initiatingEl = parent?.containerEl;
const hoverPopover = new EmbeddedView(parent, initiatingEl!, plugin, undefined, onShowCallback);
return [hoverPopover.attachLeaf(), hoverPopover];
}
export class EmbeddedView extends nosuper(HoverPopover) {
onTarget: boolean;
setActive: (event: MouseEvent) => void;
lockedOut: boolean;
abortController? = this.addChild(new Component());
detaching = false;
opening = false;
rootSplit: WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)(window.app.workspace, "vertical");
isPinned = true;
titleEl: HTMLElement;
containerEl: HTMLElement;
// It is currently not useful.
// leafInHoverEl: WorkspaceLeaf;
oldPopover = this.parent?.hoverPopover;
document: Document = this.targetEl?.ownerDocument ?? window.activeDocument ?? window.document;
id = genId(8);
bounce?: NodeJS.Timeout;
boundOnZoomOut: () => void;
originalPath: string; // these are kept to avoid adopting targets w/a different link
originalLinkText: string;
static activePopover?: EmbeddedView;
static activeWindows() {
const windows: Window[] = [window];
const { floatingSplit } = app.workspace;
if (floatingSplit) {
for (const split of floatingSplit.children) {
if (split.win) windows.push(split.win);
}
}
return windows;
}
static containerForDocument(doc: Document) {
if (doc !== document && app.workspace.floatingSplit)
for (const container of app.workspace.floatingSplit.children) {
if (container.doc === doc) return container;
}
return app.workspace.rootSplit;
}
static activePopovers() {
return this.activeWindows().flatMap(this.popoversForWindow);
}
static popoversForWindow(win?: Window) {
return (Array.prototype.slice.call(win?.document?.body.querySelectorAll(".fs-leaf-view") ?? []) as HTMLElement[])
.map(el => popovers.get(el)!)
.filter(he => he);
}
static forLeaf(leaf: WorkspaceLeaf | undefined) {
// leaf can be null such as when right clicking on an internal link
const el = leaf && document.body.matchParent.call(leaf.containerEl, ".fs-leaf-view"); // work around matchParent race condition
return el ? popovers.get(el) : undefined;
}
static iteratePopoverLeaves(ws: Workspace, cb: (leaf: WorkspaceLeaf) => boolean | void) {
for (const popover of this.activePopovers()) {
if (popover.rootSplit && ws.iterateLeaves(cb, popover.rootSplit)) return true;
}
return false;
}
hoverEl: HTMLElement = this.document.defaultView!.createDiv({
cls: "fs-block fs-leaf-view",
attr: { id: "fs-" + this.id },
});
constructor(
parent: EmbeddedViewParent,
public targetEl: HTMLElement,
public plugin: FloatSearchPlugin,
waitTime?: number,
public onShowCallback?: () => unknown,
) {
//
super();
if (waitTime === undefined) {
waitTime = 300;
}
this.onTarget = true;
this.parent = parent;
this.waitTime = waitTime;
this.state = PopoverState.Showing;
const { hoverEl } = this;
this.abortController!.load();
this.show();
this.onShow();
this.setActive = this._setActive.bind(this);
// if (hoverEl) {
// hoverEl.addEventListener("mousedown", this.setActive);
// }
// custom logic begin
popovers.set(this.hoverEl, this);
this.hoverEl.addClass("fs-block");
this.containerEl = this.hoverEl.createDiv("fs-content");
this.buildWindowControls();
this.setInitialDimensions();
}
_setActive(evt: MouseEvent) {
evt.preventDefault();
evt.stopPropagation();
this.plugin.app.workspace.setActiveLeaf(this.leaves()[0], {focus: true})
}
getDefaultMode() {
// return this.parent?.view?.getMode ? this.parent.view.getMode() : "source";
return "source";
}
updateLeaves() {
if (this.onTarget && this.targetEl && !this.document.contains(this.targetEl)) {
this.onTarget = false;
this.transition();
}
let leafCount = 0;
this.plugin.app.workspace.iterateLeaves(leaf => {
leafCount++;
}, this.rootSplit);
if (leafCount === 0) {
this.hide(); // close if we have no leaves
}
this.hoverEl.setAttribute("data-leaf-count", leafCount.toString());
}
leaves() {
const leaves: WorkspaceLeaf[] = [];
this.plugin.app.workspace.iterateLeaves(leaf => {
leaves.push(leaf);
}, this.rootSplit);
return leaves;
}
setInitialDimensions() {
this.hoverEl.style.height = 'auto';
this.hoverEl.style.width = "100%";
}
transition() {
if (this.shouldShow()) {
if (this.state === PopoverState.Hiding) {
this.state = PopoverState.Shown;
clearTimeout(this.timer);
}
} else {
if (this.state === PopoverState.Showing) {
this.hide();
} else {
if (this.state === PopoverState.Shown) {
this.state = PopoverState.Hiding;
this.timer = window.setTimeout(() => {
if (this.shouldShow()) {
this.transition();
} else {
this.hide();
}
}, this.waitTime);
}
}
}
}
buildWindowControls() {
this.titleEl = this.document.defaultView!.createDiv("popover-titlebar");
this.titleEl.createDiv("popover-title");
this.containerEl.prepend(this.titleEl);
}
attachLeaf(): WorkspaceLeaf {
this.rootSplit.getRoot = () => this.plugin.app.workspace[this.document === document ? "rootSplit" : "floatingSplit"]!;
this.rootSplit.getContainer = () => EmbeddedView.containerForDocument(this.document);
this.titleEl.insertAdjacentElement("afterend", this.rootSplit.containerEl);
const leaf = this.plugin.app.workspace.createLeafInParent(this.rootSplit, 0);
this.updateLeaves();
return leaf;
}
onload(): void {
super.onload();
this.registerEvent(this.plugin.app.workspace.on("layout-change", this.updateLeaves, this));
this.registerEvent(app.workspace.on("layout-change", () => {
// Ensure that top-level items in a popover are not tabbed
// @ts-ignore
this.rootSplit.children.forEach((item: any, index: any) => {
if (item instanceof WorkspaceTabs) {
this.rootSplit.replaceChild(index, item.children[0]);
}
})
}));
}
onShow() {
// Once we've been open for closeDelay, use the closeDelay as a hiding timeout
const closeDelay = 600;
setTimeout(() => (this.waitTime = closeDelay), closeDelay);
this.oldPopover?.hide();
this.oldPopover = null;
this.hoverEl.toggleClass("is-new", true);
this.document.body.addEventListener(
"click",
() => {
this.hoverEl.toggleClass("is-new", false);
},
{ once: true, capture: true },
);
if (this.parent) {
this.parent.hoverPopover = this;
}
// Remove original view header;
const viewHeaderEl = this.hoverEl.querySelector(".view-header");
viewHeaderEl?.remove();
const sizer = this.hoverEl.querySelector(".workspace-leaf");
if(sizer) this.hoverEl.appendChild(sizer);
// Remove original inline tilte;
const inlineTitle = this.hoverEl.querySelector(".inline-title");
if(inlineTitle) inlineTitle.remove();
this.onShowCallback?.();
this.onShowCallback = undefined; // only call it once
}
detect(el: HTMLElement) {
// TODO: may not be needed? the mouseover/out handers handle most detection use cases
const { targetEl } = this;
if (targetEl) {
this.onTarget = el === targetEl || targetEl.contains(el);
}
}
shouldShow() {
return this.shouldShowSelf() || this.shouldShowChild();
}
shouldShowChild(): boolean {
return EmbeddedView.activePopovers().some(popover => {
if (popover !== this && popover.targetEl && this.hoverEl.contains(popover.targetEl)) {
return popover.shouldShow();
}
return false;
});
}
shouldShowSelf() {
// Don't let obsidian show() us if we've already started closing
// return !this.detaching && (this.onTarget || this.onHover);
return (
!this.detaching &&
!!(
this.onTarget ||
(this.state == PopoverState.Shown) ||
this.document.querySelector(`body>.modal-container, body > #he${this.id} ~ .menu, body > #he${this.id} ~ .suggestion-container`)
)
);
}
show() {
// native obsidian logic start
// if (!this.targetEl || this.document.body.contains(this.targetEl)) {
this.state = PopoverState.Shown;
this.timer = 0;
this.targetEl.appendChild(this.hoverEl);
this.onShow();
app.workspace.onLayoutChange();
// initializingHoverPopovers.remove(this);
// activeHoverPopovers.push(this);
// initializePopoverChecker();
this.load();
// }
// native obsidian logic end
// if this is an image view, set the dimensions to the natural dimensions of the image
// an interactjs reflow will be triggered to constrain the image to the viewport if it's
// too large
if (this.hoverEl.dataset.imgHeight && this.hoverEl.dataset.imgWidth) {
this.hoverEl.style.height = parseFloat(this.hoverEl.dataset.imgHeight) + this.titleEl.offsetHeight + "px";
this.hoverEl.style.width = parseFloat(this.hoverEl.dataset.imgWidth) + "px";
}
}
onHide() {
this.oldPopover = null;
if (this.parent?.hoverPopover === this) {
this.parent.hoverPopover = null;
}
}
hide() {
this.onTarget = false;
this.detaching = true;
// Once we reach this point, we're committed to closing
// in case we didn't ever call show()
// A timer might be pending to call show() for the first time, make sure
// it doesn't bring us back up after we close
if (this.timer) {
clearTimeout(this.timer);
this.timer = 0;
}
// Hide our HTML element immediately, even if our leaves might not be
// detachable yet. This makes things more responsive and improves the
// odds of not showing an empty popup that's just going to disappear
// momentarily.
this.hoverEl.hide();
// If a file load is in progress, we need to wait until it's finished before
// detaching leaves. Because we set .detaching, The in-progress openFile()
// will call us again when it finishes.
if (this.opening) return;
// Leave this code here to observe the state of the leaves
const leaves = this.leaves();
if (leaves.length) {
// Detach all leaves before we unload the popover and remove it from the DOM.
// Each leaf.detach() will trigger layout-changed and the updateLeaves()
// method will then call hide() again when the last one is gone.
// leaves[0].detach();
// leaves[0].detach();
this.targetEl.empty();
} else {
this.parent = null;
this.abortController?.unload();
this.abortController = undefined;
return this.nativeHide();
}
}
nativeHide() {
const { hoverEl, targetEl } = this;
this.state = PopoverState.Hidden;
hoverEl.detach();
if (targetEl) {
const parent = targetEl.matchParent(".fs-leaf-view");
if (parent) popovers.get(parent)?.transition();
}
this.onHide();
this.unload();
}
resolveLink(linkText: string, sourcePath: string): TFile | null {
const link = parseLinktext(linkText);
const tFile = link ? this.plugin.app.metadataCache.getFirstLinkpathDest(link.path, sourcePath) : null;
return tFile;
}
async openLink(linkText: string, sourcePath: string, eState?: EphemeralState, createInLeaf?: WorkspaceLeaf) {
let file = this.resolveLink(linkText, sourcePath);
const link = parseLinktext(linkText);
if (!file && createInLeaf) {
const folder = this.plugin.app.fileManager.getNewFileParent(sourcePath);
file = await this.plugin.app.fileManager.createNewMarkdownFile(folder, link.path);
}
if (!file) {
// this.displayCreateFileAction(linkText, sourcePath, eState);
return;
}
const { viewRegistry } = this.plugin.app;
const viewType = viewRegistry.typeByExtension[file.extension];
if (!viewType || !viewRegistry.viewByType[viewType]) {
// this.displayOpenFileAction(file);
return;
}
eState = Object.assign(this.buildEphemeralState(file, link), eState);
const parentMode = this.getDefaultMode();
const state = this.buildState(parentMode, eState);
const leaf = await this.openFile(file, state, createInLeaf);
const leafViewType = leaf?.view?.getViewType();
// console.log(leaf);
if (leafViewType === "image") {
// TODO: temporary workaround to prevent image popover from disappearing immediately when using live preview
if (
this.parent?.hasOwnProperty("editorEl") &&
(this.parent as unknown as MarkdownEditView).editorEl!.hasClass("is-live-preview")
) {
this.waitTime = 3000;
}
const img = leaf!.view.contentEl.querySelector("img")!;
this.hoverEl.dataset.imgHeight = String(img.naturalHeight);
this.hoverEl.dataset.imgWidth = String(img.naturalWidth);
this.hoverEl.dataset.imgRatio = String(img.naturalWidth / img.naturalHeight);
} else if (leafViewType === "pdf") {
this.hoverEl.style.height = "800px";
this.hoverEl.style.width = "600px";
}
if (state.state?.mode === "source") {
this.whenShown(() => {
// Not sure why this is needed, but without it we get issue #186
if (requireApiVersion("1.0"))(leaf?.view as any)?.editMode?.reinit?.();
leaf?.view?.setEphemeralState(state.eState);
});
}
}
whenShown(callback: () => any) {
// invoke callback once the popover is visible
if (this.detaching) return;
const existingCallback = this.onShowCallback;
this.onShowCallback = () => {
if (this.detaching) return;
callback();
if (typeof existingCallback === "function") existingCallback();
};
if (this.state === PopoverState.Shown) {
this.onShowCallback();
this.onShowCallback = undefined;
}
}
async openFile(file: TFile, openState?: OpenViewState, useLeaf?: WorkspaceLeaf) {
if (this.detaching) return;
const leaf = useLeaf ?? this.attachLeaf();
this.opening = true;
try {
await leaf.openFile(file, openState);
} catch (e) {
console.error(e);
} finally {
this.opening = false;
if (this.detaching) this.hide();
}
this.plugin.app.workspace.setActiveLeaf(leaf);
return leaf;
}
buildState(parentMode: string, eState?: EphemeralState) {
return {
active: false, // Don't let Obsidian force focus if we have autofocus off
state: { mode: "source" }, // Don't set any state for the view, because this leaf is stayed on another view.
eState: eState,
};
}
buildEphemeralState(
file: TFile,
link?: {
path: string;
subpath: string;
},
) {
const cache = this.plugin.app.metadataCache.getFileCache(file);
const subpath = cache ? resolveSubpath(cache, link?.subpath || "") : undefined;
const eState: EphemeralState = { subpath: link?.subpath };
if (subpath) {
eState.line = subpath.start.line;
eState.startLoc = subpath.start;
eState.endLoc = subpath.end || undefined;
}
return eState;
}
}

210
src/types/types-obsidian.d.ts vendored Normal file
View file

@ -0,0 +1,210 @@
import "obsidian";
import {
EditorPosition,
EphemeralState,
Loc,
MarkdownPreviewRenderer, MarkdownSubView,
Plugin,
PluginManifest,
SuggestModal,
TFile, TFolder,
View, WorkspaceItem,
WorkspaceLeaf
} from "obsidian";
import { EmbeddedViewParent } from "../leafView";
interface InternalPlugins {
switcher: QuickSwitcherPlugin;
"page-preview": InternalPlugin;
graph: GraphPlugin;
}
interface OpenViewState {
eState?: EphemeralState;
state?: { mode: string };
active?: boolean;
}
declare class QuickSwitcherModal extends SuggestModal<TFile> {
getSuggestions(query: string): TFile[] | Promise<TFile[]>;
renderSuggestion(value: TFile, el: HTMLElement): unknown;
onChooseSuggestion(item: TFile, evt: MouseEvent | KeyboardEvent): unknown;
}
interface InternalPlugin {
disable(): void;
enable(): void;
enabled: boolean;
_loaded: boolean;
instance: { name: string; id: string };
}
interface GraphPlugin extends InternalPlugin {
views: { localgraph: (leaf: WorkspaceLeaf) => GraphView };
}
interface GraphView extends View {
engine: typeof Object;
renderer: { worker: { terminate(): void } };
}
interface QuickSwitcherPlugin extends InternalPlugin {
instance: {
name: string;
id: string;
QuickSwitcherModal: typeof QuickSwitcherModal;
};
}
declare global {
const i18next: {
t(id: string): string;
};
interface Window {
activeWindow: Window;
activeDocument: Document;
}
}
declare module "obsidian" {
interface App {
internalPlugins: {
plugins: InternalPlugins;
getPluginById<T extends keyof InternalPlugins>(id: T): InternalPlugins[T];
};
plugins: {
manifests: Record<string, PluginManifest>;
plugins: Record<string, Plugin> & {
["recent-files-obsidian"]: Plugin & {
shouldAddFile(file: TFile): boolean;
};
};
getPlugin(id: string): Plugin;
getPlugin(id: "calendar"): CalendarPlugin;
};
dom: { appContainerEl: HTMLElement };
viewRegistry: ViewRegistry;
openWithDefaultApp(path: string): void;
}
interface ViewRegistry {
typeByExtension: Record<string, string>; // file extensions to view types
viewByType: Record<string, (leaf: WorkspaceLeaf) => View>; // file extensions to view types
}
interface CalendarPlugin {
view: View;
}
interface WorkspaceParent {
insertChild(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;
}
interface MarkdownEditView {
editorEl: HTMLElement;
}
class MarkdownPreviewRendererStatic extends MarkdownPreviewRenderer {
static registerDomEvents(el: HTMLElement, handlerInstance: unknown, cb: (el: HTMLElement) => unknown): void;
}
interface WorkspaceLeaf {
openLinkText(linkText: string, path: string, state?: unknown): Promise<void>;
updateHeader(): void;
containerEl: HTMLDivElement;
working: boolean;
parentSplit: WorkspaceParent;
activeTime: number;
}
interface Workspace {
recordHistory(leaf: WorkspaceLeaf, pushHistory: boolean): void;
iterateLeaves(callback: (item: WorkspaceLeaf) => boolean | void, item: WorkspaceItem | WorkspaceItem[]): boolean;
iterateLeaves(item: WorkspaceItem | WorkspaceItem[], callback: (item: WorkspaceLeaf) => boolean | void): boolean;
getDropLocation(event: MouseEvent): {
target: WorkspaceItem;
sidedock: boolean;
};
recursiveGetTarget(event: MouseEvent, parent: WorkspaceParent): WorkspaceItem;
recordMostRecentOpenedFile(file: TFile): void;
onDragLeaf(event: MouseEvent, leaf: WorkspaceLeaf): void;
onLayoutChange(): void; // tell Obsidian leaves have been added/removed/etc.
activeLeafEvents(): void;
floatingSplit: any;
}
interface Editor {
getClickableTokenAt(pos: EditorPosition): {
text: string;
type: string;
start: EditorPosition;
end: EditorPosition;
};
}
interface View {
iconEl: HTMLElement;
file: TFile;
setMode(mode: MarkdownSubView): Promise<void>;
followLinkUnderCursor(newLeaf: boolean): void;
modes: Record<string, MarkdownSubView>;
getMode(): string;
headerEl: HTMLElement;
contentEl: HTMLElement;
}
interface SearchView extends View {
onKeyArrowRightInFocus(e: KeyboardEvent): void;
onKeyArrowLeftInFocus(e: KeyboardEvent): void;
onKeyArrowUpInFocus(e: KeyboardEvent): void;
onKeyArrowDownInFocus(e: KeyboardEvent): void;
onKeyEnterInFocus(e: KeyboardEvent): void;
onKeyShowMoreBefore(e: KeyboardEvent): void;
onKeyShowMoreAfter(e: KeyboardEvent): void;
dom: any;
}
interface EmptyView extends View {
actionListEl: HTMLElement;
emptyTitleEl: HTMLElement;
}
interface FileManager {
createNewMarkdownFile(folder: TFolder, fileName: string): Promise<TFile>;
}
enum PopoverState {
Showing,
Shown,
Hiding,
Hidden,
}
interface Menu {
items: MenuItem[];
dom: HTMLElement;
hideCallback: () => unknown;
}
interface MenuItem {
iconEl: HTMLElement;
dom: HTMLElement;
}
interface EphemeralState {
focus?: boolean;
subpath?: string;
line?: number;
startLoc?: Loc;
endLoc?: Loc;
scroll?: number;
}
interface HoverParent {
type?: string;
}
interface HoverPopover {
parent: EmbeddedViewParent | null;
targetEl: HTMLElement;
hoverEl: HTMLElement;
position(pos?: MousePos): void;
hide(): void;
show(): void;
shouldShowSelf(): boolean;
timer: number;
waitTime: number;
shouldShow(): boolean;
transition(): void;
}
interface MousePos {
x: number;
y: number;
}
}

View file

@ -22,6 +22,10 @@ If your plugin does not need CSS, delete this file.
height: 100% !important;
}
.fs-block .workspace-leaf-resize-handle {
display: none;
}
.modal-container.float-search-modal-container.mod-dim {
z-index: 30;
}

View file

@ -1,3 +1,4 @@
{
"0.0.1": "0.15.0"
"0.0.1": "0.15.0",
"1.0.0": "0.15.0"
}