generated from tpl/obsidian-sample-plugin
			Reorganize ui code into ui directory
This commit is contained in:
		
							parent
							
								
									976fe482b4
								
							
						
					
					
						commit
						259b939bbb
					
				
							
								
								
									
										23
									
								
								src/main.ts
								
								
								
								
							
							
						
						
									
										23
									
								
								src/main.ts
								
								
								
								
							| 
						 | 
				
			
			@ -5,9 +5,13 @@ import {
 | 
			
		|||
	DEFAULT_SETTINGS,
 | 
			
		||||
} from "./settings/settings";
 | 
			
		||||
import { getBookByLegacyId, type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
import { Templater } from "./utils/templater";
 | 
			
		||||
import { GoodreadsSearchModal } from "@views/goodreads-search-modal";
 | 
			
		||||
import { GoodreadsSearchSuggestModal } from "@views/goodreads-search-suggest-modal";
 | 
			
		||||
import { Templater } from "@utils/templater";
 | 
			
		||||
import {
 | 
			
		||||
	GoodreadsSearchModal,
 | 
			
		||||
	GoodreadsSearchSuggestModal,
 | 
			
		||||
	ReadingProgressModal,
 | 
			
		||||
	RatingModal,
 | 
			
		||||
} from "@ui/modals";
 | 
			
		||||
import {
 | 
			
		||||
	CONTENT_TYPE_EXTENSIONS,
 | 
			
		||||
	IN_PROGRESS_STATE,
 | 
			
		||||
| 
						 | 
				
			
			@ -15,10 +19,7 @@ import {
 | 
			
		|||
	TO_BE_READ_STATE,
 | 
			
		||||
} from "./const";
 | 
			
		||||
import { ReadingLog, Storage } from "@utils/storage";
 | 
			
		||||
import { ReadingProgressModal } from "@views/reading-progress-modal";
 | 
			
		||||
import { RatingModal } from "@views/rating-modal";
 | 
			
		||||
import { renderCodeBlockProcessor } from "@utils/svelte";
 | 
			
		||||
import ReadingLogViewer from "@components/ReadingLogViewer.svelte";
 | 
			
		||||
import { registerReadingLogCodeBlockProcessor } from "@ui/code-blocks";
 | 
			
		||||
 | 
			
		||||
export default class BookTrackerPlugin extends Plugin {
 | 
			
		||||
	settings: BookTrackerPluginSettings;
 | 
			
		||||
| 
						 | 
				
			
			@ -65,13 +66,7 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
 | 
			
		||||
		this.addSettingTab(new BookTrackerSettingTab(this.app, this));
 | 
			
		||||
 | 
			
		||||
		this.registerMarkdownCodeBlockProcessor(
 | 
			
		||||
			"readinglog",
 | 
			
		||||
			renderCodeBlockProcessor(ReadingLogViewer, {
 | 
			
		||||
				app: this.app,
 | 
			
		||||
				readingLog: this.readingLog,
 | 
			
		||||
			})
 | 
			
		||||
		);
 | 
			
		||||
		registerReadingLogCodeBlockProcessor(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onunload() {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
import { registerCodeBlockRenderer } from ".";
 | 
			
		||||
import { SvelteCodeBlockRenderer } from "./SvelteCodeBlockRenderer";
 | 
			
		||||
import ReadingLogCodeBlockView from "./ReadingLogCodeBlockView.svelte";
 | 
			
		||||
import type BookTrackerPlugin from "@src/main";
 | 
			
		||||
 | 
			
		||||
export function registerReadingLogCodeBlockProcessor(
 | 
			
		||||
	plugin: BookTrackerPlugin
 | 
			
		||||
): void {
 | 
			
		||||
	registerCodeBlockRenderer(
 | 
			
		||||
		plugin,
 | 
			
		||||
		"readinglog",
 | 
			
		||||
		(_source, el) => new ReadingLogCodeBlockRenderer(el, plugin)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ReadingLogCodeBlockRenderer extends SvelteCodeBlockRenderer<
 | 
			
		||||
	typeof ReadingLogCodeBlockView
 | 
			
		||||
> {
 | 
			
		||||
	constructor(contentEl: HTMLElement, plugin: BookTrackerPlugin) {
 | 
			
		||||
		super(contentEl, ReadingLogCodeBlockView, {
 | 
			
		||||
			props: {
 | 
			
		||||
				app: plugin.app,
 | 
			
		||||
				readingLog: plugin.readingLog,
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onunload() {}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,8 +3,7 @@
 | 
			
		|||
	import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
	import type { App } from "obsidian";
 | 
			
		||||
	import { Edit, Trash, Plus } from "lucide-svelte";
 | 
			
		||||
	import { ReadingLogEntryEditModal } from "@views/reading-log-entry-edit-modal";
 | 
			
		||||
	import { ReadingLogNewEntryModal } from "@views/reading-log-new-entry-modal";
 | 
			
		||||
	import { ReadingLogEntryEditModal } from "@ui/modals";
 | 
			
		||||
 | 
			
		||||
	const ALL_TIME = "ALL_TIME";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -70,23 +69,24 @@
 | 
			
		|||
	);
 | 
			
		||||
 | 
			
		||||
	function createEntry() {
 | 
			
		||||
		const modal = new ReadingLogNewEntryModal(app);
 | 
			
		||||
		modal.once("submit", async (event) => {
 | 
			
		||||
		const modal = new ReadingLogEntryEditModal(app, async (entry) => {
 | 
			
		||||
			modal.close();
 | 
			
		||||
			await readingLog.addRawEntry(event.entry);
 | 
			
		||||
			await readingLog.addRawEntry(entry);
 | 
			
		||||
			reload();
 | 
			
		||||
		});
 | 
			
		||||
		modal.open();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function editEntry(i: number, entry: ReadingLogEntry) {
 | 
			
		||||
		const modal = new ReadingLogEntryEditModal(app, entry);
 | 
			
		||||
		modal.once("submit", async (event) => {
 | 
			
		||||
			console.log(i, event);
 | 
			
		||||
			modal.close();
 | 
			
		||||
			await readingLog.updateEntry(i, event.entry);
 | 
			
		||||
			reload();
 | 
			
		||||
		});
 | 
			
		||||
		const modal = new ReadingLogEntryEditModal(
 | 
			
		||||
			app,
 | 
			
		||||
			async (entry) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				await readingLog.updateEntry(i, entry);
 | 
			
		||||
				reload();
 | 
			
		||||
			},
 | 
			
		||||
			entry,
 | 
			
		||||
		);
 | 
			
		||||
		modal.open();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import { mount, unmount, type Component, type MountOptions } from "svelte";
 | 
			
		||||
import { MarkdownRenderChild } from "obsidian";
 | 
			
		||||
 | 
			
		||||
export class SvelteCodeBlockRenderer<
 | 
			
		||||
	TComponent extends Component<TProps, TExports, TBindings>,
 | 
			
		||||
	TProps extends Record<string, any> = {},
 | 
			
		||||
	TExports extends Record<string, any> = {},
 | 
			
		||||
	TBindings extends keyof TProps | "" = string
 | 
			
		||||
> extends MarkdownRenderChild {
 | 
			
		||||
	protected component: TExports | undefined;
 | 
			
		||||
 | 
			
		||||
	constructor(
 | 
			
		||||
		private readonly contentEl: HTMLElement,
 | 
			
		||||
		private readonly componentCtor: TComponent,
 | 
			
		||||
		private readonly mountOpts: Omit<MountOptions<TProps>, "target">
 | 
			
		||||
	) {
 | 
			
		||||
		super(contentEl);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onload(): void {
 | 
			
		||||
		this.component = mount(this.componentCtor, {
 | 
			
		||||
			...this.mountOpts,
 | 
			
		||||
			target: this.contentEl,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onunload(): void {
 | 
			
		||||
		if (this.component) {
 | 
			
		||||
			unmount(this.component);
 | 
			
		||||
			this.component = undefined;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
import type { Plugin } from "obsidian";
 | 
			
		||||
import { mount, unmount, type Component, type MountOptions } from "svelte";
 | 
			
		||||
import { MarkdownRenderChild } from "obsidian";
 | 
			
		||||
 | 
			
		||||
export function registerCodeBlockRenderer(
 | 
			
		||||
	plugin: Plugin,
 | 
			
		||||
	name: string,
 | 
			
		||||
	renderer: (source: string, el: HTMLElement) => MarkdownRenderChild
 | 
			
		||||
): void {
 | 
			
		||||
	plugin.registerMarkdownCodeBlockProcessor(name, (source, el, ctx) => {
 | 
			
		||||
		ctx.addChild(renderer(source, el));
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class SvelteCodeBlockRenderer<
 | 
			
		||||
	TComponent extends Component<TProps, TExports, TBindings>,
 | 
			
		||||
	TProps extends Record<string, any> = {},
 | 
			
		||||
	TExports extends Record<string, any> = {},
 | 
			
		||||
	TBindings extends keyof TProps | "" = string
 | 
			
		||||
> extends MarkdownRenderChild {
 | 
			
		||||
	protected component: TExports | undefined;
 | 
			
		||||
 | 
			
		||||
	constructor(
 | 
			
		||||
		private readonly contentEl: HTMLElement,
 | 
			
		||||
		private readonly componentCtor: TComponent,
 | 
			
		||||
		private readonly mountOpts: Omit<MountOptions<TProps>, "target">
 | 
			
		||||
	) {
 | 
			
		||||
		super(contentEl);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onload(): void {
 | 
			
		||||
		this.component = mount(this.componentCtor, {
 | 
			
		||||
			...this.mountOpts,
 | 
			
		||||
			target: this.contentEl,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onunload(): void {
 | 
			
		||||
		if (this.component) {
 | 
			
		||||
			unmount(this.component);
 | 
			
		||||
			this.component = undefined;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { registerReadingLogCodeBlockProcessor } from "./ReadingLogCodeBlock";
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
import GoodreadsSearchModalView from "./GoodreadsSearchModalView.svelte";
 | 
			
		||||
import { type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./SvelteModal";
 | 
			
		||||
 | 
			
		||||
export class GoodreadsSearchModal extends SvelteModal<
 | 
			
		||||
	typeof GoodreadsSearchModalView
 | 
			
		||||
> {
 | 
			
		||||
	constructor(
 | 
			
		||||
		app: App,
 | 
			
		||||
		onSearch: (error: any, results: SearchResult[]) => void = () => {}
 | 
			
		||||
	) {
 | 
			
		||||
		super(app, GoodreadsSearchModalView, { props: { onSearch } });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static createAndOpen(app: App): Promise<SearchResult[]> {
 | 
			
		||||
		return new Promise((resolve, reject) => {
 | 
			
		||||
			const modal = new GoodreadsSearchModal(app, (error, results) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
 | 
			
		||||
				if (error) {
 | 
			
		||||
					reject(error);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				resolve(results);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,11 +2,10 @@
 | 
			
		|||
	import { searchBooks, type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
 | 
			
		||||
	interface Props {
 | 
			
		||||
		onSearch: (query: string, results: SearchResult[]) => void;
 | 
			
		||||
		onError: (error: Error) => void;
 | 
			
		||||
		onSearch: (error: any, results?: SearchResult[]) => void;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let { onSearch, onError }: Props = $props();
 | 
			
		||||
	let { onSearch }: Props = $props();
 | 
			
		||||
 | 
			
		||||
	let query = $state("");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -16,12 +15,12 @@
 | 
			
		|||
			try {
 | 
			
		||||
				const results = await searchBooks(query);
 | 
			
		||||
				if (results.length === 0) {
 | 
			
		||||
					onError(new Error("No results found."));
 | 
			
		||||
					onSearch(new Error("No results found."));
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				onSearch(query, results);
 | 
			
		||||
				onSearch(null, results);
 | 
			
		||||
			} catch (error) {
 | 
			
		||||
				onError(error as Error);
 | 
			
		||||
				onSearch(error);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import GoodreadsSearchSuggestion from "@components/GoodreadsSearchSuggestion.svelte";
 | 
			
		||||
import GoodreadsSearchSuggestion from "./GoodreadsSearchSuggestion.svelte";
 | 
			
		||||
import { type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
import { App, Notice, SuggestModal } from "obsidian";
 | 
			
		||||
import { mount } from "svelte";
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ export class GoodreadsSearchSuggestModal extends SuggestModal<SearchResult> {
 | 
			
		|||
		super(app);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getSuggestions(query: string): SearchResult[] | Promise<SearchResult[]> {
 | 
			
		||||
	getSuggestions(_query: string): SearchResult[] | Promise<SearchResult[]> {
 | 
			
		||||
		return this.results;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
import RatingModalView from "./RatingModalView.svelte";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./SvelteModal";
 | 
			
		||||
 | 
			
		||||
export class RatingModal extends SvelteModal<typeof RatingModalView> {
 | 
			
		||||
	constructor(app: App, onSubmit: (rating: number) => void = () => {}) {
 | 
			
		||||
		super(app, RatingModalView, { props: { onSubmit } });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static createAndOpen(app: App): Promise<number> {
 | 
			
		||||
		return new Promise((resolve) => {
 | 
			
		||||
			const modal = new RatingModal(app, (rating) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				resolve(rating);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import RatingInput from "./RatingInput.svelte";
 | 
			
		||||
	import RatingInput from "@ui/components/RatingInput.svelte";
 | 
			
		||||
 | 
			
		||||
	interface Props {
 | 
			
		||||
		onSubmit: (value: number) => void;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
import ReadingLogEntryEditModalView from "./ReadingLogEntryEditModalView.svelte";
 | 
			
		||||
import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./SvelteModal";
 | 
			
		||||
 | 
			
		||||
export class ReadingLogEntryEditModal extends SvelteModal<
 | 
			
		||||
	typeof ReadingLogEntryEditModalView
 | 
			
		||||
> {
 | 
			
		||||
	constructor(
 | 
			
		||||
		app: App,
 | 
			
		||||
		onSubmit?: (entry: ReadingLogEntry) => void,
 | 
			
		||||
		entry?: ReadingLogEntry
 | 
			
		||||
	) {
 | 
			
		||||
		super(app, ReadingLogEntryEditModalView, {
 | 
			
		||||
			props: { app, entry, onSubmit },
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
import ReadingProgressModalView from "./ReadingProgressModalView.svelte";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./SvelteModal";
 | 
			
		||||
 | 
			
		||||
export class ReadingProgressModal extends SvelteModal<
 | 
			
		||||
	typeof ReadingProgressModalView
 | 
			
		||||
> {
 | 
			
		||||
	constructor(
 | 
			
		||||
		app: App,
 | 
			
		||||
		pageLength: number,
 | 
			
		||||
		onSubmit: (pageNumber: number) => void = () => {}
 | 
			
		||||
	) {
 | 
			
		||||
		super(app, ReadingProgressModalView, {
 | 
			
		||||
			props: { pageLength, onSubmit },
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static createAndOpen(app: App, pageLength: number): Promise<number> {
 | 
			
		||||
		return new Promise((resolve) => {
 | 
			
		||||
			const modal = new ReadingProgressModal(
 | 
			
		||||
				app,
 | 
			
		||||
				pageLength,
 | 
			
		||||
				(pageNumber: number) => {
 | 
			
		||||
					modal.close();
 | 
			
		||||
					resolve(pageNumber);
 | 
			
		||||
				}
 | 
			
		||||
			);
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
export { GoodreadsSearchModal } from "./GoodreadsSearchModal";
 | 
			
		||||
export { GoodreadsSearchSuggestModal } from "./GoodreadsSearchSuggestModal";
 | 
			
		||||
export { RatingModal } from "./RatingModal";
 | 
			
		||||
export { ReadingLogEntryEditModal } from "./ReadingLogEntryEditModal";
 | 
			
		||||
export { ReadingProgressModal } from "./ReadingProgressModal";
 | 
			
		||||
| 
						 | 
				
			
			@ -1,59 +0,0 @@
 | 
			
		|||
export abstract class Event {}
 | 
			
		||||
 | 
			
		||||
export type EventHandler<T> = (event: T) => void;
 | 
			
		||||
 | 
			
		||||
export type EventHandlerMap<TEventMap, T extends keyof TEventMap> = Record<
 | 
			
		||||
	T,
 | 
			
		||||
	EventHandler<TEventMap[T]>[]
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
type Constructor = new (...args: any[]) => {};
 | 
			
		||||
 | 
			
		||||
export function EventEmitter<TEventMap, TBase extends Constructor>(
 | 
			
		||||
	Base: TBase
 | 
			
		||||
) {
 | 
			
		||||
	return class extends Base {
 | 
			
		||||
		private readonly listeners: EventHandlerMap<
 | 
			
		||||
			TEventMap,
 | 
			
		||||
			keyof TEventMap
 | 
			
		||||
		> = {} as EventHandlerMap<TEventMap, keyof TEventMap>;
 | 
			
		||||
 | 
			
		||||
		public on<T extends keyof TEventMap>(
 | 
			
		||||
			type: T,
 | 
			
		||||
			handler: EventHandler<TEventMap[T]>
 | 
			
		||||
		) {
 | 
			
		||||
			if (!this.listeners[type]) {
 | 
			
		||||
				this.listeners[type] = [];
 | 
			
		||||
			}
 | 
			
		||||
			this.listeners[type].push(handler);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public off<T extends keyof TEventMap>(
 | 
			
		||||
			type: T,
 | 
			
		||||
			handler: EventHandler<TEventMap[T]>
 | 
			
		||||
		) {
 | 
			
		||||
			if (this.listeners[type]) {
 | 
			
		||||
				this.listeners[type] = this.listeners[type].filter(
 | 
			
		||||
					(h) => h !== handler
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public once<T extends keyof TEventMap>(
 | 
			
		||||
			type: T,
 | 
			
		||||
			handler: EventHandler<TEventMap[T]>
 | 
			
		||||
		) {
 | 
			
		||||
			const wrappedHandler = (event: TEventMap[T]) => {
 | 
			
		||||
				handler(event);
 | 
			
		||||
				this.off(type, wrappedHandler);
 | 
			
		||||
			};
 | 
			
		||||
			this.on(type, wrappedHandler);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public emit<T extends keyof TEventMap>(type: T, event: TEventMap[T]) {
 | 
			
		||||
			if (this.listeners[type]) {
 | 
			
		||||
				this.listeners[type].forEach((handler) => handler(event));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,33 +0,0 @@
 | 
			
		|||
import type { MarkdownPostProcessorContext } from "obsidian";
 | 
			
		||||
import { mount, unmount, type Component, type ComponentProps } from "svelte";
 | 
			
		||||
import { MarkdownRenderChild } from "obsidian";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Renders a svelte component as a code block processor.
 | 
			
		||||
 * @param component the svelte component to render.
 | 
			
		||||
 * @param props properties forwarded to the component.
 | 
			
		||||
 * @param stateProvider an optional provider that handles state & state updates of the code block processor.
 | 
			
		||||
 */
 | 
			
		||||
export function renderCodeBlockProcessor<C extends Component>(
 | 
			
		||||
	component: C,
 | 
			
		||||
	props: ComponentProps<C>
 | 
			
		||||
) {
 | 
			
		||||
	return (
 | 
			
		||||
		source: string,
 | 
			
		||||
		containerEl: HTMLElement,
 | 
			
		||||
		ctx: MarkdownPostProcessorContext
 | 
			
		||||
	) => {
 | 
			
		||||
		const svelteComponent = mount(component, {
 | 
			
		||||
			target: containerEl,
 | 
			
		||||
			props,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		class UnloadSvelteComponent extends MarkdownRenderChild {
 | 
			
		||||
			onunload() {
 | 
			
		||||
				unmount(svelteComponent);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.addChild(new UnloadSvelteComponent(containerEl));
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { App, TFile } from "obsidian";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import * as Handlebars from "handlebars";
 | 
			
		||||
 | 
			
		||||
Handlebars.registerHelper("formatDate", (date: Date, format: string) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,58 +0,0 @@
 | 
			
		|||
import GoodreadsSearch from "@components/GoodreadsSearch.svelte";
 | 
			
		||||
import { type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./svelte-modal";
 | 
			
		||||
 | 
			
		||||
export class SearchEvent extends Event {
 | 
			
		||||
	constructor(
 | 
			
		||||
		public readonly query: string,
 | 
			
		||||
		public readonly results: SearchResult[]
 | 
			
		||||
	) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ErrorEvent extends Event {
 | 
			
		||||
	constructor(public readonly error: Error) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface GoodreadsSearchModalEventMap {
 | 
			
		||||
	search: SearchEvent;
 | 
			
		||||
	error: ErrorEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class GoodreadsSearchModal extends EventEmitter<
 | 
			
		||||
	GoodreadsSearchModalEventMap,
 | 
			
		||||
	typeof SvelteModal<typeof GoodreadsSearch>
 | 
			
		||||
>(SvelteModal) {
 | 
			
		||||
	constructor(app: App) {
 | 
			
		||||
		super(app, GoodreadsSearch, {
 | 
			
		||||
			props: {
 | 
			
		||||
				onError: (error: Error) => {
 | 
			
		||||
					this.emit("error", new ErrorEvent(error));
 | 
			
		||||
				},
 | 
			
		||||
				onSearch: (query: string, results: SearchResult[]) => {
 | 
			
		||||
					this.emit("search", new SearchEvent(query, results));
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static createAndOpen(app: App): Promise<SearchResult[]> {
 | 
			
		||||
		return new Promise((resolve, reject) => {
 | 
			
		||||
			const modal = new GoodreadsSearchModal(app);
 | 
			
		||||
			modal.once("search", (event: SearchEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				resolve(event.results);
 | 
			
		||||
			});
 | 
			
		||||
			modal.once("error", (event: ErrorEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				reject(event.error);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +0,0 @@
 | 
			
		|||
import Rating from "@components/Rating.svelte";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./svelte-modal";
 | 
			
		||||
 | 
			
		||||
class SubmitEvent extends Event {
 | 
			
		||||
	constructor(public readonly rating: number) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface RatingModalEventMap {
 | 
			
		||||
	submit: SubmitEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class RatingModal extends EventEmitter<
 | 
			
		||||
	RatingModalEventMap,
 | 
			
		||||
	typeof SvelteModal<typeof Rating>
 | 
			
		||||
>(SvelteModal) {
 | 
			
		||||
	constructor(app: App) {
 | 
			
		||||
		super(app, Rating, {
 | 
			
		||||
			props: {
 | 
			
		||||
				onSubmit: (rating: number) =>
 | 
			
		||||
					this.emit("submit", new SubmitEvent(rating)),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static createAndOpen(app: App): Promise<number> {
 | 
			
		||||
		return new Promise((resolve) => {
 | 
			
		||||
			const modal = new RatingModal(app);
 | 
			
		||||
			modal.once("submit", (event: SubmitEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				resolve(event.rating);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,31 +0,0 @@
 | 
			
		|||
import ReadingLogEntryEditor from "@components/ReadingLogEntryEditor.svelte";
 | 
			
		||||
import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./svelte-modal";
 | 
			
		||||
 | 
			
		||||
export class SubmitEvent extends Event {
 | 
			
		||||
	constructor(public readonly entry: ReadingLogEntry) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ReadingLogEntryEditModalEventMap {
 | 
			
		||||
	submit: SubmitEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ReadingLogEntryEditModal extends EventEmitter<
 | 
			
		||||
	ReadingLogEntryEditModalEventMap,
 | 
			
		||||
	typeof SvelteModal<typeof ReadingLogEntryEditor>
 | 
			
		||||
>(SvelteModal) {
 | 
			
		||||
	constructor(app: App, entry: ReadingLogEntry) {
 | 
			
		||||
		super(app, ReadingLogEntryEditor, {
 | 
			
		||||
			props: {
 | 
			
		||||
				app,
 | 
			
		||||
				entry,
 | 
			
		||||
				onSubmit: (entry: ReadingLogEntry) =>
 | 
			
		||||
					this.emit("submit", new SubmitEvent(entry)),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,30 +0,0 @@
 | 
			
		|||
import ReadingLogEntryEditor from "@components/ReadingLogEntryEditor.svelte";
 | 
			
		||||
import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./svelte-modal";
 | 
			
		||||
 | 
			
		||||
export class SubmitEvent extends Event {
 | 
			
		||||
	constructor(public readonly entry: ReadingLogEntry) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ReadingLogNewEntryModalEventMap {
 | 
			
		||||
	submit: SubmitEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ReadingLogNewEntryModal extends EventEmitter<
 | 
			
		||||
	ReadingLogNewEntryModalEventMap,
 | 
			
		||||
	typeof SvelteModal<typeof ReadingLogEntryEditor>
 | 
			
		||||
>(SvelteModal) {
 | 
			
		||||
	constructor(app: App) {
 | 
			
		||||
		super(app, ReadingLogEntryEditor, {
 | 
			
		||||
			props: {
 | 
			
		||||
				app,
 | 
			
		||||
				onSubmit: (entry: ReadingLogEntry) =>
 | 
			
		||||
					this.emit("submit", new SubmitEvent(entry)),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,41 +0,0 @@
 | 
			
		|||
import ReadingProgress from "@components/ReadingProgress.svelte";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
import { SvelteModal } from "./svelte-modal";
 | 
			
		||||
 | 
			
		||||
export class SubmitEvent extends Event {
 | 
			
		||||
	constructor(public readonly pageNumber: number) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ReadingProgressModalEventMap {
 | 
			
		||||
	submit: SubmitEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ReadingProgressModal extends EventEmitter<
 | 
			
		||||
	ReadingProgressModalEventMap,
 | 
			
		||||
	typeof SvelteModal<typeof ReadingProgress>
 | 
			
		||||
>(SvelteModal) {
 | 
			
		||||
	constructor(app: App, pageLength: number) {
 | 
			
		||||
		super(app, ReadingProgress, {
 | 
			
		||||
			props: {
 | 
			
		||||
				pageLength,
 | 
			
		||||
				onSubmit: (pageNumber: number) => {
 | 
			
		||||
					this.emit("submit", new SubmitEvent(pageNumber));
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static createAndOpen(app: App, pageLength: number): Promise<number> {
 | 
			
		||||
		return new Promise((resolve) => {
 | 
			
		||||
			const modal = new ReadingProgressModal(app, pageLength);
 | 
			
		||||
			modal.once("submit", (event: SubmitEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				resolve(event.pageNumber);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,9 +20,9 @@
 | 
			
		|||
      "ES7"
 | 
			
		||||
    ],
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@components/*": ["src/components/*"],
 | 
			
		||||
      "@data-sources/*": ["src/data-sources/*"],
 | 
			
		||||
      "@settings/*": ["src/settings/*"],
 | 
			
		||||
      "@ui/*": ["src/ui/*"],
 | 
			
		||||
      "@utils/*": ["src/utils/*"],
 | 
			
		||||
      "@views/*": ["src/views/*"],
 | 
			
		||||
      "@src/*": ["src/*"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue