diff --git a/README.md b/README.md index d65f3a7..e8ab5bc 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ A plugin for iconophiles, designed to blend seamlessly with vanilla Obsidian. -Click almost any icon on a tab, sidebar, ribbon, or title bar to swap in one of the 1,700+ icons included in the app, or one of the 1,900+ emojis that your device supports. +Click almost any icon on a tab, sidebar, ribbon, or the title bar to swap in one of the 1,700+ [Lucide icons](https://lucide.dev/) included in the app, or one of the 1,900+ [emojis](https://www.unicode.org/emoji/charts/full-emoji-list.html) that your device supports. ![Banner](banner.webp) > ⤿ Themes: [Ayu Light & Mirage](https://github.com/taronull/ayu-obsidian) / [Fancy-a-Story](https://github.com/ElsaTam/obsidian-fancy-a-story) / [Primary](https://github.com/primary-theme/obsidian) -Includes language support for English, Arabic, German, Spanish, French, Indonesian, Japanese, Russian, and Simplified Chinese. Most of these languages are currently machine-translated, but if you can supply more accurate translations, absolutely send a message or a pull request :) +Includes language support for English, Arabic, German, Spanish, French, Indonesian, Japanese, Russian, Ukrainian, and Simplified Chinese. Most of these languages are currently machine-translated, but if you can supply more accurate translations, absolutely send a message or a pull request :) ## Supported items diff --git a/i18n/ar.json b/i18n/ar.json index 1dbb101..f2d34e1 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "تبديل جميع أيقونات الملفات", "toggleAllFolderIcons": "تبديل جميع أيقونات المجلد", "toggleMinimalFolderIcons": "تبديل أيقونات المجلد البسيطة", + "toggleMarkdownTabIcons": "تبديل أيقونات علامات تبويب Markdown", + "toggleMenuActions": "تبديل إجراءات القائمة", + "toggleQuickSwitcherIcons": "تبديل أيقونات الانتقال السريع", "toggleBiggerSearchResults": "تبديل نتائج البحث الأكبر", "changeIconCurrentFile": "تغيير أيقونة الملف الحالي" }, @@ -319,6 +322,19 @@ "name": "أيقونات المجلد البسيطة", "desc": "استبدل أسهم المجلد بأيقونات المجلد الخاصة بك." }, + "showMarkdownTabIcons": { + "name": "إظهار أيقونات علامات تبويب Markdown", + "desc": "إظهار أيقونات علامات تبويب ملفات Markdown." + }, + "headingMenusAndDialogs": "القوائم ومربعات الحوار", + "showMenuActions": { + "name": "إظهار إجراءات القائمة", + "desc": "إظهار الإجراءات المتعلقة بالأيقونات في قوائم السياق." + }, + "showQuickSwitcherIcons": { + "name": "الانتقال السريع إظهار أيقونات", + "desc": "الانتقال السريع إظهار الأيقونات في نتائج بحث." + }, "headingIconPicker": "أداة اختيار الأيقونات", "showItemName": { "name": "إظهار اسم العنصر", diff --git a/i18n/de.json b/i18n/de.json index 1d9a275..ca90f10 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "Alle Dateisymbole umschalten", "toggleAllFolderIcons": "Alle Ordnersymbole umschalten", "toggleMinimalFolderIcons": "Minimale Ordnersymbole umschalten", + "toggleMarkdownTabIcons": "Markdown-Tabsymbole umschalten", + "toggleMenuActions": "Menüaktionen umschalten", + "toggleQuickSwitcherIcons": "Schnellauswahlsymbole umschalten", "toggleBiggerSearchResults": "Größere Suchergebnisse umschalten", "changeIconCurrentFile": "Symbol der aktuellen Datei ändern" }, @@ -319,6 +322,19 @@ "name": "Minimale Ordnersymbole", "desc": "Ordnerpfeile durch Ihre Ordnersymbole ersetzen." }, + "showMarkdownTabIcons": { + "name": "Markdown-Tabsymbole anzeigen", + "desc": "Tabsymbole für Markdown-Dateien anzeigen." + }, + "headingMenusAndDialogs": "Menüs & Dialoge", + "showMenuActions": { + "name": "Menüaktionen anzeigen", + "desc": "Symbolbezogene Aktionen in Kontextmenüs anzeigen." + }, + "showQuickSwitcherIcons": { + "name": "Schnellauswahlsymbole anzeigen", + "desc": "Symbole in den Suchergebnissen von Schnellauswahl anzeigen." + }, "headingIconPicker": "Symbolauswahl", "showItemName": { "name": "Elementnamen anzeigen", diff --git a/i18n/es.json b/i18n/es.json index dae594d..b6b7bfe 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "Alternar todos los íconos de archivos", "toggleAllFolderIcons": "Alternar todos los íconos de carpetas", "toggleMinimalFolderIcons": "Alternar íconos de carpeta mínimos", + "toggleMarkdownTabIcons": "Alternar íconos de pestañas de Markdown", + "toggleMenuActions": "Alternar acciones del menú", + "toggleQuickSwitcherIcons": "Alternar íconos del selector rápido", "toggleBiggerSearchResults": "Alternar resultados de búsqueda más grandes", "changeIconCurrentFile": "Cambiar el ícono del archivo actual" }, @@ -319,6 +322,19 @@ "name": "Íconos de carpeta mínimos", "desc": "Reemplazar las flechas de carpeta con los íconos de carpeta." }, + "showMarkdownTabIcons": { + "name": "Mostrar íconos de pestañas de Markdown", + "desc": "Mostrar íconos de pestañas para archivos Markdown." + }, + "headingMenusAndDialogs": "Menús y diálogos", + "showMenuActions": { + "name": "Mostrar acciones de menú", + "desc": "Mostrar acciones relacionadas con íconos en los menús contextuales." + }, + "showQuickSwitcherIcons": { + "name": "Mostrar íconos del selector rápido", + "desc": "Mostrar íconos en los resultados de búsqueda de los selectores rápidos." + }, "headingIconPicker": "Selector de íconos", "showItemName": { "name": "Mostrar el nombre del elemento", diff --git a/i18n/fr.json b/i18n/fr.json index 6138bd5..8df196b 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "Basculer toutes les icônes de fichier", "toggleAllFolderIcons": "Basculer toutes les icônes de dossier", "toggleMinimalFolderIcons": "Basculer les icônes de dossier minimales", + "toggleMarkdownTabIcons": "Basculer les icônes d'onglets Markdown", + "toggleMenuActions": "Bascular les actions du menu", + "toggleQuickSwitcherIcons": "Bascular les icônes de sélecteur rapide", "toggleBiggerSearchResults": "Basculer les résultats de recherche plus grands", "changeIconCurrentFile": "Changer l'icône du fichier actuel" }, @@ -319,6 +322,19 @@ "name": "Icônes de dossier minimales", "desc": "Remplacez les flèches de dossier par vos icônes de dossier." }, + "showMarkdownTabIcons": { + "name": "Afficher les icônes des onglets Markdown", + "desc": "Afficher les icônes des onglets pour les fichiers Markdown." + }, + "headingMenusAndDialogs": "Menus et boîtes de dialogue", + "showMenuActions": { + "name": "Afficher les actions des menus", + "desc": "Afficher les actions liées aux icônes dans les menus contextuels." + }, + "showQuickSwitcherIcons": { + "name": "Afficher les icônes des sélecteurs rapides", + "desc": "Afficher les icônes dans les résultats de recherche des sélecteurs rapides." + }, "headingIconPicker": "Sélecteur d'icônes", "showItemName": { "name": "Afficher le nom de l'élément", diff --git a/i18n/id.json b/i18n/id.json index e2bed69..3253201 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "Beralih semua ikon berkas", "toggleAllFolderIcons": "Beralih semua ikon folder", "toggleMinimalFolderIcons": "Beralih ikon folder minimal", + "toggleMarkdownTabIcons": "Beralih ikon tab Markdown", + "toggleMenuActions": "Beralih tindakan menu", + "toggleQuickSwitcherIcons": "Beralih ikon peralih cepat", "toggleBiggerSearchResults": "Beralih hasil pencarian yang lebih besar", "changeIconCurrentFile": "Ubah ikon file saat ini" }, @@ -319,6 +322,19 @@ "name": "Ikon folder minimal", "desc": "Ganti tanda panah folder dengan ikon folder Anda." }, + "showMarkdownTabIcons": { + "name": "Tampilkan ikon tab Markdown", + "desc": "Tampilkan ikon tab untuk file Markdown." + }, + "headingMenusAndDialogs": "Menu & dialog", + "showMenuActions": { + "name": "Tampilkan tindakan menu", + "desc": "Tampilkan tindakan terkait ikon dalam menu konteks." + }, + "showQuickSwitcherIcons": { + "name": "Tampilkan ikon peralih cepat", + "desc": "Tampilkan ikon dalam hasil pencarian peralih cepat." + }, "headingIconPicker": "Pemilih ikon", "showItemName": { "name": "Tampilkan nama item", diff --git a/i18n/ja.json b/i18n/ja.json index e741868..fd0f3ba 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "すべてのファイルアイコンを切り替える", "toggleAllFolderIcons": "すべてのフォルダアイコンを切り替える", "toggleMinimalFolderIcons": "最小限のフォルダアイコンを切り替える", + "toggleMarkdownTabIcons": "マークダウンタブアイコンを切り替える", + "toggleMenuActions": "メニューアクションの切り替え", + "toggleQuickSwitcherIcons": "クイックスイッチャーアイコンの切り替え", "toggleBiggerSearchResults": "検索結果を大きく切り替えます", "changeIconCurrentFile": "現在のファイルのアイコンを変更する" }, @@ -319,6 +322,19 @@ "name": "最小限のフォルダアイコン", "desc": "フォルダの矢印をフォルダアイコンに置き換えます。" }, + "showMarkdownTabIcons": { + "name": "マークダウンタブアイコンを表示", + "desc": "マークダウンファイルのタブアイコンを表示します。" + }, + "headingMenusAndDialogs": "メニューとダイアログ", + "showMenuActions": { + "name": "メニューアクションを表示", + "desc": "コンテキストメニューにアイコン関連のアクションを表示します。" + }, + "showQuickSwitcherIcons": { + "name": "クイックスイッチャーアイコンを表示", + "desc": "クイックスイッチャーの検索結果にアイコンを表示します。" + }, "headingIconPicker": "アイコン ピッカー", "showItemName": { "name": "アイテム名を表示します", diff --git a/i18n/ru.json b/i18n/ru.json index 6345a6a..6b2c35e 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "Переключить все значки файлов", "toggleAllFolderIcons": "Переключить все значки папок", "toggleMinimalFolderIcons": "Переключить минимальные значки папок", + "toggleMarkdownTabIcons": "Переключить значки вкладок Markdown", + "toggleMenuActions": "Переключить действия меню", + "toggleQuickSwitcherIcons": "Переключить значки Быстрый переход", "toggleBiggerSearchResults": "Переключить большие результаты поиска", "changeIconCurrentFile": "Изменить значок текущего файла" }, @@ -319,6 +322,19 @@ "name": "Минимальные значки папок", "desc": "Заменить стрелки папок на ваши значки папок." }, + "showMarkdownTabIcons": { + "name": "Показать значки вкладок Markdown", + "desc": "Показать значки вкладок для файлов Markdown." + }, + "headingMenusAndDialogs": "Меню и диалоги", + "showMenuActions": { + "name": "Показать действия меню", + "desc": "Показать действия, связанные со значками, в контекстных меню." + }, + "showQuickSwitcherIcons": { + "name": "Показать значки быстрого переключения", + "desc": "Показать значки в результатах поиска быстрых переключателей." + }, "headingIconPicker": "Выбор значков", "showItemName": { "name": "Показывать имя элемента", diff --git a/i18n/uk.json b/i18n/uk.json index 2360eaf..a5748ea 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -279,6 +279,9 @@ "toggleAllFileIcons": "Перемкнути усі значки файлів", "toggleAllFolderIcons": "Перемкнути усі значки тек", "toggleMinimalFolderIcons": "Перемкнути найменші значки тек", + "toggleMarkdownTabIcons": "Перемкнути значків вкладок Markdown", + "toggleMenuActions": "Перемкнути дій меню", + "toggleQuickSwitcherIcons": "Перемкнути значки швидкий перехід", "toggleBiggerSearchResults": "Перемкнути більші результати пошуку", "changeIconCurrentFile": "Змінити значок поточного файлу" }, @@ -319,6 +322,19 @@ "name": "Найменші значки тек", "desc": "Замінити стрілки тек вашими значками." }, + "showMarkdownTabIcons": { + "name": "Показувати значки вкладок Markdown", + "desc": "Показувати значки вкладок для файлів Markdown." + }, + "headingMenusAndDialogs": "Меню та діалогові вікна", + "showMenuActions": { + "name": "Показувати дії меню", + "desc": "Показувати дії, пов’язані зі значками, у контекстних меню." + }, + "showQuickSwitcherIcons": { + "name": "Показувати значки швидкий перехід", + "desc": "Показувати значки в результатах пошуку швидкий перехід." + }, "headingIconPicker": "Вибір значків", "showItemName": { "name": "Показати назву елемента", diff --git a/i18n/zh.json b/i18n/zh.json index 0ae01d4..3cf2deb 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -26,8 +26,8 @@ "changeEmojis": "更换 {#} 个表情符号", "changeMix": "更改图标/表情符号", "changeMixes": "更改 {#} 个图标/表情符号", - "overrulePrefix": "您的规则 ", - "overruleSuffix": " 正在覆盖这个图标。", + "overrulePrefix": "覆盖", + "overruleSuffix": " 覆盖", "overrules": "您的规则书正在覆盖这些图标中的一些。", "search": "搜索", "searchIcons": "搜索图标……", @@ -50,7 +50,7 @@ "cyan": "青色", "blue": "蓝色", "purple": "紫色", - "pink": "粉色", + "pink": "粉红色", "gray": "灰色" } }, @@ -107,7 +107,7 @@ "enterName": "为该规则命名", "enterValue": "输入一个值", "enterRegex": "输入一个正则表达式", - "enterHexCode": "输入一个 #hexcode", + "enterHexCode": "输入一个十六进制代码", "enterNumber": "输入一个数字", "matchConditions": { "name": "匹配条件", @@ -216,8 +216,8 @@ "!yearIs": "年份不在", "yearIsBefore": "年份在之前", "yearIsAfter": "年份在之后", - "iconIs": "ID是", - "!iconIs": "ID不是", + "iconIs": "图标是", + "!iconIs": "图标不是", "nameIs": "名称是", "!nameIs": "名称不是", "nameContains": "名称包含", @@ -278,7 +278,10 @@ }, "toggleAllFileIcons": "切换所有文件图标", "toggleAllFolderIcons": "切换所有文件夹图标", - "toggleMinimalFolderIcons": "切换最小文件夹图标", + "toggleMinimalFolderIcons": "切换极简文件夹图标", + "toggleMarkdownTabIcons": "切换 Markdown 标签图标", + "toggleMenuActions": "切换菜单操作", + "toggleQuickSwitcherIcons": "切换快速切换图标", "toggleBiggerSearchResults": "切换更大的搜索结果", "changeIconCurrentFile": "更改当前文件的图标" }, @@ -319,6 +322,19 @@ "name": "最小文件夹图标", "desc": "用您的文件夹图标替换文件夹箭头。" }, + "showMarkdownTabIcons": { + "name": "显示 Markdown 标签图标", + "desc": "显示 Markdown 文件的标签图标。" + }, + "headingMenusAndDialogs": "菜单和对话框", + "showMenuActions": { + "name": "显示菜单操作", + "desc": "在上下文菜单中显示与图标相关的操作。" + }, + "showQuickSwitcherIcons": { + "name": "显示快速切换器图标", + "desc": "在快速切换器的搜索结果中显示图标。" + }, "headingIconPicker": "图标选择器", "showItemName": { "name": "显示项目名称", diff --git a/manifest.json b/manifest.json index 01e60ff..a662254 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "iconic", "name": "Iconic", - "version": "1.1.1", + "version": "1.1.2", "minAppVersion": "1.6.0", "description": "Customize your icons and their colors directly from the UI, including tabs, files & folders, bookmarks, tags, properties, and ribbon commands.", "author": "Holo", diff --git a/package-lock.json b/package-lock.json index 582dce7..b1fe28d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "iconic", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d177ce1..3374de6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iconic", - "version": "1.1.1", + "version": "1.1.2", "description": "Customize your icons and their colors directly from the UI, including tabs, files & folders, bookmarks, tags, properties, and ribbon commands.", "main": "main.js", "scripts": { diff --git a/src/IconicPlugin.ts b/src/IconicPlugin.ts index e17c522..ae6d007 100644 --- a/src/IconicPlugin.ts +++ b/src/IconicPlugin.ts @@ -3,7 +3,7 @@ import IconicSettingTab from 'src/IconicSettingTab'; import EMOJIS from 'src/Emojis'; import STRINGS from 'src/Strings'; import MenuManager from 'src/managers/MenuManager'; -import RuleManager, { RulePage, RuleTrigger } from 'src/managers/RuleManager'; +import RuleManager, { RuleTrigger } from 'src/managers/RuleManager'; import AppIconManager from 'src/managers/AppIconManager'; import TabIconManager from 'src/managers/TabIconManager'; import FileIconManager from 'src/managers/FileIconManager'; @@ -12,15 +12,17 @@ import TagIconManager from 'src/managers/TagIconManager'; import PropertyIconManager from 'src/managers/PropertyIconManager'; import EditorIconManager from 'src/managers/EditorIconManager'; import RibbonIconManager from 'src/managers/RibbonIconManager'; +import QuickSwitcherIconManager from 'src/managers/QuickSwitcherIconManager'; import IconPicker from 'src/dialogs/IconPicker'; import RulePicker from 'src/dialogs/RulePicker'; export const ICONS = new Map(); export { EMOJIS }; export { STRINGS }; +export type Category = 'app' | 'tab' | 'file' | 'folder' | 'group' | 'search' | 'graph' | 'url' | 'tag' | 'property' | 'ribbon' | 'rule'; export type AppItemId = 'help' | 'settings' | 'pin' | 'sidebarLeft' | 'sidebarRight' | 'minimize' | 'maximize' | 'unmaximize' | 'close'; -const OPENABLE_TYPES = ['markdown', 'canvas', 'audio', 'video', 'pdf']; +export const FILE_TAB_TYPES = ['markdown', 'canvas', 'bases', 'image', 'audio', 'video', 'pdf']; const SYNCABLE_TYPES = ['image', 'audio', 'video', 'pdf', 'unsupported']; const IMAGE_EXTENSIONS = ['bmp', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'avif']; const AUDIO_EXTENSIONS = ['mp3', 'wav', 'm4a', '3gp', 'flac', 'ogg', 'oga', 'opus']; @@ -37,12 +39,11 @@ export interface Icon { export interface Item extends Icon { id: string; name: string; - category: 'app' | 'tab' | 'file' | 'folder' | 'group' | 'search' | 'graph' | 'url' | 'tag' | 'property' | 'ribbon' | 'rule'; + category: Category; iconDefault: string | null; } export type AppItem = Item; export interface TabItem extends Item { - isFile: boolean; isActive: boolean; isRoot: boolean; isStacked: boolean; @@ -53,7 +54,6 @@ export interface FileItem extends Item { items: FileItem[] | null; } export interface BookmarkItem extends Item { - isFileOrFolder: boolean; items: BookmarkItem[] | null; } export type TagItem = Item; @@ -74,6 +74,9 @@ interface IconicSettings { showAllFileIcons: boolean, showAllFolderIcons: boolean, minimalFolderIcons: boolean; + showMarkdownTabIcons: boolean; + showMenuActions: boolean; + showQuickSwitcherIcons: boolean; showItemName: string; biggerSearchResults: string; maxSearchResults: number; @@ -87,7 +90,7 @@ interface IconicSettings { dialogState: { iconMode: boolean; emojiMode: boolean; - rulePage: RulePage; + rulePage: Category; }, appIcons: Record; tabIcons: Record; @@ -129,7 +132,10 @@ const DEFAULT_SETTINGS: IconicSettings = { clickableIcons: 'desktop', showAllFileIcons: false, showAllFolderIcons: false, - minimalFolderIcons: false, + minimalFolderIcons: true, + showMarkdownTabIcons: true, + showMenuActions: true, + showQuickSwitcherIcons: true, showItemName: 'desktop', biggerSearchResults: 'mobile', maxSearchResults: 50, @@ -171,7 +177,8 @@ export default class IconicPlugin extends Plugin { propertyIconManager?: PropertyIconManager; editorIconManager?: EditorIconManager; ribbonIconManager?: RibbonIconManager; - commands: Command[] = []; + quickSwitcherIconManager?: QuickSwitcherIconManager; + dialogCommands: Command[] = []; /** * @override @@ -334,13 +341,10 @@ export default class IconicPlugin extends Plugin { })); this.registerEvent(this.app.vault.on('modify', tAbstractFile => { - const page = tAbstractFile instanceof TFile ? 'file' : 'folder'; - // If a modified file/folder triggers a new ruling, refresh icons - if (this.ruleManager.triggerRulings(page, 'modify')) { - if (page === 'file') this.tabIconManager?.refreshIcons(); - this.fileIconManager?.refreshIcons(); - this.bookmarkIconManager?.refreshIcons(); - } + this.onFileModify(tAbstractFile); + })); + this.registerEvent(this.app.metadataCache.on('changed', tAbstractFile => { + this.onFileModify(tAbstractFile); })); this.registerEvent(this.app.vault.on('delete', (tAbstractFile) => { @@ -377,7 +381,7 @@ export default class IconicPlugin extends Plugin { }); // COMMAND: Toggle bigger icons - this.commands.push(this.addCommand({ + this.dialogCommands.push(this.addCommand({ id: 'toggle-bigger-icons', name: STRINGS.commands.toggleBiggerIcons, callback: () => { @@ -398,7 +402,7 @@ export default class IconicPlugin extends Plugin { })); // COMMAND: Toggle clickable icons - this.commands.push(this.addCommand({ + this.dialogCommands.push(this.addCommand({ id: 'toggle-clickable-icons', name: Platform.isDesktop ? STRINGS.commands.toggleClickableIcons.desktop : STRINGS.commands.toggleClickableIcons.mobile, callback: () => { @@ -420,7 +424,7 @@ export default class IconicPlugin extends Plugin { })); // COMMAND: Toggle all file icons - this.commands.push(this.addCommand({ + this.dialogCommands.push(this.addCommand({ id: 'toggle-all-file-icons', name: STRINGS.commands.toggleAllFileIcons, callback: () => { @@ -432,7 +436,7 @@ export default class IconicPlugin extends Plugin { })); // COMMAND: Toggle all folder icons - this.commands.push(this.addCommand({ + this.dialogCommands.push(this.addCommand({ id: 'toggle-all-folder-icons', name: STRINGS.commands.toggleAllFolderIcons, callback: () => { @@ -445,7 +449,7 @@ export default class IconicPlugin extends Plugin { })); // COMMAND: Toggle minimal folder icons - this.commands.push(this.addCommand({ + this.dialogCommands.push(this.addCommand({ id: 'toggle-minimal.folder-icons', name: STRINGS.commands.toggleMinimalFolderIcons, callback: () => { @@ -457,8 +461,31 @@ export default class IconicPlugin extends Plugin { } })); + // COMMAND: Toggle Markdown tab icons + this.addCommand({ + id: 'toggle-markdown-tab-icons', + name: STRINGS.commands.toggleMarkdownTabIcons, + callback: () => { + this.settings.showMarkdownTabIcons = !this.settings.showMarkdownTabIcons; + this.saveSettings(); + this.refreshBodyClasses(); + } + }); + + // COMMAND: Toggle menu actions + this.addCommand({ + id: 'toggle-menu-actions', + name: STRINGS.commands.toggleMenuActions, + callback: () => { + this.settings.showMenuActions = !this.settings.showMenuActions; + this.saveSettings(); + this.refreshManagers(); + this.menuManager.closeAndFlush(); + } + }); + // COMMAND: Toggle bigger search results - this.commands.push(this.addCommand({ + this.dialogCommands.push(this.addCommand({ id: 'toggle-bigger-search-results', name: STRINGS.commands.toggleBiggerSearchResults, callback: () => { @@ -508,6 +535,19 @@ export default class IconicPlugin extends Plugin { this.refreshBodyClasses(); } + /** + * Refresh icon managers after a file/folder is modified. + */ + private onFileModify(tAbstractFile: TAbstractFile): void { + const page = tAbstractFile instanceof TFile ? 'file' : 'folder'; + // If a modified file/folder triggers a new ruling, refresh icons + if (this.ruleManager.triggerRulings(page, 'modify')) { + if (page === 'file') this.tabIconManager?.refreshIcons(); + this.fileIconManager?.refreshIcons(); + this.bookmarkIconManager?.refreshIcons(); + } + } + /** * Initialize all manager instances. */ @@ -522,6 +562,7 @@ export default class IconicPlugin extends Plugin { try { this.propertyIconManager = new PropertyIconManager(this) } catch (e) { console.error(e) } try { this.editorIconManager = new EditorIconManager(this) } catch (e) { console.error(e) } try { this.ribbonIconManager = new RibbonIconManager(this) } catch (e) { console.error(e) } + try { this.quickSwitcherIconManager = new QuickSwitcherIconManager(this) } catch (e) { console.error(e) } } /** @@ -546,6 +587,7 @@ export default class IconicPlugin extends Plugin { const { body } = activeDocument; body.toggleClass('iconic-bigger-icons', unloading ? false : this.isSettingEnabled('biggerIcons')); body.toggleClass('iconic-clickable-icons', unloading ? false : this.isSettingEnabled('clickableIcons')); + body.toggleClass('iconic-markdown-tab-icons', unloading ? false : this.settings.showMarkdownTabIcons); body.toggleClass('iconic-bigger-search-results', unloading ? false : this.isSettingEnabled('biggerSearchResults')); body.toggleClass('iconic-uncolor-hover', unloading ? false : this.settings.uncolorHover); body.toggleClass('iconic-uncolor-drag', unloading ? false : this.settings.uncolorDrag); @@ -554,7 +596,6 @@ export default class IconicPlugin extends Plugin { // @ts-expect-error (Private API) const theme = this.app.customCss?.theme; body.toggleClass('iconic-theme-btopaz', unloading ? false : theme === 'Blue Topaz'); - body.toggleClass('iconic-theme-border', unloading ? false : theme === 'Border'); body.toggleClass('iconic-theme-cat', unloading ? false : theme === 'Catppuccin'); body.toggleClass('iconic-theme-cglow', unloading ? false : theme === 'Cyber Glow'); body.toggleClass('iconic-theme-discord', unloading ? false : theme === 'Discordian'); @@ -653,7 +694,7 @@ export default class IconicPlugin extends Plugin { this.app.workspace.iterateAllLeaves(leaf => { if (tab) return; const tabType = leaf.view.getViewType(); - if (tabType === tabId || OPENABLE_TYPES.includes(tabType) && leaf.view.getState().file === tabId) { + if (tabType === tabId || FILE_TAB_TYPES.includes(tabType) && leaf.view.getState().file === tabId) { tab = this.defineTabItem(leaf, unloading); } }); @@ -685,7 +726,7 @@ export default class IconicPlugin extends Plugin { // @ts-expect-error (Private API) const isStacked = leaf.parent?.isStacked === true; - if (OPENABLE_TYPES.includes(tabType)) { + if (FILE_TAB_TYPES.includes(tabType)) { const filePath = leaf.view.getState().file; // Used because view.file is undefined on deferred views const fileId = typeof filePath === 'string' ? filePath : ''; const fileIcon = this.settings.fileIcons[fileId] ?? {}; @@ -699,7 +740,6 @@ export default class IconicPlugin extends Plugin { : leaf.view.getIcon(), icon: unloading ? null : fileIcon.icon ?? null, color: unloading ? null : fileIcon.color ?? null, - isFile: true, isActive: isActive, isRoot: isRoot, isStacked: isStacked, @@ -725,7 +765,6 @@ export default class IconicPlugin extends Plugin { iconDefault: iconDefault, icon: unloading ? null : tabIcon.icon ?? null, color: unloading ? null : tabIcon.color ?? null, - isFile: false, isActive: isActive, isRoot: isRoot, isStacked: isStacked, @@ -811,7 +850,7 @@ export default class IconicPlugin extends Plugin { const subpath = subpathStart > -1 ? fileId.substring(subpathStart, fileId.length) : ''; const path = subpathStart > -1 ? fileId.substring(0, subpathStart) : fileId; - const [, tree = '', filename] = path.match(/^(.*\/)?(.*)$/) ?? []; + const [, tree = '', filename] = path.match(/^(.*\/)?(.*)$/s) ?? []; const extensionStart = filename.lastIndexOf('.'); const extension = filename.substring(extensionStart > -1 ? extensionStart + 1 : filename.length) || ''; const basename = filename.substring(0, extensionStart > -1 ? extensionStart : filename.length) || ''; @@ -831,11 +870,15 @@ export default class IconicPlugin extends Plugin { /** * Get bookmark definition. */ - getBookmarkItem(bmarkId: string, isFileOrFolder: boolean, unloading?: boolean): BookmarkItem { + getBookmarkItem(bmarkId: string, bmarkCategory: Category, unloading?: boolean): BookmarkItem { // @ts-expect-error (Private API) const bmarkBases = this.flattenBookmarks(this.app.internalPlugins?.plugins?.bookmarks?.instance?.items ?? []); const bmarkBase = bmarkBases.find(bmarkBase => { - return isFileOrFolder && bmarkBase.path + (bmarkBase.subpath ?? '') === bmarkId || bmarkBase.ctime === bmarkId + switch (bmarkCategory) { + case 'file': // Fallthrough + case 'folder': return bmarkBase.path + (bmarkBase.subpath ?? '') === bmarkId; + default: return bmarkBase.ctime === bmarkId; + } }) ?? {}; return this.defineBookmarkItem(bmarkBase, unloading); } @@ -918,7 +961,6 @@ export default class IconicPlugin extends Plugin { iconDefault: iconDefault, icon: unloading ? null : bmarkIcon?.icon ?? null, color: unloading ? null : bmarkIcon?.color ?? null, - isFileOrFolder: bmarkBase.type === 'file' || bmarkBase.type === 'folder', items: bmarkBase.items?.map((bmark: any) => this.defineBookmarkItem(bmark, unloading)) ?? null, } } @@ -1005,7 +1047,7 @@ export default class IconicPlugin extends Plugin { private definePropertyItem(propBase: any, unloading?: boolean): PropertyItem { const propIcon = this.settings.propertyIcons[propBase.name] ?? {}; let iconDefault; - switch (propBase.type) { + switch (propBase.widget ?? propBase.type) { // Pre-1.9.0 compatible case 'text': iconDefault = 'lucide-text'; break; case 'multitext': iconDefault = 'lucide-list'; break; case 'number': iconDefault = 'lucide-binary'; break; @@ -1023,7 +1065,7 @@ export default class IconicPlugin extends Plugin { iconDefault: iconDefault, icon: unloading ? null : propIcon.icon ?? null, color: unloading ? null : propIcon.color ?? null, - type: propBase.type ?? null, + type: propBase.widget ?? propBase.type ?? null, // Pre-1.9.0 compatible } } @@ -1116,13 +1158,17 @@ export default class IconicPlugin extends Plugin { */ saveBookmarkIcon(bmark: BookmarkItem, icon: string | null, color: string | null): void { const triggers: Set = new Set(); - if (bmark.category === 'file' || bmark.category === 'folder') { - const bmarkBase = this.settings.fileIcons[bmark.id]; - if (icon !== bmarkBase?.icon) triggers.add('icon'); - if (color !== bmarkBase?.color) triggers.add('color'); - this.updateIconSetting(this.settings.fileIcons, bmark.id, icon, color); - } else { - this.updateIconSetting(this.settings.bookmarkIcons, bmark.id, icon, color); + switch (bmark.category) { + case 'file': // Fallthrough + case 'folder': { + const bmarkBase = this.settings.fileIcons[bmark.id]; + if (icon !== bmarkBase?.icon) triggers.add('icon'); + if (color !== bmarkBase?.color) triggers.add('color'); + this.updateIconSetting(this.settings.fileIcons, bmark.id, icon, color); + } + default: { + this.updateIconSetting(this.settings.bookmarkIcons, bmark.id, icon, color); + } } this.saveSettings(); this.ruleManager.triggerRulings('file', ...triggers); @@ -1138,13 +1184,17 @@ export default class IconicPlugin extends Plugin { for (const bmark of bmarks) { if (icon !== undefined) bmark.icon = icon; if (color !== undefined) bmark.color = color; - if (bmark.category === 'file' || bmark.category === 'folder') { - const bmarkBase = this.settings.fileIcons[bmark.id]; - if (icon !== bmarkBase?.icon) triggers.add('icon'); - if (color !== bmarkBase?.color) triggers.add('color'); - this.updateIconSetting(this.settings.fileIcons, bmark.id, bmark.icon, bmark.color); - } else { - this.updateIconSetting(this.settings.bookmarkIcons, bmark.id, bmark.icon, bmark.color); + switch (bmark.category) { + case 'file': // Fallthrough + case 'folder': { + const bmarkBase = this.settings.fileIcons[bmark.id]; + if (icon !== bmarkBase?.icon) triggers.add('icon'); + if (color !== bmarkBase?.color) triggers.add('color'); + this.updateIconSetting(this.settings.fileIcons, bmark.id, bmark.icon, bmark.color); + } + default: { + this.updateIconSetting(this.settings.bookmarkIcons, bmark.id, bmark.icon, bmark.color); + } } } this.saveSettings(); @@ -1336,6 +1386,7 @@ export default class IconicPlugin extends Plugin { * @override */ onunload(): void { + this.menuManager.unload(); this.ruleManager.unload(); this.appIconManager?.unload(); this.tabIconManager?.unload(); @@ -1345,6 +1396,7 @@ export default class IconicPlugin extends Plugin { this.propertyIconManager?.unload(); this.editorIconManager?.unload(); this.ribbonIconManager?.unload(); + this.quickSwitcherIconManager?.unload(); this.refreshBodyClasses(true); } } diff --git a/src/IconicSettingTab.ts b/src/IconicSettingTab.ts index fbc4525..f4eced2 100644 --- a/src/IconicSettingTab.ts +++ b/src/IconicSettingTab.ts @@ -140,6 +140,48 @@ export default class IconicSettingTab extends PluginSettingTab { }) ); + // Show Markdown tab icons + new Setting(this.containerEl) + .setName(STRINGS.settings.showMarkdownTabIcons.name) + .setDesc(STRINGS.settings.showMarkdownTabIcons.desc) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.showMarkdownTabIcons) + .onChange(value => { + this.plugin.settings.showMarkdownTabIcons = value; + this.plugin.saveSettings(); + this.plugin.refreshBodyClasses(); + }) + ); + + // HEADING: Menus & dialogs + new Setting(this.containerEl).setHeading().setName(STRINGS.settings.headingMenusAndDialogs); + + // Show menu actions + new Setting(this.containerEl) + .setName(STRINGS.settings.showMenuActions.name) + .setDesc(STRINGS.settings.showMenuActions.desc) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.showMenuActions) + .onChange(value => { + this.plugin.settings.showMenuActions = value; + this.plugin.saveSettings(); + this.plugin.refreshManagers(); + }) + ); + + // Show quick switcher icons + new Setting(this.containerEl) + .setName(STRINGS.settings.showQuickSwitcherIcons.name) + .setDesc(STRINGS.settings.showQuickSwitcherIcons.desc) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.showQuickSwitcherIcons) + .onChange(value => { + this.plugin.settings.showQuickSwitcherIcons = value; + this.plugin.saveSettings(); + // this.plugin.promptManager?.refreshIcons(); + }) + ); + // HEADING: Icon picker new Setting(this.containerEl).setName(STRINGS.settings.headingIconPicker).setHeading(); @@ -290,7 +332,7 @@ export default class IconicSettingTab extends PluginSettingTab { }) ); - // Colorless ribbon buttonn + // Colorless ribbon button new Setting(this.containerEl) .setName(STRINGS.settings.uncolorQuick.name) .setDesc(STRINGS.settings.uncolorQuick.desc) diff --git a/src/Strings.ts b/src/Strings.ts index 4b147c5..cd5f650 100644 --- a/src/Strings.ts +++ b/src/Strings.ts @@ -279,6 +279,9 @@ export default class Strings { toggleAllFileIcons: 'Toggle all file icons', toggleAllFolderIcons: 'Toggle all folder icons', toggleMinimalFolderIcons: 'Toggle minimal folder icons', + toggleMarkdownTabIcons: 'Toggle Markdown tab icons', + toggleMenuActions: 'Toggle menu actions', + toggleQuickSwitcherIcons: 'Toggle quick switcher icons', toggleBiggerSearchResults: 'Toggle bigger search results', changeIconCurrentFile: 'Change icon of the current file', }; @@ -319,6 +322,19 @@ export default class Strings { name: 'Minimal folder icons', desc: 'Replace folder arrows with your folder icons.', }, + showMarkdownTabIcons: { + name: 'Show Markdown tab icons', + desc: 'Show tab icons for Markdown files.', + }, + headingMenusAndDialogs: 'Menus & dialogs', + showMenuActions: { + name: 'Show menu actions', + desc: 'Show icon-related actions in context menus.', + }, + showQuickSwitcherIcons: { + name: 'Show quick switcher icons', + desc: 'Show icons in search results of quick switchers.', + }, headingIconPicker: 'Icon picker', showItemName: { name: 'Show item name', diff --git a/src/dialogs/IconPicker.ts b/src/dialogs/IconPicker.ts index 7d00106..70118ab 100644 --- a/src/dialogs/IconPicker.ts +++ b/src/dialogs/IconPicker.ts @@ -1,7 +1,7 @@ import { ButtonComponent, ColorComponent, ExtraButtonComponent, Hotkey, Menu, Modal, Platform, Setting, TextComponent, prepareFuzzySearch } from 'obsidian'; -import IconicPlugin, { Item, Icon, ICONS, EMOJIS, STRINGS } from 'src/IconicPlugin'; +import IconicPlugin, { Category, Item, Icon, ICONS, EMOJIS, STRINGS } from 'src/IconicPlugin'; import ColorUtils, { COLORS } from 'src/ColorUtils'; -import { RuleItem, RulePage } from 'src/managers/RuleManager'; +import { RuleItem } from 'src/managers/RuleManager'; import IconManager from 'src/managers/IconManager'; import RuleEditor from 'src/dialogs/RuleEditor'; @@ -36,11 +36,6 @@ class IconPickerManager extends IconManager { super.refreshIcon(item, iconEl, onClick); } - /** - * Not used by {@link IconPicker}. - */ - refreshIcons(): void {} - /** * @override */ @@ -117,8 +112,8 @@ export default class IconPicker extends Modal { this.callback = callback; this.multiCallback = multiCallback; - // Allow hotkeys in icon picker - for (const command of this.plugin.commands) if (command.callback) { + // Allow hotkeys in dialog + for (const command of this.plugin.dialogCommands) if (command.callback) { // @ts-expect-error (Private API) const hotkeys: Hotkey[] = this.app.hotkeyManager?.customKeys?.[command.id] ?? []; for (const hotkey of hotkeys) { @@ -753,22 +748,20 @@ export default class IconPicker extends Modal { */ private updateOverruleReminder(): void { this.overruleEl?.remove(); - let page: RulePage; + let page: Category; let rule: RuleItem | null = null; // Determine which rule to display - if (this.items.length > 1) for (const item of this.items) { - if (item.category === 'file' || item.category === 'folder') { + if (this.items.length > 1) { + for (const item of this.items) { rule = this.plugin.ruleManager.checkRuling(item.category, item.id); page = item.category; if (rule) break; } } else { const item = this.items[0]; - if (item.category === 'file' || item.category === 'folder') { - rule = this.plugin.ruleManager.checkRuling(item.category, item.id); - page = item.category; - } + rule = this.plugin.ruleManager.checkRuling(item.category, item.id); + page = item.category; } if (rule) { diff --git a/src/dialogs/RuleChecker.ts b/src/dialogs/RuleChecker.ts index f7531d6..97a8796 100644 --- a/src/dialogs/RuleChecker.ts +++ b/src/dialogs/RuleChecker.ts @@ -1,23 +1,22 @@ import { ButtonComponent, Modal, Setting } from 'obsidian'; -import IconicPlugin, { FileItem, STRINGS } from 'src/IconicPlugin'; -import { RulePage } from 'src/managers/RuleManager'; +import IconicPlugin, { Category, FileItem, STRINGS } from 'src/IconicPlugin'; /** * Dialog for previewing the items matched by a rule. */ export default class RuleChecker extends Modal { private readonly plugin: IconicPlugin; - private readonly page: RulePage; + private readonly page: Category; private readonly matches: FileItem[]; - private constructor(plugin: IconicPlugin, page: RulePage, matches: FileItem[]) { + private constructor(plugin: IconicPlugin, page: Category, matches: FileItem[]) { super(plugin.app); this.plugin = plugin; this.page = page; this.matches = matches; - // Allow hotkeys in rule checker - for (const command of this.plugin.commands) if (command.callback) { + // Allow hotkeys in dialog + for (const command of this.plugin.dialogCommands) if (command.callback) { // @ts-expect-error (Private API) const hotkeys: Hotkey[] = this.app.hotkeyManager?.customKeys?.[command.id] ?? []; for (const hotkey of hotkeys) { @@ -29,7 +28,7 @@ export default class RuleChecker extends Modal { /** * Open a dialog to preview a list of matches. */ - static open(plugin: IconicPlugin, page: RulePage, matches: FileItem[]): void { + static open(plugin: IconicPlugin, page: Category, matches: FileItem[]): void { new RuleChecker(plugin, page, matches).open(); } diff --git a/src/dialogs/RuleEditor.ts b/src/dialogs/RuleEditor.ts index 0ba4b34..48e2ea0 100644 --- a/src/dialogs/RuleEditor.ts +++ b/src/dialogs/RuleEditor.ts @@ -1,7 +1,7 @@ import { ButtonComponent, DropdownComponent, ExtraButtonComponent, Modal, Platform, Setting, TextComponent } from 'obsidian'; -import IconicPlugin, { Icon, Item, FileItem, STRINGS } from 'src/IconicPlugin'; +import IconicPlugin, { Category, Icon, Item, FileItem, STRINGS } from 'src/IconicPlugin'; import ColorUtils from 'src/ColorUtils'; -import { RulePage, RuleItem, ConditionItem } from 'src/managers/RuleManager'; +import { RuleItem, ConditionItem } from 'src/managers/RuleManager'; import IconManager from 'src/managers/IconManager'; import RuleChecker from 'src/dialogs/RuleChecker'; import IconPicker from 'src/dialogs/IconPicker'; @@ -464,11 +464,6 @@ class RuleEditorManager extends IconManager { super.refreshIcon(item, iconEl, onClick); } - /** - * Not used by {@link RuleEditor}. - */ - refreshIcons(): void { } - /** * @override */ @@ -506,7 +501,7 @@ export default class RuleEditor extends Modal { private readonly iconManager: RuleEditorManager; // Rule - private readonly page: RulePage; + private readonly page: Category; private readonly rule: RuleItem; private readonly callback: RuleEditorCallback | null; private matches: FileItem[] = []; @@ -517,7 +512,7 @@ export default class RuleEditor extends Modal { private addCondSetting: Setting; private matchesButton: ButtonComponent; - private constructor(plugin: IconicPlugin, page: RulePage, rule: RuleItem, callback: RuleEditorCallback | null) { + private constructor(plugin: IconicPlugin, page: Category, rule: RuleItem, callback: RuleEditorCallback | null) { super(plugin.app); this.plugin = plugin; this.iconManager = new RuleEditorManager(plugin); @@ -525,8 +520,8 @@ export default class RuleEditor extends Modal { this.rule = window.structuredClone(rule); this.callback = callback; - // Allow hotkeys in rule editor - for (const command of this.plugin.commands) if (command.callback) { + // Allow hotkeys in dialog + for (const command of this.plugin.dialogCommands) if (command.callback) { // @ts-expect-error (Private API) const hotkeys: Hotkey[] = this.app.hotkeyManager?.customKeys?.[command.id] ?? []; for (const hotkey of hotkeys) { @@ -538,7 +533,7 @@ export default class RuleEditor extends Modal { /** * Open a dialog to edit a single rule. */ - static open(plugin: IconicPlugin, page: RulePage, rule: RuleItem, callback: RuleEditorCallback): void { + static open(plugin: IconicPlugin, page: Category, rule: RuleItem, callback: RuleEditorCallback): void { new RuleEditor(plugin, page, rule, callback).open(); } @@ -551,6 +546,7 @@ export default class RuleEditor extends Modal { switch (this.page) { case 'file': this.setTitle(STRINGS.ruleEditor.fileRule); break; case 'folder': this.setTitle(STRINGS.ruleEditor.folderRule); break; + default: this.setTitle(STRINGS.categories.rule); break; } const ruleSetting = new Setting(this.contentEl); @@ -816,7 +812,7 @@ export default class RuleEditor extends Modal { */ class ConditionSetting extends Setting { private readonly plugin: IconicPlugin; - private readonly page: RulePage; + private readonly page: Category; private readonly condition: ConditionItem; // Components @@ -838,7 +834,7 @@ class ConditionSetting extends Setting { containerEl: HTMLElement, plugin: IconicPlugin, iconManager: RuleEditorManager, - page: RulePage, + page: Category, condition: ConditionItem, condEls: HTMLElement[], onChange: () => void, @@ -1020,6 +1016,7 @@ class ConditionSetting extends Setting { // Update sources let srcOptions: DropdownOptions; switch (this.page) { + default: srcOptions = FILE_SOURCES; break; case 'file': srcOptions = FILE_SOURCES; break; case 'folder': srcOptions = FOLDER_SOURCES; break; } diff --git a/src/dialogs/RulePicker.ts b/src/dialogs/RulePicker.ts index 3c1d6ec..ea6b310 100644 --- a/src/dialogs/RulePicker.ts +++ b/src/dialogs/RulePicker.ts @@ -1,7 +1,7 @@ -import { ExtraButtonComponent, Modal, Setting, ToggleComponent } from 'obsidian'; -import IconicPlugin, { Icon, Item, STRINGS } from 'src/IconicPlugin'; +import { ExtraButtonComponent, Menu, Modal, Setting, ToggleComponent } from 'obsidian'; +import IconicPlugin, { Category, Icon, Item, STRINGS } from 'src/IconicPlugin'; import ColorUtils from 'src/ColorUtils'; -import { RulePage, RuleItem } from 'src/managers/RuleManager'; +import { RuleItem } from 'src/managers/RuleManager'; import IconManager from 'src/managers/IconManager'; import RuleEditor from 'src/dialogs/RuleEditor'; import IconPicker from 'src/dialogs/IconPicker'; @@ -21,11 +21,6 @@ class RulePickerManager extends IconManager { super.refreshIcon(item, iconEl, onClick); } - /** - * Not used by {@link RulePicker}. - */ - refreshIcons(): void {} - /** * @override */ @@ -68,8 +63,8 @@ export default class RulePicker extends Modal { this.plugin = plugin; this.iconManager = new RulePickerManager(plugin); - // Allow hotkeys in rule picker - for (const command of this.plugin.commands) if (command.callback) { + // Allow hotkeys in dialog + for (const command of this.plugin.dialogCommands) if (command.callback) { // @ts-expect-error (Private API) const hotkeys: Hotkey[] = this.app.hotkeyManager?.customKeys?.[command.id] ?? []; for (const hotkey of hotkeys) { @@ -100,11 +95,8 @@ export default class RulePicker extends Modal { file: STRINGS.rulePicker.fileRules, folder: STRINGS.rulePicker.folderRules, }) - .onChange(value => { - switch (value) { - case 'file': dialogState.rulePage = value; break; - case 'folder': dialogState.rulePage = value; break; - } + .onChange((value: Category) => { + dialogState.rulePage = value; this.refreshRules(); }) .setValue(dialogState.rulePage); @@ -180,6 +172,23 @@ export default class RulePicker extends Modal { ); this.ruleEls.push(ruleSetting.settingEl); + // Context menu for deleting a rule + this.iconManager.setEventListener(ruleSetting.settingEl, 'contextmenu', event => { + const menu = new Menu(); + menu.addItem(menuItem => { menuItem + .setIcon('lucide-trash-2') + .setTitle(STRINGS.rulePicker.removeRule) + .onClick(() => { + ruleSetting.settingEl.remove(); + this.ruleEls.remove(ruleSetting.settingEl); + const rulePage = this.plugin.settings.dialogState.rulePage; + const isRulingChanged = this.plugin.ruleManager.deleteRule(rulePage, rule.id); + if (isRulingChanged) this.refreshPageManagers(); + }); + }); + menu.showAtMouseEvent(event); + }); + // Move the Add Rule button below this rule ruleSetting.settingEl.insertAdjacentElement('afterend', this.addRuleSetting.settingEl); } @@ -214,7 +223,7 @@ export default class RulePicker extends Modal { */ class RuleSetting extends Setting { private readonly plugin: IconicPlugin; - private readonly page: RulePage; + private readonly page: Category; private readonly rule: RuleItem; // Components @@ -228,7 +237,7 @@ class RuleSetting extends Setting { containerEl: HTMLElement, plugin: IconicPlugin, iconManager: RulePickerManager, - page: RulePage, + page: Category, rule: RuleItem, ruleEls: HTMLElement[], onRulingChange: () => void, @@ -305,6 +314,7 @@ class RuleSetting extends Setting { isRulingChanged = plugin.ruleManager.saveRule(page, newRule); } else { this.settingEl.remove(); + this.ruleEls.remove(this.settingEl); isRulingChanged = plugin.ruleManager.deleteRule(page, rule.id); } if (isRulingChanged) onRulingChange(); diff --git a/src/managers/AppIconManager.ts b/src/managers/AppIconManager.ts index fda7f13..b4a0885 100644 --- a/src/managers/AppIconManager.ts +++ b/src/managers/AppIconManager.ts @@ -47,7 +47,13 @@ export default class AppIconManager extends IconManager { if (this.helpEl) { const helpItem = this.plugin.getAppItem('help', unloading); this.refreshIcon(helpItem, this.helpEl); - this.setEventListener(this.helpEl, 'contextmenu', event => this.onContextMenu('help', event)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(this.helpEl, 'contextmenu', event => { + this.onContextMenu('help', event); + }); + } else { + this.stopEventListener(this.helpEl, 'contextmenu'); + } } } @@ -61,7 +67,13 @@ export default class AppIconManager extends IconManager { if (this.settingsEl) { const settingsItem = this.plugin.getAppItem('settings', unloading); this.refreshIcon(settingsItem, this.settingsEl); - this.setEventListener(this.settingsEl, 'contextmenu', event => this.onContextMenu('settings', event)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(this.settingsEl, 'contextmenu', event => { + this.onContextMenu('settings', event); + }); + } else { + this.stopEventListener(this.settingsEl, 'contextmenu'); + } } // Sidebar pins @@ -73,7 +85,13 @@ export default class AppIconManager extends IconManager { for (const pinEl of this.pinEls) { const pinItem = this.plugin.getAppItem('pin', unloading); this.refreshIcon(pinItem, pinEl); - this.setEventListener(pinEl, 'contextmenu', event => this.onContextMenu('pin', event)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(pinEl, 'contextmenu', event => { + this.onContextMenu('pin', event); + }); + } else { + this.stopEventListener(pinEl, 'contextmenu'); + } } } @@ -87,7 +105,13 @@ export default class AppIconManager extends IconManager { if (iconEl) { const item = this.plugin.getAppItem('sidebarLeft', unloading); this.refreshIcon(item, iconEl); - this.setEventListener(sidebarLeftEl, 'contextmenu', event => this.onContextMenu('sidebarLeft', event)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(sidebarLeftEl, 'contextmenu', event => { + this.onContextMenu('sidebarLeft', event); + }); + } else { + this.stopEventListener(sidebarLeftEl, 'contextmenu'); + } } } @@ -101,7 +125,13 @@ export default class AppIconManager extends IconManager { if (iconEl) { const item = this.plugin.getAppItem('sidebarRight', unloading); this.refreshIcon(item, iconEl); - this.setEventListener(this.sidebarRightEl, 'contextmenu', event => this.onContextMenu('sidebarRight', event)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(this.sidebarRightEl, 'contextmenu', event => { + this.onContextMenu('sidebarRight', event); + }); + } else { + this.stopEventListener(this.sidebarRightEl, 'contextmenu'); + } } } @@ -123,7 +153,13 @@ export default class AppIconManager extends IconManager { const rectEl = this.minimizeEl.createSvg('svg', SVG_INFO).createSvg('rect', MINIMIZE_RECT); if (item.color) rectEl.style.fill = ColorUtils.toRgb(item.color); } - this.setEventListener(this.minimizeEl, 'contextmenu', event => this.onContextMenu('minimize', event)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(this.minimizeEl, 'contextmenu', event => { + this.onContextMenu('minimize', event); + }); + } else { + this.stopEventListener(this.minimizeEl, 'contextmenu'); + } } // Maximize / Restore down @@ -149,7 +185,13 @@ export default class AppIconManager extends IconManager { pathEl2.style.fill = ColorUtils.toRgb(item.color); } } - this.setEventListener(this.closeEl, 'contextmenu', event => this.onContextMenu('close', event)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(this.closeEl, 'contextmenu', event => { + this.onContextMenu('close', event); + }); + } else { + this.stopEventListener(this.closeEl, 'contextmenu'); + } } } @@ -186,9 +228,13 @@ export default class AppIconManager extends IconManager { if (item.color) rectEl.style.stroke = ColorUtils.toRgb(item.color); } } - this.setEventListener(this.maximizeEl, 'contextmenu', event => { - this.onContextMenu(isMaximized ? 'unmaximize' : 'maximize', event); - }); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(this.maximizeEl, 'contextmenu', event => { + this.onContextMenu(isMaximized ? 'unmaximize' : 'maximize', event); + }); + } else { + this.stopEventListener(this.maximizeEl, 'contextmenu'); + } this.setMutationsObserver(this.maximizeEl, { childList: true }, () => { this.refreshMaximizeIcon(); }); @@ -232,4 +278,12 @@ export default class AppIconManager extends IconManager { if (menu instanceof Menu) menu.showAtMouseEvent(event); } + + /** + * @override + */ + unload(): void { + this.refreshIcons(true); + super.unload(); + } } diff --git a/src/managers/BookmarkIconManager.ts b/src/managers/BookmarkIconManager.ts index ead8261..8807121 100644 --- a/src/managers/BookmarkIconManager.ts +++ b/src/managers/BookmarkIconManager.ts @@ -1,5 +1,5 @@ import { WorkspaceLeaf } from 'obsidian'; -import IconicPlugin, { BookmarkItem, STRINGS } from 'src/IconicPlugin'; +import IconicPlugin, { Category, BookmarkItem, STRINGS } from 'src/IconicPlugin'; import { RuleItem } from 'src/managers/RuleManager'; import IconManager from 'src/managers/IconManager'; import RuleEditor from 'src/dialogs/RuleEditor'; @@ -85,7 +85,7 @@ export default class BookmarkIconManager extends IconManager { itemEl.addClass('iconic-item'); const bmark = bmarks[itemEls.indexOf(itemEl)]; - if (!bmark) continue; + if (!bmark || bmark.category === 'url') continue; // Check for an icon ruling let rule: RuleItem | BookmarkItem = bmark; @@ -154,14 +154,19 @@ export default class BookmarkIconManager extends IconManager { if (selfEl) { this.selectionLookup.set(selfEl, bmark); this.setEventListener(selfEl, 'touchstart', () => this.isTouchActive = true); - this.setEventListener(selfEl, 'contextmenu', () => { - // Mobile fires this event twice on bookmarks, so skip the mid-touch event - if (this.isTouchActive) { - this.isTouchActive = false; - } else { - this.onContextMenu(bmark.id, bmark.isFileOrFolder); - } - }, { capture: true }); + + if (this.plugin.settings.showMenuActions) { + this.setEventListener(selfEl, 'contextmenu', () => { + // Mobile fires this event twice on bookmarks, so skip the mid-touch event + if (this.isTouchActive) { + this.isTouchActive = false; + } else { + this.onContextMenu(bmark.id, bmark.category); + } + }, { capture: true }); + } else { + this.stopEventListener(selfEl, 'contextmenu'); + } } // Update ghost icon when dragging @@ -184,14 +189,14 @@ export default class BookmarkIconManager extends IconManager { /** * When user context-clicks a bookmark, add custom items to the menu. */ - private onContextMenu(clickedBmarkId: string, isFileOrFolder: boolean): void { + private onContextMenu(clickedId: string, clickedCategory: Category): void { this.plugin.menuManager.closeAndFlush(); - const clickedBmark: BookmarkItem = this.plugin.getBookmarkItem(clickedBmarkId, isFileOrFolder); + const clickedBmark: BookmarkItem = this.plugin.getBookmarkItem(clickedId, clickedCategory); const selectedBmarks: BookmarkItem[] = []; for (const [selectableEl, bmark] of this.selectionLookup) { if (selectableEl.hasClass('is-selected')) { - selectedBmarks.push(this.plugin.getBookmarkItem(bmark.id, bmark.isFileOrFolder)); + selectedBmarks.push(this.plugin.getBookmarkItem(bmark.id, bmark.category)); } } @@ -281,4 +286,12 @@ export default class BookmarkIconManager extends IconManager { } } } + + /** + * @override + */ + unload(): void { + this.refreshIcons(true); + super.unload(); + } } diff --git a/src/managers/EditorIconManager.ts b/src/managers/EditorIconManager.ts index 2d9464f..a066ad8 100644 --- a/src/managers/EditorIconManager.ts +++ b/src/managers/EditorIconManager.ts @@ -53,9 +53,13 @@ export default class EditorIconManager extends IconManager { } else { this.refreshIcon(tag, iconEl); } - this.setEventListener(tagEl, 'contextmenu', event => { - this.onTagNewContextMenu(tag.id, event); - }); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(tagEl, 'contextmenu', event => { + this.onTagNewContextMenu(tag.id, event); + }); + } else { + this.stopEventListener(tagEl, 'contextmenu'); + } } }); @@ -66,6 +70,16 @@ export default class EditorIconManager extends IconManager { this.refreshViewIcons(leaf.view); } } + + // Refresh icons in the active leaf + this.plugin.registerEvent(this.app.workspace.on('active-leaf-change', () => { + this.refreshIcons(); + })); + + // If we add a new property to a file, refresh property icons + this.plugin.registerEvent(this.app.vault.on('modify', () => { + this.refreshIcons(); + })); } /** @@ -115,6 +129,7 @@ export default class EditorIconManager extends IconManager { } } }); + this.setEventListener(propsEl, 'click', event => { const pointEls = activeDocument.elementsFromPoint(event.x, event.y); const iconEl = pointEls.find(el => el.hasClass('metadata-property-icon')); @@ -135,17 +150,22 @@ export default class EditorIconManager extends IconManager { } } }, { capture: true }); - this.setEventListener(propsEl, 'contextmenu', event => { - const pointEls = activeDocument.elementsFromPoint(event.x, event.y); - const iconEl = pointEls.find(el => el.hasClass('metadata-property-icon')); - const propEl = pointEls.find(el => el.hasClass('metadata-property')); - if (iconEl && propEl instanceof HTMLElement) { - const prop = propEl.dataset.propertyKey - ? this.plugin.getPropertyItem(propEl.dataset.propertyKey) - : null; - if (prop) this.onPropertyContextMenu(prop.id); - } - }, { capture: true }); + + if (this.plugin.settings.showMenuActions) { + this.setEventListener(propsEl, 'contextmenu', event => { + const pointEls = activeDocument.elementsFromPoint(event.x, event.y); + const iconEl = pointEls.find(el => el.hasClass('metadata-property-icon')); + const propEl = pointEls.find(el => el.hasClass('metadata-property')); + if (iconEl && propEl instanceof HTMLElement) { + const prop = propEl.dataset.propertyKey + ? this.plugin.getPropertyItem(propEl.dataset.propertyKey) + : null; + if (prop) this.onPropertyContextMenu(prop.id); + } + }, { capture: true }); + } else { + this.stopEventListener(propsEl, 'contextmenu'); + } } /** @@ -237,7 +257,11 @@ export default class EditorIconManager extends IconManager { iconEl?.remove(); } EditorIconManager.setTagColor(tag, propTagEl); - this.setEventListener(propTagEl, 'contextmenu', () => this.onTagContextMenu(tag.id)); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(propTagEl, 'contextmenu', () => this.onTagContextMenu(tag.id)); + } else { + this.stopEventListener(propTagEl, 'contextmenu'); + } } // Hashtags (editing mode) @@ -259,17 +283,25 @@ export default class EditorIconManager extends IconManager { const tagBeginEl = tagEndEl.previousElementSibling; if (tagBeginEl instanceof HTMLElement && tagBeginEl.hasClass('cm-hashtag-begin')) { EditorIconManager.setTagColor(tag, tagBeginEl); - this.setEventListener(tagBeginEl, 'contextmenu', event => { + if (this.plugin.settings.showMenuActions) { + this.setEventListener(tagBeginEl, 'contextmenu', event => { + if (Platform.isDesktop) this.onTagContextMenu(tag.id, true); + if (Platform.isMobile) this.onTagNewContextMenu(tag.id, event); + }); + } else { + this.stopEventListener(tagBeginEl, 'contextmenu'); + } + } + // Decorate 2nd half of tag + EditorIconManager.setTagColor(tag, tagEndEl); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(tagEndEl, 'contextmenu', event => { if (Platform.isDesktop) this.onTagContextMenu(tag.id, true); if (Platform.isMobile) this.onTagNewContextMenu(tag.id, event); }); + } else { + this.stopEventListener(tagEndEl, 'contextmenu'); } - // Decorate 2nd half of tag - EditorIconManager.setTagColor(tag, tagEndEl); - this.setEventListener(tagEndEl, 'contextmenu', event => { - if (Platform.isDesktop) this.onTagContextMenu(tag.id, true); - if (Platform.isMobile) this.onTagNewContextMenu(tag.id, event); - }); } } } @@ -417,4 +449,12 @@ export default class EditorIconManager extends IconManager { if (tagEl.hasClass('multi-select-pill')) tagEl.style.removeProperty(`--pill-color-remove`); } } + + /** + * @override + */ + unload(): void { + this.refreshIcons(true); + super.unload(); + } } diff --git a/src/managers/FileIconManager.ts b/src/managers/FileIconManager.ts index 52f3455..de7cc71 100644 --- a/src/managers/FileIconManager.ts +++ b/src/managers/FileIconManager.ts @@ -17,10 +17,14 @@ export default class FileIconManager extends IconManager { constructor(plugin: IconicPlugin) { super(plugin); this.plugin.registerEvent(this.app.workspace.on('file-menu', (menu, tFile) => { - this.onContextMenu(tFile.path); + if (this.plugin.settings.showMenuActions) { + this.onContextMenu(tFile.path); + } })); this.plugin.registerEvent(this.app.workspace.on('files-menu', (menu, tFiles) => { - this.onContextMenu(...tFiles.map(tFile => tFile.path)); + if (this.plugin.settings.showMenuActions) { + this.onContextMenu(...tFiles.map(tFile => tFile.path)); + } })); this.plugin.registerEvent(this.app.workspace.on('layout-change', () => { if (activeDocument.contains(this.containerEl)) return; @@ -89,53 +93,74 @@ export default class FileIconManager extends IconManager { } // Set up mutation observer with performance optimizations: - // 1. Only refresh on expand (not collapse) to reduce unnecessary updates + // 1. Only refresh children on expand (not collapse) to reduce unnecessary updates // 2. Use debouncing to prevent multiple rapid refreshes this.setMutationsObserver(itemEl, { subtree: true, attributeFilter: ['class', 'data-path'], attributeOldValue: true, }, mutations => { - if (mutations.some(mutation => { - // Always refresh on data-path changes - if (mutation.attributeName === 'data-path') return true; + let shouldRefreshChildren = false; + let shouldRefreshSelf = false; + + for (const mutation of mutations) { + if (mutation.attributeName === 'data-path') { + shouldRefreshSelf = true; + break; + } - // Refresh when folder is expanded + // Refresh on folder collapse/expand if (mutation.attributeName === 'class' && mutation.target instanceof HTMLElement) { const wasCollapsed = mutation.oldValue?.includes('is-collapsed'); const isCollapsed = mutation.target.hasClass('is-collapsed'); - return wasCollapsed && !isCollapsed; // Only trigger on expand + + // Only refresh children if expanding, not collapsing + if (wasCollapsed && !isCollapsed) { + shouldRefreshChildren = true; + shouldRefreshSelf = true; + } else if (!wasCollapsed && isCollapsed) { + shouldRefreshSelf = true; + } } - return false; - })) { + } + + if (shouldRefreshSelf) { + this.refreshChildIcons([file], [itemEl]); + } + if (shouldRefreshChildren) { const childItemEls = itemEl.findAll(':scope > .tree-item-children > .tree-item'); if (file.items && childItemEls) { - // Use debounced refresh to prevent multiple rapid refreshes - this.debouncedRefresh([file, ...file.items], [itemEl, ...childItemEls]); + this.debouncedRefresh(file.items, childItemEls); } } }); } + // Ensure icon element positioned before filename let iconEl = selfEl.find(':scope > .tree-item-icon') ?? selfEl.createDiv({ cls: 'tree-item-icon' }); + const innerEl = selfEl.find('.tree-item-inner'); + if (iconEl !== innerEl?.previousElementSibling) { + innerEl?.insertAdjacentElement('beforebegin', iconEl); + } if (file.items) { // Toggle default icon based on expand/collapse state if (file.iconDefault) file.iconDefault = iconEl.hasClass('is-collapsed') ? 'lucide-folder-closed' : 'lucide-folder-open'; - let folderIconEl = selfEl.find(':scope > .iconic-sidekick:not(.tree-item-icon)'); - if (this.plugin.settings.minimalFolderIcons || !this.plugin.settings.showAllFolderIcons && !rule.icon && !rule.iconDefault) { - folderIconEl?.remove(); - } else { - const arrowColor = rule.icon || rule.iconDefault ? null : rule.color; - this.refreshIcon({ icon: null, color: arrowColor }, iconEl); - folderIconEl = folderIconEl ?? selfEl.createDiv({ cls: 'iconic-sidekick' }); - if (iconEl.nextElementSibling !== folderIconEl) { - iconEl.insertAdjacentElement('afterend', folderIconEl); - } - iconEl = folderIconEl; + } + + let folderIconEl = selfEl.find(':scope > .iconic-sidekick:not(.tree-item-icon)'); + if (this.plugin.settings.minimalFolderIcons || !this.plugin.settings.showAllFolderIcons && !rule.icon && !rule.iconDefault) { + folderIconEl?.remove(); + } else { + const arrowColor = rule.icon || rule.iconDefault ? null : rule.color; + this.refreshIcon({ icon: null, color: arrowColor }, iconEl); + folderIconEl = folderIconEl ?? selfEl.createDiv({ cls: 'iconic-sidekick' }); + if (iconEl.nextElementSibling !== folderIconEl) { + iconEl.insertAdjacentElement('afterend', folderIconEl); } + iconEl = folderIconEl; } if (iconEl.hasClass('collapse-icon') && !rule.icon && !rule.iconDefault) { @@ -278,6 +303,7 @@ export default class FileIconManager extends IconManager { */ unload(): void { window.clearTimeout(this.refreshTimerId); + this.refreshIcons(true); super.unload(); } } diff --git a/src/managers/IconManager.ts b/src/managers/IconManager.ts index 2aab03e..f2479e7 100644 --- a/src/managers/IconManager.ts +++ b/src/managers/IconManager.ts @@ -65,12 +65,6 @@ export default abstract class IconManager { } } - /** - * Refresh all icon elements controlled by this {@link IconManager}. - * @param unloading Revert to default icons if true - */ - abstract refreshIcons(unloading?: boolean): void; - /** * Set an inline color filter on an element. */ @@ -171,7 +165,6 @@ export default abstract class IconManager { * Revert all DOM changes when plugin unloads. */ unload(): void { - this.refreshIcons(true); this.stopEventListeners(); this.stopMutationObservers(); } diff --git a/src/managers/MenuManager.ts b/src/managers/MenuManager.ts index 376b71a..b7dee60 100644 --- a/src/managers/MenuManager.ts +++ b/src/managers/MenuManager.ts @@ -6,10 +6,15 @@ import { Menu, MenuItem } from 'obsidian'; export default class MenuManager { private menu: Menu | null; private queuedActions: (() => void)[] = []; + private showAtPositionOriginal: typeof Menu.prototype.showAtPosition; + private showAtPositionProxy: typeof Menu.prototype.showAtPosition; constructor() { const manager = this; - Menu.prototype.showAtPosition = new Proxy(Menu.prototype.showAtPosition, { + this.showAtPositionOriginal = Menu.prototype.showAtPosition; + + // Catch menus as they open + this.showAtPositionProxy = new Proxy(Menu.prototype.showAtPosition, { apply(showAtPosition, menu, args) { manager.menu = menu; if (manager.queuedActions.length > 0) { @@ -18,6 +23,9 @@ export default class MenuManager { return showAtPosition.call(menu, ...args); } }); + + // Replace original method + Menu.prototype.showAtPosition = this.showAtPositionProxy; } /** @@ -114,4 +122,13 @@ export default class MenuManager { this.menu = null; this.flush(); } + + /** + * Revert proxy to original method if possible. + */ + unload(): void { + if (Menu.prototype.showAtPosition === this.showAtPositionProxy) { + Menu.prototype.showAtPosition = this.showAtPositionOriginal; + } + } } diff --git a/src/managers/PropertyIconManager.ts b/src/managers/PropertyIconManager.ts index 135e26f..b193b5e 100644 --- a/src/managers/PropertyIconManager.ts +++ b/src/managers/PropertyIconManager.ts @@ -74,7 +74,11 @@ export default class PropertyIconManager extends IconManager { this.refreshIcon(prop, iconEl); } - this.setEventListener(itemEl, 'contextmenu', () => this.onContextMenu(prop.id), { capture: true }); + if (this.plugin.settings.showMenuActions) { + this.setEventListener(itemEl, 'contextmenu', () => this.onContextMenu(prop.id), { capture: true }); + } else { + this.stopEventListener(itemEl, 'contextmenu'); + } } this.setMutationsObserver(this.containerEl, { @@ -157,4 +161,12 @@ export default class PropertyIconManager extends IconManager { ); } } + + /** + * @override + */ + unload(): void { + this.refreshIcons(true); + super.unload(); + } } diff --git a/src/managers/QuickSwitcherIconManager.ts b/src/managers/QuickSwitcherIconManager.ts new file mode 100644 index 0000000..5e42ed0 --- /dev/null +++ b/src/managers/QuickSwitcherIconManager.ts @@ -0,0 +1,248 @@ +import { Plugin, SuggestModal, TFile, WorkspaceLeaf } from 'obsidian'; +import IconicPlugin, { FILE_TAB_TYPES } from 'src/IconicPlugin'; +import IconManager from 'src/managers/IconManager'; + +type PluginModal = SuggestModal & { plugin: Plugin }; + +/** + * Allow type-safe access to a modal.plugin property. + */ +function isPluginModal(modal: SuggestModal): modal is PluginModal { + return (modal as SuggestModal & { plugin: unknown }).plugin instanceof Plugin; +} + +const QUICK_SWITCHER = 'qs'; +const QUICK_SWITCHER_PP = 'qs++'; +const ANOTHER_QUICK_SWITCHER = 'aqs'; + +/** + * Intercepts quick switchers to add custom icons. + */ +export default class QuickSwitcherIconManager extends IconManager { + private onOpenOriginal: typeof SuggestModal.prototype.onOpen; + private onOpenProxy: typeof SuggestModal.prototype.onOpen; + private setInstructionsOriginal: typeof SuggestModal.prototype.setInstructions; + private setInstructionsProxy: typeof SuggestModal.prototype.setInstructions; + + constructor(plugin: IconicPlugin) { + super(plugin); + const manager = this; + this.onOpenOriginal = SuggestModal.prototype.onOpen; + this.setInstructionsOriginal = SuggestModal.prototype.setInstructions; + + // Catch Quick Switcher and Quick Switcher++ modals + this.onOpenProxy = new Proxy(SuggestModal.prototype.onOpen, { + apply(onOpen, modal, args) { + if (manager.isDisabled()) { + return onOpen.call(modal, ...args); + } + + const modalType = manager.getModalType(modal); + if (!modalType) { + return onOpen.call(modal, ...args); + } + + // Proxy renderSuggestion() for each instance + modal.renderSuggestion = new Proxy(modal.renderSuggestion, { + apply(renderSuggestion, modal, args) { + const [value, el] = args; + switch (modalType) { + case QUICK_SWITCHER: { + modal.modalEl.addClass('iconic-quick-switcher'); + manager.refreshSuggestionIcon(value, el); + break; + } + case QUICK_SWITCHER_PP: { + modal.modalEl.addClass('iconic-quick-switcher'); + manager.refreshSuggestionIconQSPP(value, el); + break; + } + } + return renderSuggestion.call(modal, ...args);; + } + }); + + return onOpen.call(modal, ...args); + } + }); + + // Catch Another Quick Switcher modals, which never call super.onOpen() + this.setInstructionsProxy = new Proxy(SuggestModal.prototype.setInstructions, { + apply(setInstructions, modal, args) { + if (manager.isDisabled()) { + return setInstructions.call(modal, ...args); + } + + const modalType = manager.getModalType(modal); + if (modalType !== ANOTHER_QUICK_SWITCHER) { + return setInstructions.call(modal, ...args); + } + + // Proxy renderSuggestion() for every instance + modal.renderSuggestion = new Proxy(modal.renderSuggestion, { + apply(renderSuggestion, modal, args) { + if (manager.isDisabled()) { + return renderSuggestion.call(modal, ...args); + } + const [value, el] = args; + // Call the base method first so custom elements are available + const returnValue = renderSuggestion.call(modal, ...args); + modal.modalEl.addClass('iconic-another-quick-switcher'); + // Refresh s + manager.refreshSuggestionIconAQS(value, el); + return returnValue; + } + }); + + return setInstructions.call(modal, ...args); + } + }); + + // Replace original methods + SuggestModal.prototype.onOpen = this.onOpenProxy; + SuggestModal.prototype.setInstructions = this.setInstructionsProxy; + } + + /** + * Determine which type of modal this is. + */ + private getModalType(modal: SuggestModal): string | null { + // Check for Another Quick Switcher + if (modal.modalEl.hasClass('another-quick-switcher__modal-prompt')) { + return ANOTHER_QUICK_SWITCHER; + } + + // Check for Quick Switcher++ + if (isPluginModal(modal) && modal.plugin.manifest.id === 'darlal-switcher-plus') { + return QUICK_SWITCHER_PP; + } + + // Check for Quick Switcher + if ('shouldShowMarkdown' in modal) { + return QUICK_SWITCHER; + } + + return null; + } + + /** + * Refresh icon of a Quick Switcher suggestion. + */ + private refreshSuggestionIcon(value: any, el: HTMLElement): void { + switch (value?.type) { + case 'file': { + if (value.file instanceof TFile) { + const file = this.plugin.getFileItem(value.file.path); + const rule = this.plugin.ruleManager.checkRuling('file', file.id) ?? file; + if (rule.icon || rule.color) { + const iconEl = el.find('.iconic-icon') ?? el.createDiv(); + this.refreshIcon(rule, iconEl); + } + } + break; + } + case 'bookmark': { + const bmarkBase = value.item; + if (bmarkBase.type === 'file') { + const file = this.plugin.getFileItem(bmarkBase.path); + const rule = this.plugin.ruleManager.checkRuling('file', file.id) ?? file; + if (rule.icon || rule.color) { + const iconEl = el.find('.iconic-icon') ?? el.createDiv(); + this.refreshIcon(rule, iconEl); + } + } + break; + } + } + } + + /** + * Refresh icon of a Quick Switcher++ suggestion. + */ + private refreshSuggestionIconQSPP(value: any, el: HTMLElement): void { + switch (value?.type) { + case 'file': { + if (value.file instanceof TFile) { + const file = this.plugin.getFileItem(value.file.path); + const rule = this.plugin.ruleManager.checkRuling('file', file.id) ?? file; + if (rule.icon || rule.color) { + const iconEl = el.find('.iconic-icon') ?? el.createDiv(); + this.refreshIcon(rule, iconEl); + } + } + break; + } + case 'bookmark': { + const bmarkBase = value.item; + if (bmarkBase.type === 'file' || bmarkBase.type === 'folder') { + const file = this.plugin.getFileItem(bmarkBase.path); + const rule = this.plugin.ruleManager.checkRuling(bmarkBase.type, file.id) ?? file; + if (rule.icon || rule.color) { + const iconEl = el.find('.iconic-icon') ?? el.createDiv(); + this.refreshIcon(rule, iconEl); + } + } + break; + } + case 'editorList': { // Represents an open tab in Editor Mode + if (!(value.item instanceof WorkspaceLeaf)) break; + const tabType = value.item.view.getViewType(); + const iconDefault = value.item.view.getIcon(); + + // Distinguish between file tabs and plugin tabs + if (FILE_TAB_TYPES.includes(tabType) && value.file instanceof TFile) { + const file = this.plugin.getFileItem(value.file.path); + const rule = this.plugin.ruleManager.checkRuling('file', file.id) ?? file; + if (rule.icon || rule.color) { + const iconEl = el.find('.iconic-icon') ?? el.createDiv(); + this.refreshIcon(rule, iconEl); + } + } else { + const tab = this.plugin.getTabItem(tabType); + if (tab) { + tab.iconDefault = iconDefault; + const iconEl = el.find('.iconic-icon') ?? el.createDiv(); + this.refreshIcon(tab, iconEl); + } + } + break; + } + } + } + + /** + * Refresh icon of Another Quick Switcher suggestion. + */ + private refreshSuggestionIconAQS(value: any, el: HTMLElement): void { + const tFile = value.file; + if (tFile instanceof TFile) { + const itemEl = el.find('.another-quick-switcher__item'); + const file = this.plugin.getFileItem(tFile.path); + if (file.icon || file.color) { + const iconEl = itemEl.find('.iconic-icon') ?? itemEl.createDiv(); + itemEl.prepend(iconEl); + this.refreshIcon(file, iconEl); + } + } + } + + /** + * Check whether user has disabled quick switcher icons. + */ + private isDisabled(): boolean { + return !this.plugin.settings.showQuickSwitcherIcons; + } + + /** + * @override + */ + unload(): void { + super.unload(); + if (SuggestModal.prototype.onOpen === this.onOpenProxy) { + SuggestModal.prototype.onOpen = this.onOpenOriginal; + } + if (SuggestModal.prototype.setInstructions === this.setInstructionsProxy) { + SuggestModal.prototype.setInstructions = this.setInstructionsOriginal; + } + } +} diff --git a/src/managers/RibbonIconManager.ts b/src/managers/RibbonIconManager.ts index 0bf2f0d..988e805 100644 --- a/src/managers/RibbonIconManager.ts +++ b/src/managers/RibbonIconManager.ts @@ -97,9 +97,15 @@ export default class RibbonIconManager extends IconManager { ribbonItem.iconDefault = null; } this.refreshIcon(ribbonItem, iconEl); - this.setEventListener(iconEl, 'contextmenu', event => { - this.onContextMenu(ribbonItem.id, event); - }, { capture: true }); + + // Add context menu + if (this.plugin.settings.showMenuActions) { + this.setEventListener(iconEl, 'contextmenu', event => { + this.onContextMenu(ribbonItem.id, event); + }, { capture: true }); + } else { + this.stopEventListener(iconEl, 'contextmenu'); + } } } } @@ -205,4 +211,12 @@ export default class RibbonIconManager extends IconManager { if (menu instanceof Menu) menu.showAtMouseEvent(event); } + + /** + * @override + */ + unload(): void { + this.refreshIcons(true); + super.unload(); + } } diff --git a/src/managers/RuleManager.ts b/src/managers/RuleManager.ts index 219b347..2787533 100644 --- a/src/managers/RuleManager.ts +++ b/src/managers/RuleManager.ts @@ -1,9 +1,8 @@ import { TFile } from 'obsidian'; -import IconicPlugin, { Item, FileItem, ICONS, EMOJIS, STRINGS } from 'src/IconicPlugin'; +import IconicPlugin, { Category, Item, FileItem, ICONS, EMOJIS, STRINGS } from 'src/IconicPlugin'; const BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; -export type RulePage = 'file' | 'folder'; export type RuleTrigger = 'icon' | 'color' | 'rename' | 'move' | 'tag' | 'property' | 'modify' | 'date' | 'time'; export interface RuleItem extends Item { @@ -63,21 +62,23 @@ export default class RuleManager { /** * Get array of rule definitions from a given page. */ - getRules(page: RulePage): RuleItem[] { + getRules(page: Category): RuleItem[] { switch (page) { case 'file': return this.plugin.settings.fileRules.map(ruleBase => this.defineRule(ruleBase)); case 'folder': return this.plugin.settings.folderRules.map(ruleBase => this.defineRule(ruleBase)); + default: return []; } } /** * Get rule definition from a given page. */ - getRule(page: RulePage, ruleId: string): RuleItem | null { + getRule(page: Category, ruleId: string): RuleItem | null { let ruleBases: typeof this.plugin.settings.fileRules; switch (page) { case 'file': ruleBases = this.plugin.settings.fileRules; break; case 'folder': ruleBases = this.plugin.settings.folderRules; break; + default: ruleBases = []; } const ruleBase = ruleBases.find(rule => rule.id === ruleId); return ruleBase ? this.defineRule(ruleBase) : null; @@ -86,7 +87,7 @@ export default class RuleManager { /** * Get array of rule bases from a given page. */ - private getRuleBases(page: RulePage): typeof this.plugin.settings.fileRules { + private getRuleBases(page: Category): typeof this.plugin.settings.fileRules { switch (page) { default: return this.plugin.settings.fileRules; case 'file': return this.plugin.settings.fileRules; @@ -97,8 +98,9 @@ export default class RuleManager { /** * Get default rule icon for a given page. */ - getPageIcon(page: RulePage): string { + getPageIcon(page: Category): string { switch (page) { + default: return 'lucide-file'; case 'file': return 'lucide-file'; case 'folder': return 'lucide-folder'; } @@ -124,7 +126,7 @@ export default class RuleManager { /** * Generate a 5-character rule ID. 916,132,832 possible values. */ - private newRuleId(page: RulePage): string { + private newRuleId(page: Category): string { const ids = this.getRuleBases(page).map(ruleBase => ruleBase.id); let id: string; let collisions = 0; @@ -141,7 +143,7 @@ export default class RuleManager { /** * Create new rule on a given page. */ - newRule(page: RulePage): RuleItem { + newRule(page: Category): RuleItem { const newRule: RuleItem = { id: this.newRuleId(page), name: STRINGS.rulePicker.untitledRule, @@ -160,7 +162,7 @@ export default class RuleManager { /** * Move rule within a given page, and return true if this changes any rulings. */ - moveRule(page: RulePage, rule: RuleItem, toIndex: number): boolean { + moveRule(page: Category, rule: RuleItem, toIndex: number): boolean { const ruleBases = this.getRuleBases(page); const ruleBase = ruleBases.find(ruleBase => ruleBase.id === rule.id); if (!ruleBase) return false; @@ -176,7 +178,7 @@ export default class RuleManager { /** * Save rule to a given page, and return true if this changes any rulings. */ - saveRule(page: RulePage, newRule: RuleItem): boolean { + saveRule(page: Category, newRule: RuleItem): boolean { const ruleBases = this.getRuleBases(page); let ruleBase = ruleBases.find(rule => rule.id === newRule.id); if (!ruleBase) { @@ -212,7 +214,7 @@ export default class RuleManager { /** * Delete rule from a given page, and return true if this changes any rulings. */ - deleteRule(page: RulePage, ruleId: string): boolean { + deleteRule(page: Category, ruleId: string): boolean { const ruleBases = this.getRuleBases(page); const index = ruleBases.findIndex(ruleBase => ruleBase.id === ruleId); if (index === -1) return false; @@ -225,18 +227,19 @@ export default class RuleManager { /** * Check the ruling for a given item. */ - checkRuling(page: RulePage, itemId: string, unloading?: boolean): RuleItem | null { + checkRuling(page: Category, itemId: string, unloading?: boolean): RuleItem | null { if (unloading) return null; switch (page) { case 'file': return this.fileRulings.get(itemId) ?? null; case 'folder': return this.folderRulings.get(itemId) ?? null; + default: return null; } } /** * Update rulings for a given page, and return true if this changes any rulings. */ - updateRulings(page: RulePage): boolean { + updateRulings(page: Category): boolean { const now = new Date(); // Use this timestamp to check any chronological conditions const enabledRules = this.getRules(page).filter(rule => rule.enabled); @@ -348,20 +351,41 @@ export default class RuleManager { /** * Check a given condition and activate any triggers it will need. */ - private updateTriggers(page: RulePage, condition: ConditionItem): void { + private updateTriggers(page: Category, condition: ConditionItem): void { let triggers: Set; switch (page) { case 'file': triggers = this.fileTriggers; break; case 'folder': triggers = this.folderTriggers; break; + default: return; } switch (condition.source) { case 'icon': triggers.add('icon'); break; case 'color': triggers.add('color'); break; - case 'name': triggers.add('rename'); break; - case 'filename': triggers.add('rename'); break; - case 'extension': triggers.add('rename'); break; - case 'tree': triggers.add('move'); break; - case 'path': triggers.add('move'); break; + case 'name': { + triggers.add('rename'); + triggers.add('move'); + break; + } + case 'filename': { + triggers.add('rename'); + triggers.add('move'); + break; + } + case 'extension': { + triggers.add('rename'); + triggers.add('move'); + break; + } + case 'tree': { + triggers.add('rename'); + triggers.add('move'); + break; + } + case 'path': { + triggers.add('rename'); + triggers.add('move'); + break; + } case 'headings': triggers.add('modify'); break; case 'links': triggers.add('modify'); break; case 'tags': triggers.add('modify'); break; @@ -425,7 +449,7 @@ export default class RuleManager { * Update rulings for a given page, and return true if this changes any rulings. * @param triggers If none of the specified triggers are active, skip the update. */ - triggerRulings(page: RulePage, ...triggers: RuleTrigger[]): boolean { + triggerRulings(page: Category, ...triggers: RuleTrigger[]): boolean { switch (page) { case 'file': for (const trigger of triggers) { if (this.fileTriggers.has(trigger)) { @@ -437,8 +461,8 @@ export default class RuleManager { return this.updateRulings(page); } } + default: return false; } - return false; } /** @@ -487,7 +511,7 @@ export default class RuleManager { for (const condition of rule.conditions) { let isConditionMatched = false; - let source: boolean | number | string | string[] | null | undefined = undefined; + let source: boolean | number | string | (string | null)[] | null | undefined = undefined; const isNegated = condition.operator.startsWith('!'); const operator = condition.operator.replace('!', ''); const value = condition.value; @@ -533,7 +557,7 @@ export default class RuleManager { // Prepare case-insensitive strings const sourceLower = String.isString(source) ? source.toLowerCase() : ''; - const sourceLowers = Array.isArray(source) ? source.map(item => item.toLowerCase()) : []; + const sourceLowers = Array.isArray(source) ? source.map(item => String(item).toLowerCase()) : []; const valueLower = String.isString(value) ? value.toLowerCase() : ''; // Check if condition is true @@ -647,9 +671,9 @@ export default class RuleManager { case 'anyStartWith': isConditionMatched = RuleManager.any(sourceLowers, 'startWith', valueLower); break; case 'anyEndWith': isConditionMatched = RuleManager.any(sourceLowers, 'endWith', valueLower); break; case 'anyMatch': isConditionMatched = RuleManager.any(source, 'match', value); break; - case 'noneContain': isConditionMatched = RuleManager.none(source, 'contain', value); break; - case 'noneStartWith': isConditionMatched = RuleManager.none(source, 'startWith', value); break; - case 'noneEndWith': isConditionMatched = RuleManager.none(source, 'endWith', value); break; + case 'noneContain': isConditionMatched = RuleManager.none(sourceLowers, 'contain', value); break; + case 'noneStartWith': isConditionMatched = RuleManager.none(sourceLowers, 'startWith', value); break; + case 'noneEndWith': isConditionMatched = RuleManager.none(sourceLowers, 'endWith', value); break; case 'noneMatch': isConditionMatched = RuleManager.none(source, 'match', value); break; case 'countIs': isConditionMatched = value !== '' && source.length === Number(value); break; case 'countIsLess': isConditionMatched = value !== '' && source.length < Number(value); break; @@ -828,19 +852,19 @@ export default class RuleManager { /** * Check whether all items match a given operator & value. */ - private static all(items: string[], operator: 'are' | 'contain' | 'startWith' | 'endWith' | 'match', value: string): boolean { + private static all(items: (string | null)[], operator: 'are' | 'contain' | 'startWith' | 'endWith' | 'match', value: string): boolean { if (items.length === 0 || value === '') return false; switch (operator) { case 'are': for (const item of items) if (item !== value) return false; break; - case 'contain': for (const item of items) if (!item.includes(value)) return false; break; - case 'startWith': for (const item of items) if (!item.startsWith(value)) return false; break; - case 'endWith': for (const item of items) if (!item.endsWith(value)) return false; break; + case 'contain': for (const item of items) if (!String(item).includes(value)) return false; break; + case 'startWith': for (const item of items) if (!String(item).startsWith(value)) return false; break; + case 'endWith': for (const item of items) if (!String(item).endsWith(value)) return false; break; case 'match': { try { const regex = RuleManager.unwrapRegex(value); for (const item of items) { - if (!regex.test(item)) return false; + if (!regex.test(String(item))) return false; } } catch { /* Catch invalid regex */ }; break; @@ -852,19 +876,19 @@ export default class RuleManager { /** * Check whether any items match a given operator & value. */ - private static any(items: string[], operator: 'are' | 'contain' | 'startWith' | 'endWith' | 'match', value: string): boolean { + private static any(items: (string | null)[], operator: 'are' | 'contain' | 'startWith' | 'endWith' | 'match', value: string): boolean { if (value === '') return false; switch (operator) { case 'are': for (const item of items) if (item === value) return true; break; - case 'contain': for (const item of items) if (item.includes(value)) return true; break; - case 'startWith': for (const item of items) if (item.startsWith(value)) return true; break; - case 'endWith': for (const item of items) if (item.endsWith(value)) return true; break; + case 'contain': for (const item of items) if (String(item).includes(value)) return true; break; + case 'startWith': for (const item of items) if (String(item).startsWith(value)) return true; break; + case 'endWith': for (const item of items) if (String(item).endsWith(value)) return true; break; case 'match': { try { const regex = RuleManager.unwrapRegex(value); for (const item of items) { - if (regex.test(item)) return true; + if (regex.test(String(item))) return true; } } catch { /* Catch invalid regex */ }; break; @@ -876,19 +900,19 @@ export default class RuleManager { /** * Check whether no items match a given operator & value. */ - private static none(items: string[], operator: 'are' | 'contain' | 'startWith' | 'endWith' | 'match', value: string): boolean { + private static none(items: (string | null)[], operator: 'are' | 'contain' | 'startWith' | 'endWith' | 'match', value: string): boolean { if (value === '') return false; switch (operator) { case 'are': for (const item of items) if (item === value) return false; break; - case 'contain': for (const item of items) if (item.includes(value)) return false; break; - case 'startWith': for (const item of items) if (item.startsWith(value)) return false; break; - case 'endWith': for (const item of items) if (item.endsWith(value)) return false; break; + case 'contain': for (const item of items) if (String(item).includes(value)) return false; break; + case 'startWith': for (const item of items) if (String(item).startsWith(value)) return false; break; + case 'endWith': for (const item of items) if (String(item).endsWith(value)) return false; break; case 'match': { try { const regex = RuleManager.unwrapRegex(value); for (const item of items) { - if (regex.test(item)) return false; + if (regex.test(String(item))) return false; } } catch { /* Catch invalid regex */ }; break; diff --git a/src/managers/TabIconManager.ts b/src/managers/TabIconManager.ts index 3df067b..570dc5a 100644 --- a/src/managers/TabIconManager.ts +++ b/src/managers/TabIconManager.ts @@ -1,5 +1,5 @@ import { Platform } from 'obsidian'; -import IconicPlugin, { FileItem, TabItem, STRINGS } from 'src/IconicPlugin'; +import IconicPlugin, { Category, FileItem, TabItem, STRINGS } from 'src/IconicPlugin'; import IconManager from './IconManager'; import RuleEditor from 'src/dialogs/RuleEditor'; import IconPicker from 'src/dialogs/IconPicker'; @@ -39,15 +39,15 @@ export default class TabIconManager extends IconManager { for (const tab of tabs) { const tabEl = tab.tabEl; const iconEl = tab.iconEl; - if (!tabEl || !iconEl) continue; + if (!tabEl || !iconEl || tab.id === 'webviewer') continue; // Check for an icon ruling - const rule = tab.isFile + const rule = tab.category === 'file' ? this.plugin.ruleManager.checkRuling('file', tab.id, unloading) ?? tab : tab; if (tab.isRoot && this.plugin.isSettingEnabled('clickableIcons')) { - if (tab.isFile) { + if (tab.category === 'file') { const file = this.plugin.getFileItem(tab.id); this.refreshIcon(rule, iconEl, event => { IconPicker.openSingle(this.plugin, file, (newIcon, newColor) => { @@ -82,10 +82,10 @@ export default class TabIconManager extends IconManager { }); // Skip menu listener if tab is handled by workspace.on('file-menu') - if (tab.isFile && (tab.isActive || tab.isStacked)) { + if (!this.plugin.settings.showMenuActions || tab.category === 'file' && (tab.isActive || tab.isStacked)) { this.stopEventListener(tabEl, 'contextmenu'); } else { - this.setEventListener(tabEl, 'contextmenu', () => this.onContextMenu(tab.id, tab.isFile)); + this.setEventListener(tabEl, 'contextmenu', () => this.onContextMenu(tab.id, tab.category)); } // Refresh when tab is pinned/unpinned @@ -115,11 +115,25 @@ export default class TabIconManager extends IconManager { // @ts-expect-error (Private API) if (this.app.workspace.leftSplit.activeTabIconEl === iconEl) { // @ts-expect-error (Private API) - this.setEventListener(this.app.workspace.leftSplit.activeTabHeaderEl, 'contextmenu', () => this.onContextMenu(tab.id, tab.isFile)); + const leftActiveTabEl = this.app.workspace.leftSplit.activeTabHeaderEl; + if (this.plugin.settings.showMenuActions) { + this.setEventListener(leftActiveTabEl, 'contextmenu', () => { + this.onContextMenu(tab.id, tab.category); + }); + } else { + this.stopEventListener(leftActiveTabEl, 'contextmenu'); + } // @ts-expect-error (Private API) } else if (this.app.workspace.rightSplit.activeTabIconEl === iconEl) { // @ts-expect-error (Private API) - this.setEventListener(this.app.workspace.rightSplit.activeTabHeaderEl, 'contextmenu', () => this.onContextMenu(tab.id, tab.isFile)); + const rightActiveTabEl = this.app.workspace.rightSplit.activeTabHeaderEl; + if (this.plugin.settings.showMenuActions) { + this.setEventListener(rightActiveTabEl, 'contextmenu', () => { + this.onContextMenu(tab.id, tab.category); + }); + } else { + this.stopEventListener(rightActiveTabEl, 'contextmenu'); + } } } } @@ -128,10 +142,10 @@ export default class TabIconManager extends IconManager { /** * When user context-clicks a tab, add custom items to the menu. */ - private onContextMenu(tabId: string, isFile: boolean) { + private onContextMenu(tabId: string, tabCategory: Category) { this.plugin.menuManager.closeAndFlush(); - if (isFile) { + if (tabCategory === 'file') { this.onFileContextMenu(this.plugin.getFileItem(tabId)); } else { const tab = this.plugin.getTabItem(tabId); @@ -224,4 +238,12 @@ export default class TabIconManager extends IconManager { }); } } + + /** + * @override + */ + unload(): void { + this.refreshIcons(true); + super.unload(); + } } diff --git a/src/managers/TagIconManager.ts b/src/managers/TagIconManager.ts index 83007d3..9fd6ff7 100644 --- a/src/managers/TagIconManager.ts +++ b/src/managers/TagIconManager.ts @@ -85,8 +85,12 @@ export default class TagIconManager extends IconManager { this.refreshIcon(tag, iconEl); } - if (selfEl) { - this.setEventListener(selfEl, 'contextmenu', event => this.onContextMenu(tag.id, event)); + if (this.plugin.settings.showMenuActions && selfEl) { + this.setEventListener(selfEl, 'contextmenu', event => { + this.onContextMenu(tag.id, event); + }); + } else { + this.stopEventListener(selfEl, 'contextmenu'); } } } @@ -135,4 +139,12 @@ export default class TagIconManager extends IconManager { menu.showAtMouseEvent(event); } } + + /** + * @override + */ + unload(): void { + this.refreshIcons(true); + super.unload(); + } } diff --git a/styles.css b/styles.css index 0b0993b..8455bc8 100644 --- a/styles.css +++ b/styles.css @@ -25,7 +25,7 @@ body { } /* Tabs */ -.workspace .mod-root .workspace-tab-header[data-type="markdown"] .workspace-tab-header-inner-icon, +.iconic-markdown-tab-icons .workspace .mod-root .workspace-tab-header[data-type="markdown"] .workspace-tab-header-inner-icon, .workspace .mod-root .workspace-tab-header[data-type="empty"] .workspace-tab-header-inner-icon { display: flex; } @@ -121,6 +121,50 @@ a.tag { cursor: var(--cursor-link); } +/* Quick Switcher */ +.iconic-quick-switcher .iconic-icon { + height: var(--icon-size); +} +.iconic-quick-switcher .prompt-input { + padding-inline-start: calc(var(--icon-size) + var(--size-4-8)); +} +.iconic-quick-switcher .suggestion-item { + align-items: center; +} +.iconic-quick-switcher .suggestion-content { + padding-inline-start: calc(var(--icon-size) + var(--size-4-2)); +} +.iconic-quick-switcher .iconic-icon + .suggestion-content { + padding-inline-start: var(--size-4-2); +} + +/* Another Quick Switcher */ +.iconic-another-quick-switcher .iconic-icon { + height: var(--icon-size); +} +.iconic-another-quick-switcher .another-quick-switcher__item { + flex-direction: row; + padding-inline-start: var(--size-4-5); +} +.iconic-another-quick-switcher .another-quick-switcher__starred_item, +.iconic-another-quick-switcher .another-quick-switcher__phantom_item { + padding-inline-start: var(--size-4-3); +} +.iconic-another-quick-switcher .another-quick-switcher__item > .another-quick-switcher__item__entry { + padding-inline-start: calc(var(--icon-size) + var(--size-4-2)); +} +.iconic-another-quick-switcher .another-quick-switcher__item > .iconic-icon + .another-quick-switcher__item__entry { + padding-inline-start: var(--size-4-2); +} +.iconic-another-quick-switcher .another-quick-switcher__starred_item > .another-quick-switcher__item__entry, +.iconic-another-quick-switcher .another-quick-switcher__phantom_item > .another-quick-switcher__item__entry { + padding-inline-start: calc(var(--icon-size) + var(--size-4-2)); +} +.iconic-another-quick-switcher .another-quick-switcher__item::before { + margin-left: -1rem; + margin-right: var(--size-4-2); +} + /* Icon picker */ .is-mobile .mod-confirmation > .modal.iconic-icon-picker { background-color: var(--modal-background); @@ -304,6 +348,7 @@ a.tag { } .iconic-match { padding: var(--size-4-1); + white-space: nowrap; } .iconic-highlight-tree .iconic-match-tree, .iconic-highlight-name .iconic-match-name, @@ -356,21 +401,6 @@ a.tag { display: flex; } -/* Theme: Border */ -/* .iconic-theme-border .workspace-leaf-content:is([data-type="file-explorer"], [data-type="bookmarks"]) .nav-file-title::before { - display: none; -} -.iconic-theme-border .workspace-leaf-content:is([data-type="file-explorer"], [data-type="bookmarks"]) .tree-item > .tree-item-self.mod-collapsible > .collapse-icon { - -webkit-mask-image: none; - background-color: transparent; -} -.iconic-theme-border .workspace-leaf-content:is([data-type="file-explorer"], [data-type="bookmarks"]) .tree-item-self.mod-collapsible > .collapse-icon > .svg-icon { - color: unset; -} -.iconic-theme-border .workspace-leaf-content[data-type="file-explorer"] .tree-item > .tree-item-self:is(.nav-file-title, .nav-folder.mod-root > .nav-folder-title)::before { - display: none; -} */ - /* Theme: Catppuccin */ .iconic-theme-cat .workspace-leaf-content[data-type="file-explorer"] .nav-folder-title-content::before { display: none;