Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ async function loadPlugin(data: Record<string, unknown>) {
closing: null,
writeInFlight: null,
storeUnsubscribers: [],
views: new Set(),
manifest: { dir: ".obsidian/plugins/podnotes" },
app: {
secretStorage: {
Expand Down
1 change: 0 additions & 1 deletion src/getContextMenuHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ function makeApp(workspace: unknown, file: TFile) {
app,
player,
navigation: new Navigation(),
views: new Set(),
});
plugin.set(podnotes);
return app;
Expand Down
42 changes: 0 additions & 42 deletions src/main.activateView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ function setupPlugin({
// Build a bare instance so we exercise activateView without running the full
// onload() side effects (store wiring, command registration, etc.).
const plugin = Object.create(PodNotes.prototype) as PodNotes;
Object.assign(plugin as unknown as Record<string, unknown>, {
podcastViewMountEnabled: true,
views: new Set(),
});
(plugin as unknown as { app: { workspace: typeof workspace } }).app = {
workspace,
};
Expand Down Expand Up @@ -88,25 +84,6 @@ describe("PodNotes.activateView", () => {
await expect(plugin.activateView()).resolves.toBeUndefined();
expect(workspace.revealLeaf).not.toHaveBeenCalled();
});

it("enables and mounts a dormant restored view before revealing it", async () => {
const existing = makeLeaf();
const firstRestoredView = { mountPodcastView: vi.fn() };
const secondRestoredView = { mountPodcastView: vi.fn() };
const { plugin } = setupPlugin({ existingLeaves: [existing] });
Object.assign(plugin as unknown as Record<string, unknown>, {
podcastViewMountEnabled: false,
views: new Set([firstRestoredView, secondRestoredView]),
});

expect(plugin.shouldMountPodcastView()).toBe(false);

await plugin.activateView();

expect(plugin.shouldMountPodcastView()).toBe(true);
expect(firstRestoredView.mountPodcastView).toHaveBeenCalledTimes(1);
expect(secondRestoredView.mountPodcastView).toHaveBeenCalledTimes(1);
});
});

describe("PodNotes.onLayoutReady", () => {
Expand Down Expand Up @@ -150,9 +127,6 @@ describe("PodNotes.onLayoutReady", () => {
isUnloaded: false,
isReady: true,
storeUnsubscribers: [],
views: new Set(),
// onload leaves the view dormant in the mobile app.
podcastViewMountEnabled: runtime === "desktop",
settings: structuredClone(DEFAULT_SETTINGS),
persistenceUnknownFields: {},
loadData: vi.fn(async () => structuredClone(disk) ?? null),
Expand Down Expand Up @@ -250,21 +224,6 @@ describe("PodNotes.onLayoutReady", () => {
expect(workspace.detachLeavesOfType).not.toHaveBeenCalled();
});
});

it("mounts the dormant view when the mobile app opens it on the first start", async () => {
const { plugin } = await start("mobile");

expect(plugin.shouldMountPodcastView()).toBe(true);
});

it("keeps the view dormant on a later mobile start", async () => {
const { plugin } = await start(
"mobile",
encodePodNotesData(structuredClone(DEFAULT_SETTINGS)),
);

expect(plugin.shouldMountPodcastView()).toBe(false);
});
});

// Locks the actual #55 wiring (not just activateView's internals): the
Expand Down Expand Up @@ -328,7 +287,6 @@ describe("PodNotes onload wiring (#55)", () => {
closing: null,
writeInFlight: null,
storeUnsubscribers: [],
views: new Set(),
manifest: { dir: ".obsidian/plugins/podnotes" },
app: {
loadLocalStorage: vi.fn(() => null),
Expand Down
1 change: 0 additions & 1 deletion src/main.fileRenames.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ async function loadPlugin(data: Record<string, unknown>): Promise<PodNotes> {
closing: null,
writeInFlight: null,
storeUnsubscribers: [],
views: new Set(),
manifest: { dir: ".obsidian/plugins/podnotes" },
app: {
loadLocalStorage: vi.fn(() => null),
Expand Down
27 changes: 2 additions & 25 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from "src/store";
import { bindStoresToSettings } from "src/store/persistence";
import { registerCommands } from "src/commands";
import { normalizePath, Notice, Platform, Plugin, type WorkspaceLeaf } from "obsidian";
import { normalizePath, Notice, Plugin, type WorkspaceLeaf } from "obsidian";
import { API } from "src/API/API";
import type { IAPI } from "src/API/IAPI";
import { VIEW_TYPE } from "src/constants";
Expand Down Expand Up @@ -96,8 +96,6 @@ export default class PodNotes extends Plugin implements IPodNotes {
public navigation!: Navigation;
public online!: Readable<boolean>;

private views = new Set<MainView>();

// Store subscriptions tied to the plugin lifetime. Disposed together in onunload.
private storeUnsubscribers: Unsubscriber[] = [];
private transcriptionService?: TranscriptionService;
Expand All @@ -106,7 +104,6 @@ export default class PodNotes extends Plugin implements IPodNotes {

private isUnloaded = false;
private freshInstall = false;
private podcastViewMountEnabled = true;
private isReady = false;
// Set once the window starts unloading or Obsidian quits: a write started then is
// cut off after it truncates the file, which leaves data.json empty.
Expand All @@ -123,8 +120,6 @@ export default class PodNotes extends Plugin implements IPodNotes {
// Before registerView, where the views Obsidian restores mount and append their sheets.
removePreviousStyles(this.app.workspace);
this.isUnloaded = false;
// The mobile app keeps a restored view dormant until PodNotes is opened.
this.podcastViewMountEnabled = !Platform.isMobileApp && this.app.isMobile !== true;
this.registerDomEvent(window, "pagehide", () => {
this.closing = "window-unloading";
});
Expand Down Expand Up @@ -202,11 +197,7 @@ export default class PodNotes extends Plugin implements IPodNotes {
this.addSettingTab(new PodNotesSettingsTab(this.app, this));

this.navigation = new Navigation();
this.registerView(VIEW_TYPE, (leaf: WorkspaceLeaf) => {
const view = new MainView(leaf, this);
this.views.add(view);
return view;
});
this.registerView(VIEW_TYPE, (leaf: WorkspaceLeaf) => new MainView(leaf, this));

// PodNotes is a single-instance view, and onunload deliberately does NOT
// detach its leaves (detaching there would reset a leaf the user moved back
Expand Down Expand Up @@ -265,11 +256,6 @@ export default class PodNotes extends Plugin implements IPodNotes {
// makes "Show PodNotes" and the ribbon icon reliably surface the view even
// when it is already open but hidden in a collapsed/overflowing sidebar (#55).
async activateView(): Promise<void> {
this.podcastViewMountEnabled = true;
for (const view of this.views) {
view.mountPodcastView();
}

const { workspace } = this.app;

const existing = workspace.getLeavesOfType(VIEW_TYPE);
Expand Down Expand Up @@ -308,14 +294,6 @@ export default class PodNotes extends Plugin implements IPodNotes {
void this.saveSettings();
}

shouldMountPodcastView(): boolean {
return this.podcastViewMountEnabled;
}

unregisterPodcastView(view: MainView): void {
this.views.delete(view);
}

// Collapse the single-instance player view back to one leaf, keeping the
// first (earliest) so a cold-restart-restored leaf is preserved while a
// hot-reload's duplicate auto-open is dropped. Runs on layout-change; a no-op
Expand Down Expand Up @@ -352,7 +330,6 @@ export default class PodNotes extends Plugin implements IPodNotes {
this.localFilesMirrorUnsubscribe?.();
this.transcriptionService?.dispose();
this.transcriptionService = undefined;
this.views.clear();

// Intentionally do NOT detach the view's leaves here. Obsidian persists and
// restores plugin leaves across reloads; detaching in onunload would reset a
Expand Down
1 change: 0 additions & 1 deletion src/main.undo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ async function loadPlugin(secrets: obsidian.SecretStorage): Promise<PodNotes> {
closing: null,
writeInFlight: null,
storeUnsubscribers: [],
views: new Set(),
manifest: { dir: ".obsidian/plugins/podnotes" },
app: {
loadLocalStorage: vi.fn(() => null),
Expand Down
6 changes: 0 additions & 6 deletions src/types/IPodNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import type { Navigation } from "src/ui/PodcastView/Navigation";
import type { Readable } from "svelte/store";
import type { IPodNotesSettings } from "./IPodNotesSettings";

interface PodNotesViewRegistration {
mountPodcastView(): void;
}

export interface IPodNotes {
settings: IPodNotesSettings;
api: IAPI;
Expand All @@ -21,8 +17,6 @@ export interface IPodNotes {
navigation: Navigation;
capture: CaptureService;
online: Readable<boolean>;
shouldMountPodcastView(): boolean;
unregisterPodcastView(view: PodNotesViewRegistration): void;
saveSettings(): Promise<void>;
showPlayer(): Promise<void>;
openSettingsTab(): void;
Expand Down
2 changes: 0 additions & 2 deletions src/ui/PodcastView/index.notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ async function openPodcastPage(notes: string[]) {
const plugin = {
...services,
feedUrls: new FeedUrlRepository(app.secretStorage),
shouldMountPodcastView: () => true,
unregisterPodcastView: () => {},
} as unknown as IPodNotes;
const view = new MainView({} as WorkspaceLeaf, plugin);
const contentEl = document.body.appendChild(document.createElement("div"));
Expand Down
61 changes: 14 additions & 47 deletions src/ui/PodcastView/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,8 @@ function createVault() {
return vault;
}

function createView({
shouldMount,
layoutReady = true,
}: {
shouldMount: boolean;
layoutReady?: boolean;
}) {
const plugin = {
navigation: new Navigation(),
shouldMountPodcastView: vi.fn(() => shouldMount),
unregisterPodcastView: vi.fn(),
} as unknown as IPodNotes;
function createView({ layoutReady = true }: { layoutReady?: boolean } = {}) {
const plugin = { navigation: new Navigation() } as unknown as IPodNotes;
const view = new MainView({} as WorkspaceLeaf, plugin);
const contentEl = document.createElement("div");
const pending: Array<() => void> = [];
Expand Down Expand Up @@ -96,22 +86,13 @@ async function closeView(view: MainView): Promise<void> {
await (view as unknown as { close(): Promise<void> }).close();
}

describe("MainView mobile startup mounting", () => {
describe("MainView mounting", () => {
afterEach(() => {
vi.clearAllMocks();
});

it("does not mount the Svelte UI when plugin startup keeps the view dormant", async () => {
const { plugin, view } = createView({ shouldMount: false });

await openView(view);

expect(plugin.shouldMountPodcastView).toHaveBeenCalled();
expect(svelte.mount).not.toHaveBeenCalled();
});

it("mounts the Svelte UI when the plugin allows the view to wake", async () => {
const { contentEl, plugin, view } = createView({ shouldMount: true });
it("mounts the Svelte UI when Obsidian opens the view", async () => {
const { contentEl, plugin, view } = createView();

await openView(view);

Expand All @@ -123,7 +104,7 @@ describe("MainView mobile startup mounting", () => {
});

it("gives its components the view's own key scope, the one Obsidian hands keys to", async () => {
const { view } = createView({ shouldMount: true });
const { view } = createView();

await openView(view);

Expand All @@ -132,7 +113,7 @@ describe("MainView mobile startup mounting", () => {
});

it("Save as note asks for the feed's transcript without the paid fallback; Transcribe uses the audio", async () => {
const { plugin, view } = createView({ shouldMount: true });
const { plugin, view } = createView();
const transcription = {
transcribeCurrentEpisode: vi.fn(async () => {}),
transcribeCurrentEpisodeFromAudio: vi.fn(),
Expand All @@ -149,21 +130,8 @@ describe("MainView mobile startup mounting", () => {
expect(transcription.transcribeCurrentEpisodeFromAudio).toHaveBeenCalledExactlyOnceWith();
});

it("mountPodcastView wakes a dormant restored view exactly once", () => {
const { contentEl, plugin, view } = createView({ shouldMount: false });

view.mountPodcastView();
view.mountPodcastView();

expect(svelte.mount).toHaveBeenCalledTimes(1);
expect(svelte.mount).toHaveBeenCalledWith(expect.anything(), {
target: contentEl,
props: { services: expect.objectContaining({ navigation: plugin.navigation }) },
});
});

it("gives the view the plugin's settings, current through a toggle and an import", async () => {
const { plugin, view } = createView({ shouldMount: true });
const { plugin, view } = createView();
const settings = (autoQueue: boolean): IPodNotesSettings => ({
...structuredClone(DEFAULT_SETTINGS),
autoQueue,
Expand All @@ -184,13 +152,12 @@ describe("MainView mobile startup mounting", () => {
});

it("unmounts a mounted PodcastView", async () => {
const { plugin, view } = createView({ shouldMount: true });
const { view } = createView();

await openView(view);
await closeView(view);

expect(svelte.unmount).toHaveBeenCalledTimes(1);
expect(plugin.unregisterPodcastView).toHaveBeenCalledWith(view);
});
});

Expand All @@ -199,7 +166,7 @@ describe("MainView episode notes", () => {
const notePath = "podcasts/Episode One.md";

function createNotesView(options: { layoutReady?: boolean } = {}) {
const created = createView({ shouldMount: true, ...options });
const created = createView(options);
pluginStore.set({
app: created.app,
settings: { note: { path: "podcasts/{{title}}" } },
Expand Down Expand Up @@ -317,12 +284,12 @@ describe("MainView episode notes", () => {

describe("MainView workspace state", () => {
it("saves the open page and restores it in a new session", async () => {
const first = createView({ shouldMount: true });
const first = createView();
first.plugin.navigation.go({ kind: "feed", key: "Test Podcast" });
first.plugin.navigation.expand();
const saved = JSON.parse(JSON.stringify(first.view.getState()));

const next = createView({ shouldMount: true });
const next = createView();
await next.view.setState(saved, { history: false });

expect(next.plugin.navigation.current).toEqual({
Expand All @@ -336,7 +303,7 @@ describe("MainView workspace state", () => {
it.each([undefined, null, {}, { page: { kind: "feed" } }, { page: "queue" }])(
"keeps the page for the saved state %j",
async (state) => {
const { plugin, view } = createView({ shouldMount: true });
const { plugin, view } = createView();
plugin.navigation.go({ kind: "latest" });

await view.setState(state, { history: false });
Expand All @@ -347,7 +314,7 @@ describe("MainView workspace state", () => {
);

it("asks Obsidian to save the layout when the page changes, until the view closes", async () => {
const { plugin, view, workspace } = createView({ shouldMount: true });
const { plugin, view, workspace } = createView();
await openView(view);

plugin.navigation.expand();
Expand Down
13 changes: 0 additions & 13 deletions src/ui/PodcastView/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,6 @@ export class MainView extends ItemView {
this.app.workspace.requestSaveLayout();
});

if (!this.plugin.shouldMountPodcastView()) {
return;
}

this.mountPodcastView();
}

mountPodcastView(): void {
if (this.podcastView) {
return;
}

this.podcastView = mount(PodcastView, {
target: this.contentEl,
props: { services: this.services() },
Expand Down Expand Up @@ -159,6 +147,5 @@ export class MainView extends ItemView {
}

this.contentEl.empty();
this.plugin.unregisterPodcastView(this);
}
}
Loading
Loading