From 8f706c9372050ebe698c5baa94b680b17f2fc444 Mon Sep 17 00:00:00 2001 From: 4Source <38220764+4Source@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:04:43 +0100 Subject: [PATCH] [FEAT] Add Commands (#11) * Add commands for managing favorite lists * Refactor favoritePlugins and favoriteThemes handling for improved clarity and performance * Add community plugin/theme management commands and fetch functions * Add note about using a separate key for favorite lists during development * Fix plugin id * Add backup and restore command for favorite lists * Add detailed command descriptions for managing favorite lists in README --- .github/CONTRIBUTING.md | 2 + .gitignore | 1 + README.md | 57 +++++ src/constants.ts | 3 +- src/main.ts | 318 ++++++++++++++++++++++------ src/modals/CommunitySuggestModal.ts | 35 +++ src/modals/DialogModal.ts | 77 +++++++ src/modals/StringSuggestModal.ts | 23 ++ src/types.d.ts | 17 +- src/util/GitHub.ts | 60 ++++++ 10 files changed, 531 insertions(+), 62 deletions(-) create mode 100644 src/modals/CommunitySuggestModal.ts create mode 100644 src/modals/DialogModal.ts create mode 100644 src/modals/StringSuggestModal.ts create mode 100644 src/util/GitHub.ts diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 452faeb..a0c3550 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -26,6 +26,8 @@ If not related issue exists, open a new one using the appropriate +# Commands +If you prefer managing your favorites list with commands here are the available commands +- [Add plugin to favorite list](#add-plugin-to-favorite-list) +- [Add theme to favorite list](#add-theme-to-favorite-list) +- [Remove plugin from favorite list](#remove-plugin-from-favorite-list) +- [Remove theme from favorite list](#remove-theme-from-favorite-list) +- [Clear the plugin favorites lists](#clear-the-plugin-favorites-lists) +- [Clear the theme favorites lists](#clear-the-theme-favorites-lists) +- [Manually load the favorites lists](#manually-load-the-favorites-lists) +- [Manually save the favorites lists](#manually-save-the-favorites-lists) +- [Export favorite lists to file](#export-favorite-lists-to-file) +- [Import favorite lists from file](#import-favorite-lists-from-file) + +## Add plugin to favorite list +Search and select a community plugin to add it to your favorites list. The list is fetched from the community plugin registry. + +## Add theme to favorite list +Search and select a community theme to add it to your favorites list. The list is fetched from the community theme registry. + +## Remove plugin from favorite list +Search and select from your current favorite plugins to remove it from your favorites list. + +## Remove theme from favorite list +Search and select from your current favorite themes to remove it from your favorites list. + +## Clear the plugin favorites lists +Permanently removes ***all*** plugins from your favorites list after confirmation. +> [!WARNING] +> This action cannot be undone. + +## Clear the theme favorites lists +Permanently removes ***all*** themes from your favorites list after confirmation. +> [!WARNING] +> This action cannot be undone. + +## Manually load the favorites lists +Reloads the saved favorite plugins and themes. Use this to restore your favorites if they were changed externally or need to be refreshed. +> [!NOTE] +> This is normally not necessary because the plugin handles this internally. + +## Manually save the favorites lists +Forces the plugin to immediately save your current favorite plugins and themes. Use this if you want to ensure your favorites are persistent. +> [!WARNING] +> This could overwrite changes from another running Obsidian instance if the changes from there are not loaded beforehand. + +> [!NOTE] +> This is normally not necessary because the plugin handles this internally. + +## Export favorite lists to file +Creates a backup file containing your current favorite plugins and themes. The backup is saved as a timestamped JSON file inside the plugin’s backup folder within your vault configuration directory. + +## Import favorite lists from file +Restores favorite plugins and themes from a previously created backup file. You will be prompted to select a backup file from the plugin’s backup folder. After confirmation imported favorites will replaces your current favorites with the contents of the selected backup. +> [!WARNING] +> This will overwrite current favorite lists and can't be undone. + # Contribution Feel free to contribute. diff --git a/src/constants.ts b/src/constants.ts index 20ab87b..ed740f7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,5 @@ -export const PLUGIN_ID = 'favorite'; +export const PLUGIN_ID = 'favorites'; +export const PLUGIN_BACKUP_BASE_PATH = `/plugins/${PLUGIN_ID}/backup`; export const MONKEY_KEY_MODAL_OPEN = `${PLUGIN_ID}-Modal-open`; export const MONKEY_KEY_SETTINGS_MODAL_OPEN_TAB = `${PLUGIN_ID}-SettingsModal-openTab`; export const MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS = `${PLUGIN_ID}-CommunityPluginModal-updateItems`; diff --git a/src/main.ts b/src/main.ts index 251dbca..0c6b694 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,16 @@ -import { CommunityItem, CommunityModal, CommunityPluginsSettingTab, Modal, Plugin, setIcon, SettingsModal, SettingTab, setTooltip } from 'obsidian'; +import { CommunityItem, CommunityPluginsSettingTab, Modal, normalizePath, Notice, Plugin, setIcon, SettingsModal, SettingTab, setTooltip } from 'obsidian'; import { dedupe, around } from 'monkey-around'; -import { MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_MODAL_OPEN, MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_SETTINGS_MODAL_OPEN_TAB, MONKEY_KEY_COMMUNITY_PLUGIN_SETTINGS_TAB_RENDER_INSTALLED_PLUGIN } from './constants'; +import { MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_MODAL_OPEN, MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_SETTINGS_MODAL_OPEN_TAB, MONKEY_KEY_COMMUNITY_PLUGIN_SETTINGS_TAB_RENDER_INSTALLED_PLUGIN, PLUGIN_BACKUP_BASE_PATH } from './constants'; +import { DialogModal } from './modals/DialogModal'; +import { CommunitySuggestModal } from './modals/CommunitySuggestModal'; +import { CommunityPlugin, CommunityTheme, fetchCommunityPluginList, fetchCommunityThemeList } from './util/GitHub'; +import { StringSuggestModal } from './modals/StringSuggestModal'; export default class FavoritesPlugin extends Plugin { pluginsKey: string; themesKey: string; - favoritePlugins?: string[]; - favoriteThemes?: string[]; - communityPluginModalPrototype?: CommunityModal; - communityThemesModalPrototype?: CommunityModal; + favoritePlugins: string[]; + favoriteThemes: string[]; uninstallModalOpen?: () => void; uninstallSettingsModalOpenTab?: () => void; uninstallPluginBrowserModalUpdateItems?: () => void; @@ -31,6 +33,7 @@ export default class FavoritesPlugin extends Plugin { throw Error('Missing environment variable \'FAVORITE_THEMES_KEY\''); } console.debug(`Plugins key: ${this.pluginsKey} Themes key: ${this.themesKey}`); + this.loadFavorites(); // eslint-disable-next-line @typescript-eslint/no-this-alias -- Is required because the this context wil change inside the 'monkey-around' functions but the plugin is required to be accessible const plugin = this; @@ -57,19 +60,19 @@ export default class FavoritesPlugin extends Plugin { plugin.loadFavoritePlugins(); const selectedPluginID = args[0].id; - const isFavorite = plugin.favoritePlugins?.contains(selectedPluginID); + const isFavorite = plugin.favoritePlugins.contains(selectedPluginID); const favEl = createDiv('clickable-icon extra-setting-button'); plugin.registerDomEvent(favEl, 'click', () => { if (isFavorite) { - plugin.favoritePlugins?.remove(selectedPluginID); + plugin.favoritePlugins.remove(selectedPluginID); } else { - plugin.favoritePlugins?.push(selectedPluginID); + plugin.favoritePlugins.push(selectedPluginID); } - plugin.saveFavorites(); + plugin.saveFavoritesPlugins(); // Redraw (tab as CommunityPluginsSettingTab).render(false); @@ -131,16 +134,14 @@ export default class FavoritesPlugin extends Plugin { const result = oldMethod && oldMethod.apply(this); // Add to the favorite plugins a tag to visualize it for the user - if (plugin.favoritePlugins) { - plugin.favoritePlugins.forEach(id => { - if (this.items && this.items[id]?.nameEl) { - this.items[id].nameEl.createSpan({ - cls: 'flair', - text: 'favorite', - }); - } - }); - } + plugin.favoritePlugins.forEach(id => { + if (this.items && this.items[id]?.nameEl) { + this.items[id].nameEl.createSpan({ + cls: 'flair', + text: 'favorite', + }); + } + }); return result; }); @@ -167,7 +168,7 @@ export default class FavoritesPlugin extends Plugin { // Load the favorite plugins plugin.loadFavoritePlugins(); - const isFavorite = plugin.favoritePlugins?.contains(this.selectedItemId); + const isFavorite = plugin.favoritePlugins.contains(this.selectedItemId); // Add to the favorite plugins a tag to visualize it for the user if (isFavorite) { @@ -181,13 +182,13 @@ export default class FavoritesPlugin extends Plugin { plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { if (isFavorite) { - plugin.favoritePlugins?.remove(this.selectedItemId); + plugin.favoritePlugins.remove(this.selectedItemId); } else { - plugin.favoritePlugins?.push(this.selectedItemId); + plugin.favoritePlugins.push(this.selectedItemId); } - plugin.saveFavorites(); + plugin.saveFavoritesPlugins(); // Redraw infoEl.detach(); @@ -227,16 +228,14 @@ export default class FavoritesPlugin extends Plugin { } // Add to the favorite themes a tag to visualize it for the user - if (plugin.favoriteThemes) { - plugin.favoriteThemes.forEach(id => { - if (this.items && this.items[id]?.nameEl) { - this.items[id].nameEl.createSpan({ - cls: 'flair', - text: 'favorite', - }); - } - }); - } + plugin.favoriteThemes.forEach(id => { + if (this.items && this.items[id]?.nameEl) { + this.items[id].nameEl.createSpan({ + cls: 'flair', + text: 'favorite', + }); + } + }); return result; }); @@ -268,7 +267,7 @@ export default class FavoritesPlugin extends Plugin { // Load the favorite themes plugin.loadFavoriteThemes(); - const isFavorite = plugin.favoriteThemes?.contains(this.selectedItemId); + const isFavorite = plugin.favoriteThemes.contains(this.selectedItemId); // Add to the favorite themes a tag to visualize it for the user if (isFavorite) { @@ -282,13 +281,13 @@ export default class FavoritesPlugin extends Plugin { plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => { if (isFavorite) { - plugin.favoriteThemes?.remove(this.selectedItemId); + plugin.favoriteThemes.remove(this.selectedItemId); } else { - plugin.favoriteThemes?.push(this.selectedItemId); + plugin.favoriteThemes.push(this.selectedItemId); } - plugin.saveFavorites(); + plugin.saveFavoritesThemes(); // Redraw infoEl.detach(); @@ -321,13 +320,151 @@ export default class FavoritesPlugin extends Plugin { console.warn('Modal.open already patched!'); } - // TODO: Redraw modals if local storage 'favorite-plugins' or 'favorite-themes' changed + this.addCommand({ + id: 'save-favorites-lists', + name: 'Manually save the favorites lists', + callback: () => { + this.saveFavorites(); + new Notice('Saved favorite lists'); + }, + }); - /* - * TODO: Add command to save and reload the favorites list - * TODO: Add command to remove all favorites from list - * TODO: Add command to search for plugin to add to favorites list - */ + this.addCommand({ + id: 'load-favorites-lists', + name: 'Manually load the favorites lists', + callback: () => { + this.loadFavorites(); + new Notice('Loaded favorite lists'); + }, + }); + + this.addCommand({ + id: 'clear-plugin-favorites-lists', + name: 'Clear the plugin favorites lists', + callback: () => { + this.loadFavoritePlugins(); + const numberClear = this.favoritePlugins.length; + new DialogModal(this.app, `Clear ${numberClear} plugin(s) permanently from favorite list?`, '', () => { + this.favoritePlugins = []; + this.saveFavoritesPlugins(); + new Notice(`Cleared ${numberClear} plugins from favorite list`); + }, () => { + new Notice('Canceled clear plugins from favorite list'); + }, 'Clear', true, 'Cancel', false).open(); + }, + }); + + this.addCommand({ + id: 'clear-theme-favorites-lists', + name: 'Clear the theme favorites lists', + callback: () => { + this.loadFavoriteThemes(); + const numberClear = this.favoriteThemes.length; + new DialogModal(this.app, `Clear ${numberClear} theme(s) permanently from favorite list?`, '', () => { + this.favoriteThemes = []; + this.saveFavoritesThemes(); + new Notice(`Cleared ${numberClear} themes from favorite list`); + }, () => { + new Notice('Canceled clear themes from favorite list'); + }, 'Clear', true, 'Cancel', false).open(); + }, + }); + + this.addCommand({ + id: 'search-and-add-plugin-to-favorite-list', + name: 'Add plugin to favorite list', + callback: async () => { + const items = await fetchCommunityPluginList(); + if (!items) { + new Notice('Failed to fetch community plugins. See console for more information.'); + return; + } + + new CommunitySuggestModal(this.app, 'Select plugin which should be added to favorites list...', items, (result) => { + this.loadFavoritePlugins(); + this.favoritePlugins.push(result.id); + this.saveFavoritesPlugins(); + new Notice(`Added ${result.name} to favorite list`); + }).open(); + }, + }); + + this.addCommand({ + id: 'search-and-add-theme-to-favorite-list', + name: 'Add theme to favorite list', + callback: async () => { + const items = await fetchCommunityThemeList(); + if (!items) { + new Notice('Failed to fetch community themes. See console for more information.'); + return; + } + + new CommunitySuggestModal(this.app, 'Select theme which should be added to favorites list...', items, (result) => { + this.loadFavoriteThemes(); + this.favoriteThemes.push(result.name); + this.saveFavoritesThemes(); + new Notice(`Added ${result.name} to favorite list`); + }).open(); + }, + }); + + this.addCommand({ + id: 'search-and-remove-plugin-to-favorite-list', + name: 'Remove plugin from favorite list', + callback: async () => { + this.loadFavoritePlugins(); + if (this.favoritePlugins.length <= 0) { + new Notice('Plugin favorite list is empty'); + return; + } + const communityItems = await fetchCommunityPluginList(); + const installedItems = Object.values(this.app.plugins.manifests); + const items = this.favoritePlugins.map((value) => { + return communityItems?.find((communityValue) => value === communityValue.id) || installedItems.find(installedValue => value === installedValue.id) as unknown as CommunityPlugin || { id: value, name: value, author: 'unknown' } as CommunityPlugin; + }); + + new CommunitySuggestModal(this.app, 'Select plugin which should be removed from the favorites list...', items, (result) => { + this.favoritePlugins.remove(result.id); + this.saveFavoritesPlugins(); + new Notice(`Removed ${result.name} from favorite list`); + }).open(); + }, + }); + + this.addCommand({ + id: 'search-and-remove-theme-to-favorite-list', + name: 'Remove theme from favorite list', + callback: async () => { + this.loadFavoriteThemes(); + if (this.favoriteThemes.length <= 0) { + new Notice('Theme favorite list is empty'); + return; + } + const communityItems = await fetchCommunityThemeList(); + const installedItems = Object.values(this.app.customCss.themes); + const items = this.favoriteThemes.map((value) => { + return communityItems?.find((communityValue) => value === communityValue.name) || installedItems.find(installedValue => value === installedValue.name) as unknown as CommunityTheme || { name: value, author: 'unknown' } as CommunityTheme; + }); + + new CommunitySuggestModal(this.app, 'Select theme which should be removed from the favorites list...', items, (result) => { + this.favoriteThemes.remove(result.name); + this.saveFavoritesThemes(); + new Notice(`Removed ${result.name} from favorite list`); + }).open(); + }, + }); + + this.addCommand({ + id: 'export-favorite-lists', + name: 'Export favorite lists to file', + callback: () => { this.exportFavorites(); }, + }); + + this.addCommand({ + id: 'import-favorite-lists', + name: 'Import favorite lists from file', + callback: () => { this.importFavorites(); }, + }); } onunload() { @@ -384,23 +521,84 @@ export default class FavoritesPlugin extends Plugin { this.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); } - saveFavorites() { - if (this.favoritePlugins) { - if (this.favoritePlugins.length > 0) { - localStorage.setItem(this.pluginsKey, JSON.stringify(this.favoritePlugins)); - } - else { - localStorage.removeItem(this.pluginsKey); - } - } + loadFavorites() { + this.loadFavoritePlugins(); + this.loadFavoriteThemes(); + } - if (this.favoriteThemes) { - if (this.favoriteThemes.length > 0) { - localStorage.setItem(this.themesKey, JSON.stringify(this.favoriteThemes)); - } - else { - localStorage.removeItem(this.themesKey); - } + saveFavoritesPlugins() { + if (this.favoritePlugins.length > 0) { + localStorage.setItem(this.pluginsKey, JSON.stringify(this.favoritePlugins)); + } + else { + localStorage.removeItem(this.pluginsKey); } } + + saveFavoritesThemes() { + if (this.favoriteThemes.length > 0) { + localStorage.setItem(this.themesKey, JSON.stringify(this.favoriteThemes)); + } + else { + localStorage.removeItem(this.themesKey); + } + } + + saveFavorites() { + this.saveFavoritesPlugins(); + this.saveFavoritesThemes(); + } + + async exportFavorites() { + if (this.favoritePlugins.length <= 0 && this.favoriteThemes.length <= 0) { + new Notice('No favorite lists to backup'); + return; + } + + // Ensure backup path does exist + const backupPath = normalizePath(`${this.app.vault.configDir}${PLUGIN_BACKUP_BASE_PATH}`); + await this.app.vault.adapter.mkdir(backupPath); + + // Create the file name + const pad = (n: number) => n.toString().padStart(2, '0'); + const date = new Date(); + const dateString = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}`; + const backupFilePath = normalizePath(`${backupPath}/favorite-${dateString}.json`); + + // Write backup file for lists + await this.app.vault.adapter.write(backupFilePath, JSON.stringify({ plugins: this.favoritePlugins, themes: this.favoriteThemes })); + new Notice(`Favorites list backup successful written to: ${backupFilePath}`); + } + + async importFavorites() { + const backupPath = normalizePath(`${this.app.vault.configDir}${PLUGIN_BACKUP_BASE_PATH}`); + if (!(await this.app.vault.adapter.exists(backupPath))) { + // Backup path does not exist + new Notice(`Backup path does not exist: ${backupPath}`); + return; + } + + const backupFiles = (await this.app.vault.adapter.list(backupPath)).files; + if (backupFiles.length <= 0) { + // Backup path is empty + new Notice('Backup path is empty'); + return; + } + + new StringSuggestModal(this.app, 'Select backup file to load from', backupFiles, async (result) => { + const content = await this.app.vault.adapter.read(result); + const json = JSON.parse(content); + const loadedPlugins = json.plugins || []; + const loadedThemes = json.themes || []; + new DialogModal(this.app, 'Are you sure you want to overwrite current favorite lists?', `The list of favorite plugins will change from ${this.favoritePlugins.length} items to ${loadedPlugins.length} items, and the list of favorite themes will change from ${this.favoriteThemes.length} items to ${loadedThemes.length} items.`, () => { + this.favoritePlugins = loadedPlugins; + this.favoriteThemes = loadedThemes; + this.saveFavorites(); + + new Notice('Loaded favorites backup successfully'); + }, () => { + new Notice('Canceled loading favorites backup by user'); + }, 'Overwrite', true, 'Cancel', false).open(); + }).open(); + } } diff --git a/src/modals/CommunitySuggestModal.ts b/src/modals/CommunitySuggestModal.ts new file mode 100644 index 0000000..c740100 --- /dev/null +++ b/src/modals/CommunitySuggestModal.ts @@ -0,0 +1,35 @@ +import { App, FuzzyMatch, FuzzySuggestModal, renderResults } from 'obsidian'; + +export class CommunitySuggestModal extends FuzzySuggestModal { + private items: T[] = []; + private onSubmit: (result: T) => void = () => { }; + + constructor(app: App, title: string, items: T[], onSubmit: (result: T) => void) { + super(app); + this.setPlaceholder(title); + this.items = items; + this.onSubmit = onSubmit; + } + + getItemText(item: T): string { + return `${item.name} ${item.author}`; + } + + getItems(): T[] { + return this.items; + } + + onChooseItem(item: T): void { + this.onSubmit(item); + } + + renderSuggestion(match: FuzzyMatch, el: HTMLElement) { + const titleEl = el.createDiv(); + renderResults(titleEl, match.item.name, match.match); + + // Only render the matches in the author name. + const authorEl = el.createEl('small'); + const offset = -(match.item.name.length + 1); + renderResults(authorEl, match.item.author, match.match, offset); + } +} \ No newline at end of file diff --git a/src/modals/DialogModal.ts b/src/modals/DialogModal.ts new file mode 100644 index 0000000..fdbc2b1 --- /dev/null +++ b/src/modals/DialogModal.ts @@ -0,0 +1,77 @@ +import { App, Modal, Setting } from 'obsidian'; + +export class DialogModal extends Modal { + message: string; + submit: string; + submitWarning: boolean; + deny: string; + denyWarning: boolean; + onSubmit: () => void; + onDeny: () => void; + + constructor(app: App, title: string, message: string, onSubmit: () => void, onDeny: () => void, submit = 'Agree', submitWarning = false, deny = 'Cancel', denyWarning = true) { + super(app); + + this.titleEl.setText(title); + + this.message = message; + this.onSubmit = onSubmit; + this.onDeny = onDeny; + this.submit = submit; + this.submitWarning = submitWarning; + this.deny = deny; + this.denyWarning = denyWarning; + } + + onOpen(): void { + const { contentEl } = this; + + if (this.message && this.message !== '') { + contentEl.createEl('span', { text: this.message }); + } + + const setting = new Setting(contentEl); + if (this.submitWarning) { + setting.addButton(button => button + .setButtonText(this.submit) + .setWarning() + .onClick(() => { + this.close(); + this.onSubmit(); + })); + } + else { + setting.addButton(button => button + .setButtonText(this.submit) + .onClick(() => { + this.close(); + this.onSubmit(); + })); + } + + if (this.denyWarning) { + setting.addButton(button => button + .setButtonText(this.deny) + .setWarning() + .onClick(() => { + this.close(); + this.onDeny(); + })); + } + else { + setting.addButton(button => button + .setButtonText(this.deny) + .onClick(() => { + this.close(); + this.onDeny(); + })); + } + + setting.setClass('modal-buttons'); + } + + onClose(): void { + const { contentEl } = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/src/modals/StringSuggestModal.ts b/src/modals/StringSuggestModal.ts new file mode 100644 index 0000000..f503187 --- /dev/null +++ b/src/modals/StringSuggestModal.ts @@ -0,0 +1,23 @@ +import { App, FuzzySuggestModal } from 'obsidian'; + +export class StringSuggestModal extends FuzzySuggestModal { + private items: string[] = []; + onSubmit: (result: string) => void = () => { }; + + constructor(app: App, title: string, items: string[], onSubmit: (result: string) => void) { + super(app); + this.setPlaceholder(title); + this.items = items; + this.onSubmit = onSubmit; + } + + getItems(): string[] { + return this.items; + } + getItemText(item: string): string { + return item; + } + onChooseItem(item: string): void { + this.onSubmit(item); + } +} \ No newline at end of file diff --git a/src/types.d.ts b/src/types.d.ts index fa845a4..29161f0 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -2,7 +2,22 @@ import { } from 'obsidian'; declare module 'obsidian' { interface App { - setting: SettingsModal + setting: SettingsModal; + plugins: { + manifests: Record; + }; + customCss: { + themes: Record; + } + } + + interface ThemeManifest { + author: string; + authorUrl: string; + dir: string; + minAppVersion: string; + name: string; + version: string; } interface CommunityItem { diff --git a/src/util/GitHub.ts b/src/util/GitHub.ts new file mode 100644 index 0000000..2f38d4d --- /dev/null +++ b/src/util/GitHub.ts @@ -0,0 +1,60 @@ +/** + * Credits: https://github.com/TfTHacker/obsidian42-brat + */ + +import { request } from 'obsidian'; + +export interface CommunityEntry { + name: string; + author: string; + repo: string; +} + +export interface CommunityPlugin extends CommunityEntry { + id: string; + description: string; +} + +export interface CommunityTheme extends CommunityEntry { + screenshot: string; + modes: string[]; + legacy?: boolean; +} + +/** + * Fetch all community plugin entries. + * @returns A list of community plugins + */ +export async function fetchCommunityPluginList(): Promise { + const URL = 'https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json'; + try { + // Do a request to the url + const response = await request({ url: URL }); + + // Process the response + return (await JSON.parse(response)) as CommunityPlugin[]; + } + catch (e) { + (e as Error).message = 'Failed to fetch community plugin list! ' + (e as Error).message; + console.error(e); + } +} + +/** + * Fetch all community theme entries. + * @returns A list of community themes + */ +export async function fetchCommunityThemeList(): Promise { + const URL = 'https://raw.githubusercontent.com/obsidianmd/obsidian-releases/refs/heads/master/community-css-themes.json'; + try { + // Do a request to the url + const response = await request({ url: URL }); + + // Process the response + return (await JSON.parse(response)) as CommunityTheme[]; + } + catch (e) { + (e as Error).message = 'Failed to fetch community plugin list! ' + (e as Error).message; + console.error(e); + } +}