mirror of
https://github.com/4source/favorites-obsidian-plugin.git
synced 2026-07-22 06:44:46 +00:00
[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
This commit is contained in:
parent
81a01388fa
commit
8f706c9372
10 changed files with 531 additions and 62 deletions
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
|
@ -26,6 +26,8 @@ If not related issue exists, open a new one using the appropriate <a href="../..
|
|||
#### 1. Fork the repository.
|
||||
#### 2. Create a new vault
|
||||
Just for testing the plugin so you **can't** exedentially destroy your real vault
|
||||
> [!NOTE]
|
||||
> When you working with ``npm run dev`` the plugin will use another key to identify the favorite lists. This **should** ensure you are not overwriting your real favorite lists. When you are working with ``npm run build`` the plugin will use the real key.
|
||||
#### 3. Clone repository to your local machine.
|
||||
```shell
|
||||
cd path/to/vault
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -17,6 +17,7 @@ main.js
|
|||
|
||||
# obsidian
|
||||
data.json
|
||||
backup
|
||||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
|
|
|||
57
README.md
57
README.md
|
|
@ -8,6 +8,7 @@ Stop re-searching the same plugins or themes in every vault. With one central li
|
|||
- Plugin browser
|
||||
- Community plugins tab
|
||||
- Theme browser
|
||||
- via [Commands](#commands)
|
||||
- **Vault Independence** - Favorites are stored globally on your device, not tied to a single vault
|
||||
|
||||
# How It Works
|
||||
|
|
@ -26,6 +27,62 @@ For plugins you can also favorite already installed plugins directly from the Co
|
|||
|
||||
<img width="1104" height="1004" alt="image" src="https://github.com/user-attachments/assets/960eac0d-fea2-48e1-8faf-e54a7504149e" />
|
||||
|
||||
# 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
|
|
|||
318
src/main.ts
318
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<CommunityPlugin>(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<CommunityTheme>(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<CommunityPlugin>(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<CommunityTheme>(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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
src/modals/CommunitySuggestModal.ts
Normal file
35
src/modals/CommunitySuggestModal.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { App, FuzzyMatch, FuzzySuggestModal, renderResults } from 'obsidian';
|
||||
|
||||
export class CommunitySuggestModal<T extends { name: string, author: string }> extends FuzzySuggestModal<T> {
|
||||
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<T>, 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);
|
||||
}
|
||||
}
|
||||
77
src/modals/DialogModal.ts
Normal file
77
src/modals/DialogModal.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
23
src/modals/StringSuggestModal.ts
Normal file
23
src/modals/StringSuggestModal.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { App, FuzzySuggestModal } from 'obsidian';
|
||||
|
||||
export class StringSuggestModal extends FuzzySuggestModal<string> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
17
src/types.d.ts
vendored
17
src/types.d.ts
vendored
|
|
@ -2,7 +2,22 @@ import { } from 'obsidian';
|
|||
|
||||
declare module 'obsidian' {
|
||||
interface App {
|
||||
setting: SettingsModal
|
||||
setting: SettingsModal;
|
||||
plugins: {
|
||||
manifests: Record<string, PluginManifest>;
|
||||
};
|
||||
customCss: {
|
||||
themes: Record<string, ThemeManifest>;
|
||||
}
|
||||
}
|
||||
|
||||
interface ThemeManifest {
|
||||
author: string;
|
||||
authorUrl: string;
|
||||
dir: string;
|
||||
minAppVersion: string;
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface CommunityItem {
|
||||
|
|
|
|||
60
src/util/GitHub.ts
Normal file
60
src/util/GitHub.ts
Normal file
|
|
@ -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<CommunityPlugin[] | undefined> {
|
||||
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<CommunityTheme[] | undefined> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue