Skip to content

Commit

Permalink
Misc. menu improvements, add \ key
Browse files Browse the repository at this point in the history
* Escape now closes the entire quick explorer (use <- arrow to go up a level)
* Backslash opens context menu
* Right clicking a parent menu item doesn't end up with double selections
* Closing a context menu restores the preview view, if active
* Child menus nest closer together to use up less screen width
* A "Show in Quick Explorer" menu item now appears in file menus
  • Loading branch information
pjeby committed Sep 3, 2021
1 parent 8f4283e commit 61ba925
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 44 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ And an extensive set of keyboard operations is available as well:
* Up, Down, Home, and End move within a folder or context menu
* Left and Right arrows select parent or child folders
* Enter selects an item to open, Ctrl-or-Cmd + Enter opens a file in a new pane
* Alt + Enter opens a context menu for the selected file or folder
* Backslash (`\`) or Alt + Enter opens a context menu for the selected file or folder
* F2 initiates a rename of the current file or folder, Shift+F2 begins a move
* Tab toggles "quick preview" mode: when active, hovering or arrowing to an item will automatically display a hover preview for it, positioned so that it's always *outside* the menu (unless you're so deep in subfolders you've reached the edge of your screen). This makes it really easy to browse the contents of a folder just by arrowing down through it.
* If a page preview is active for the current file or folder, PageUp and PageDown scroll it up and down, with Ctrl-or-Cmd + Home or End jumping to the beginning or end of the note. Scrolling past the end or before the beginning (or using any of these keys without an active preview) advances the selection to the next or previous file/folder in the list.
Expand All @@ -53,6 +53,8 @@ Quick explorer also includes two hotkeyable commands:
* **Browse vault**, which opens the dropdown for the vault root, and
* **Browse current folder**, which opens the dropdown for the active file's containing folder

And last, but not least, it also adds a "Show in Quick Explorer" option to all file menus other than its own. That way, you can use it from links, graph views, the Pane Relief history menus, other views (like Stars or Recent Files), etc., so you can use it instead of the File Explorer.

### Installation

If this plugin isn't listed in the Obsidian plugin registry yet, you'll need to use a git checkout or download and unzip the release zipfile in the `.obsidian/plugins` directory of the vault you want to add it to.
Expand Down
107 changes: 87 additions & 20 deletions main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "quick-explorer",
"name": "Quick Explorer",
"version": "0.1.1",
"version": "0.1.2",
"description": "Perform file explorer operations (and see your current file path) from the title bar, using the mouse or keyboard",
"minAppVersion": "0.12.12",
"isDesktopOnly": true
Expand Down
44 changes: 38 additions & 6 deletions src/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,54 @@ export class Explorer {
new ContextMenu(app, file).cascade(target, event);
});
this.el.on("click", ".explorable", (event, target) => {
const { parentPath, filePath } = target.dataset;
const folder = app.vault.getAbstractFileByPath(parentPath);
const selected = app.vault.getAbstractFileByPath(filePath);
new FolderMenu(app, folder as TFolder, selected, target).cascade(target, event.isTrusted && event);
this.folderMenu(target, event.isTrusted && event);
});
this.el.on('dragstart', ".explorable", (event, target) => {
startDrag(app, target.dataset.filePath, event);
});
}

folderMenu(opener: HTMLElement = this.el.firstElementChild as HTMLElement, event?: MouseEvent) {
const { filePath } = opener.dataset
const selected = this.app.vault.getAbstractFileByPath(filePath);
const folder = selected.parent;
return new FolderMenu(this.app, folder, selected, opener).cascade(opener, event);
}

browseVault() {
(this.el.firstElementChild as HTMLDivElement).click();
return this.folderMenu();
}

browseCurrent() {
(this.el.lastElementChild as HTMLDivElement).click();
return this.folderMenu(this.el.lastElementChild as HTMLDivElement);
}

browseFile(file: TAbstractFile) {
if (file === this.app.workspace.getActiveFile()) return this.browseCurrent();
let menu: FolderMenu;
let opener: HTMLElement = this.el.firstElementChild as HTMLElement;
const path = [], parts = file.path.split("/").filter(p=>p);
while (opener && parts.length) {
path.push(parts[0]);
if (opener.dataset.filePath !== path.join("/")) {
menu = this.folderMenu(opener);
path.pop();
break
}
parts.shift();
opener = opener.nextElementSibling as HTMLElement;
}
while (menu && parts.length) {
path.push(parts.shift());
const idx = menu.itemForPath(path.join("/"));
if (idx == -1) break
menu.select(idx);
if (parts.length || file instanceof TFolder) {
menu.onArrowRight();
menu = menu.child as FolderMenu;
}
}
return menu;
}

update(file: TAbstractFile) {
Expand Down
31 changes: 22 additions & 9 deletions src/FolderMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ let autoPreview = false
export class FolderMenu extends PopupMenu {

parentFolder: TFolder = this.parent instanceof FolderMenu ? this.parent.folder : null;
lastOver: HTMLElement = null;

constructor(public parent: MenuParent, public folder: TFolder, public selectedFile?: TAbstractFile, public opener?: HTMLElement) {
super(parent);
this.loadFiles(folder, selectedFile);
this.scope.register([], "Tab", this.togglePreviewMode.bind(this));
this.scope.register(["Mod"], "Enter", this.onEnter.bind(this));
this.scope.register(["Alt"], "Enter", this.onEnter.bind(this));
this.scope.register(["Alt"], "Enter", this.onKeyboardContextMenu.bind(this));
this.scope.register([], "\\", this.onKeyboardContextMenu.bind(this));
this.scope.register([], "F2", this.doRename.bind(this));
this.scope.register(["Shift"], "F2", this.doMove.bind(this));

Expand Down Expand Up @@ -90,6 +90,11 @@ export class FolderMenu extends PopupMenu {
return super.onArrowLeft() ?? this.openBreadcrumb(this.opener?.previousElementSibling);
}

onKeyboardContextMenu() {
const target = this.items[this.selected]?.dom, file = target && this.fileForDom(target);
if (file) new ContextMenu(this, file).cascade(target);
}

doScroll(direction: number, toEnd: boolean, event: KeyboardEvent) {
const preview = this.hoverPopover?.hoverEl.find(".markdown-preview-view");
if (preview) {
Expand Down Expand Up @@ -223,11 +228,21 @@ export class FolderMenu extends PopupMenu {
this.items.remove(item);
}

onEscape() {
super.onEscape();
if (this.parent instanceof PopupMenu) this.parent.onEscape();
}

hide() {
this.hidePopover();
return super.hide();
}

setChildMenu(menu: PopupMenu) {
super.setChildMenu(menu);
if (autoPreview && this.canShowPopover()) this.showPopover();
}

select(idx: number, scroll = true) {
const old = this.selected;
super.select(idx, scroll);
Expand Down Expand Up @@ -303,7 +318,6 @@ export class FolderMenu extends PopupMenu {

onItemClick = (event: MouseEvent, target: HTMLDivElement) => {
const file = this.fileForDom(target);
this.lastOver = target;
if (!file) return;
if (!this.onClickFile(file, target, event)) {
// Keep current menu tree open
Expand All @@ -315,11 +329,9 @@ export class FolderMenu extends PopupMenu {

onClickFile(file: TAbstractFile, target: HTMLDivElement, event?: MouseEvent|KeyboardEvent) {
this.hidePopover();
if (event instanceof KeyboardEvent && event.key === "Enter" && Keymap.getModifiers(event) === "Alt") {
// Open context menu w/Alt-Enter
new ContextMenu(this, file).cascade(target);
return
}
const idx = this.itemForPath(file.path);
if (idx >= 0 && this.selected != idx) this.select(idx);

if (file instanceof TFile) {
if (this.app.viewRegistry.isExtensionRegistered(file.extension)) {
this.app.workspace.openLinkText(file.path, "", event && Keymap.isModifier(event, "Mod"));
Expand Down Expand Up @@ -350,7 +362,8 @@ export class FolderMenu extends PopupMenu {
onItemMenu = (event: MouseEvent, target: HTMLDivElement) => {
const file = this.fileForDom(target);
if (file) {
this.lastOver = target;
const idx = this.itemForPath(file.path);
if (idx >= 0 && this.selected != idx) this.select(idx);
new ContextMenu(this, file).cascade(target, event);
// Keep current menu tree open
event.stopPropagation();
Expand Down
18 changes: 13 additions & 5 deletions src/menus.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Menu, App, MenuItem, debounce, Keymap} from "obsidian";
import {Menu, App, MenuItem, debounce, Keymap, Scope} from "obsidian";
import {around} from "monkey-around";

declare module "obsidian" {
Expand All @@ -21,6 +21,7 @@ declare module "obsidian" {

interface MenuItem {
dom: HTMLDivElement
titleEl: HTMLDivElement
handleEvent(event: Event): void
disabled: boolean
}
Expand All @@ -40,8 +41,11 @@ export class PopupMenu extends Menu {
super(parent instanceof App ? parent : parent.app);
if (parent instanceof PopupMenu) parent.setChildMenu(this);

// Escape to close the menu
this.scope.register(null, "Escape", this.hide.bind(this));
this.scope = new Scope;
this.scope.register([], "ArrowUp", this.onArrowUp.bind(this));
this.scope.register([], "ArrowDown", this.onArrowDown.bind(this));
this.scope.register([], "Enter", this.onEnter.bind(this));
this.scope.register([], "Escape", this.onEscape.bind(this));
this.scope.register([], "ArrowLeft", this.onArrowLeft.bind(this));

this.scope.register([], "Home", this.onHome.bind(this));
Expand All @@ -58,6 +62,10 @@ export class PopupMenu extends Menu {
this.dom.addClass("qe-popup-menu");
}

onEscape() {
this.hide();
}

onload() {
this.scope.register(null, null, this.onKeyDown.bind(this));
super.onload();
Expand Down Expand Up @@ -180,8 +188,8 @@ export class PopupMenu extends Menu {
}

cascade(target: HTMLElement, event?: MouseEvent, hOverlap = 15, vOverlap = 5) {
const {left, right, top, bottom} = target.getBoundingClientRect();
const centerX = (left+right)/2, centerY = (top+bottom)/2;
const {left, right, top, bottom, width} = target.getBoundingClientRect();
const centerX = left+Math.min(150, width/3), centerY = (top+bottom)/2;
const {innerHeight, innerWidth} = window;

// Try to cascade down and to the right from the mouse or horizontal center
Expand Down
17 changes: 16 additions & 1 deletion src/quick-explorer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Plugin, TAbstractFile, TFolder} from "obsidian";
import {MenuItem, Plugin, TAbstractFile, TFolder} from "obsidian";
import {mount, unmount} from "redom";
import {Explorer, hoverSource} from "./Explorer";

Expand Down Expand Up @@ -33,6 +33,21 @@ export default class extends Plugin {
this.addCommand({ id: "browse-vault", name: "Browse vault", callback: () => { this.explorer?.browseVault(); }, });
this.addCommand({ id: "browse-current", name: "Browse current folder", callback: () => { this.explorer?.browseCurrent(); }, });

this.registerEvent(this.app.workspace.on("file-menu", (menu, file, source) => {
let item: MenuItem
if (source !== "quick-explorer") menu.addItem(i => {
i.setIcon("folder").setTitle("Show in Quick Explorer").onClick(e => { this.explorer?.browseFile(file); });
item = i;
})
if (item) {
const revealFile = i18next.t(`plugins.file-explorer.action-reveal-file`);
const idx = menu.items.findIndex(i => i.titleEl.textContent === revealFile);
(menu.dom as HTMLElement).insertBefore(item.dom, menu.items[idx+1].dom);
menu.items.remove(item);
menu.items.splice(idx+1, 0, item);
}
}));

Object.defineProperty(TFolder.prototype, "basename", {get(){ return this.name; }, configurable: true})
}

Expand Down
2 changes: 1 addition & 1 deletion versions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"0.1.1": "0.12.12",
"0.1.2": "0.12.12",
"0.0.5": "0.12.10",
"0.0.1": "0.12.3"
}

0 comments on commit 61ba925

Please sign in to comment.