Update browser modal to side-panel view
The browser interface has been refactored from a modal to a persistent side-panel view that can be opened and closed like other Obsidian panes. This provides better workflow integration and allows users to keep the browser open while working with other notes. The documentation has been updated to clarify that the "Browse reMarkable" command now opens the side-panel browser instead of a modal dialog.
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ButtonComponent } from "obsidian";
|
||||
import { RemarkableBrowserModal } from "../src/ui/browser-modal";
|
||||
import { RmapiNode } from "../src/types";
|
||||
|
||||
function createDocument(name: string): RmapiNode {
|
||||
return {
|
||||
id: `id-${name}`,
|
||||
name,
|
||||
type: "DocumentType",
|
||||
version: 1,
|
||||
modifiedClient: "2026-05-31T10:00:00Z",
|
||||
currentPage: 0,
|
||||
parent: "",
|
||||
tags: [],
|
||||
starred: false,
|
||||
};
|
||||
}
|
||||
|
||||
function createPlugin(nodes: RmapiNode[]) {
|
||||
const calls: any[] = [];
|
||||
const plugin: any = {
|
||||
app: {},
|
||||
rmapi: {
|
||||
list: async () => nodes,
|
||||
},
|
||||
downloader: {
|
||||
downloadDocument: async (...args: any[]) => {
|
||||
calls.push(args);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { calls, plugin };
|
||||
}
|
||||
|
||||
test("RemarkableBrowserModal creates import and handwriting buttons for documents", async () => {
|
||||
ButtonComponent.instances = [];
|
||||
const doc = createDocument("Meeting Notes");
|
||||
const { calls, plugin } = createPlugin([doc]);
|
||||
const modal = new RemarkableBrowserModal(plugin);
|
||||
|
||||
modal.onOpen();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
const importButton = ButtonComponent.instances.find((button) => button.buttonEl.text === "Import");
|
||||
const markdownButton = ButtonComponent.instances.find((button) => button.buttonEl.text === "Handwriting to Markdown");
|
||||
|
||||
assert.ok(importButton);
|
||||
assert.ok(markdownButton);
|
||||
|
||||
await importButton.click();
|
||||
await markdownButton.click();
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
[{ node: doc, remotePath: "/Meeting Notes" }, { convertToMd: false, forceDownload: true }],
|
||||
[{ node: doc, remotePath: "/Meeting Notes" }, { convertToMd: true, forceDownload: true }],
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ButtonComponent } from "obsidian";
|
||||
import RemarkablePlugin from "../src/main";
|
||||
import { REMARKABLE_BROWSER_VIEW_TYPE, RemarkableBrowserView } from "../src/ui/browser-view";
|
||||
import { RmapiNode } from "../src/types";
|
||||
|
||||
function createDocument(name: string): RmapiNode {
|
||||
return {
|
||||
id: `id-${name}`,
|
||||
name,
|
||||
type: "DocumentType",
|
||||
version: 1,
|
||||
modifiedClient: "2026-05-31T10:00:00Z",
|
||||
currentPage: 0,
|
||||
parent: "",
|
||||
tags: [],
|
||||
starred: false,
|
||||
};
|
||||
}
|
||||
|
||||
function createPlugin(nodes: RmapiNode[]) {
|
||||
const calls: any[] = [];
|
||||
const plugin: any = {
|
||||
app: {},
|
||||
rmapi: {
|
||||
list: async () => nodes,
|
||||
},
|
||||
downloader: {
|
||||
downloadDocument: async (...args: any[]) => {
|
||||
calls.push(args);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { calls, plugin };
|
||||
}
|
||||
|
||||
test("RemarkableBrowserView creates import and handwriting buttons for documents", async () => {
|
||||
ButtonComponent.instances = [];
|
||||
const doc = createDocument("Meeting Notes");
|
||||
const { calls, plugin } = createPlugin([doc]);
|
||||
const view = new RemarkableBrowserView({} as any, plugin);
|
||||
|
||||
await view.onOpen();
|
||||
|
||||
const importButton = ButtonComponent.instances.find((button) => button.buttonEl.text === "Import");
|
||||
const markdownButton = ButtonComponent.instances.find((button) => button.buttonEl.text === "Handwriting to Markdown");
|
||||
|
||||
assert.ok(importButton);
|
||||
assert.ok(markdownButton);
|
||||
|
||||
await importButton.click();
|
||||
await markdownButton.click();
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
[{ node: doc, remotePath: "/Meeting Notes" }, { convertToMd: false, forceDownload: true }],
|
||||
[{ node: doc, remotePath: "/Meeting Notes" }, { convertToMd: true, forceDownload: true }],
|
||||
]);
|
||||
});
|
||||
|
||||
test("openRemarkableBrowser opens the browser view in the right sidebar", async () => {
|
||||
const calls: any[] = [];
|
||||
const leaf = {
|
||||
setViewState: async (state: any) => {
|
||||
calls.push(["setViewState", state]);
|
||||
},
|
||||
};
|
||||
const plugin: any = new RemarkablePlugin();
|
||||
plugin.app = {
|
||||
workspace: {
|
||||
getLeavesOfType: () => [],
|
||||
getRightLeaf: () => leaf,
|
||||
revealLeaf: async (targetLeaf: any) => {
|
||||
calls.push(["revealLeaf", targetLeaf]);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await plugin.openRemarkableBrowser();
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
["setViewState", { type: REMARKABLE_BROWSER_VIEW_TYPE, active: true }],
|
||||
["revealLeaf", leaf],
|
||||
]);
|
||||
});
|
||||
|
||||
test("openRemarkableBrowser reveals an existing browser leaf", async () => {
|
||||
const calls: any[] = [];
|
||||
const existingLeaf = {};
|
||||
const plugin: any = new RemarkablePlugin();
|
||||
plugin.app = {
|
||||
workspace: {
|
||||
getLeavesOfType: () => [existingLeaf],
|
||||
getRightLeaf: () => {
|
||||
throw new Error("should not create a new leaf");
|
||||
},
|
||||
revealLeaf: async (targetLeaf: any) => {
|
||||
calls.push(targetLeaf);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await plugin.openRemarkableBrowser();
|
||||
|
||||
assert.deepEqual(calls, [existingLeaf]);
|
||||
});
|
||||
@@ -22,6 +22,7 @@ export class Plugin {
|
||||
addRibbonIcon(): void {}
|
||||
addCommand(): void {}
|
||||
addSettingTab(): void {}
|
||||
registerView(): void {}
|
||||
addStatusBarItem(): { setText: (text: string) => void } {
|
||||
return { setText: () => {} };
|
||||
}
|
||||
@@ -62,6 +63,30 @@ export class Modal {
|
||||
onClose(): void {}
|
||||
}
|
||||
|
||||
export class ItemView {
|
||||
leaf: any;
|
||||
contentEl = createElement();
|
||||
|
||||
constructor(leaf: any) {
|
||||
this.leaf = leaf;
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
async onOpen(): Promise<void> {}
|
||||
onClose(): void {}
|
||||
}
|
||||
|
||||
export class ButtonComponent {
|
||||
static instances: ButtonComponent[] = [];
|
||||
buttonEl = createElement();
|
||||
|
||||
Reference in New Issue
Block a user