diff --git a/src/lib/icon.ts b/src/lib/icon.ts index 312c5f25..7534d2bc 100644 --- a/src/lib/icon.ts +++ b/src/lib/icon.ts @@ -216,6 +216,8 @@ const addAll = ( if (titleEl.children.length === 2 || titleEl.children.length === 1) { // Gets the icon name directly or from the inheritance folder. const iconName = typeof value === 'string' ? value : value.iconName; + const iconColor = + typeof value === 'string' ? undefined : value.iconColor; if (iconName) { // Removes a possible existing icon. const existingIcon = titleEl.querySelector('.iconize-icon'); @@ -231,7 +233,7 @@ const addAll = ( IconCache.getInstance().set(dataPath, { iconNameWithPrefix: iconName, }); - dom.setIconForNode(plugin, iconName, iconNode); + dom.setIconForNode(plugin, iconName, iconNode, iconColor); titleEl.insertBefore(iconNode, titleInnerEl); } diff --git a/src/lib/inheritance.ts b/src/lib/inheritance.ts index 46c8a9ac..39803a43 100644 --- a/src/lib/inheritance.ts +++ b/src/lib/inheritance.ts @@ -26,7 +26,12 @@ const getFolders = ( plugin: IconFolderPlugin, ): Record => { return Object.entries(plugin.getData()) - .filter(([k, v]) => k !== 'settings' && typeof v === 'object') + .filter( + ([k, v]) => + k !== 'settings' && + typeof v === 'object' && + (v as FolderIconObject).inheritanceIcon, + ) .reduce>((prev, [path, value]) => { prev[path] = value as FolderIconObject; return prev; @@ -57,6 +62,10 @@ const add = ( return; } + if (!(folder as FolderIconObject).inheritanceIcon) { + return; + } + // A inner function that helps to add the inheritance icon to the DOM. const addIcon = (fileItem: FileItem): void => { const titleEl = getFileItemTitleEl(fileItem); @@ -133,6 +142,10 @@ const remove = ( return; } + if (!(folder as FolderIconObject).inheritanceIcon) { + return; + } + // Gets all files that include the folder path of the currently opened vault. const files = getFiles(plugin, folderPath); diff --git a/src/lib/util/dom.ts b/src/lib/util/dom.ts index 77810085..83eda1e4 100644 --- a/src/lib/util/dom.ts +++ b/src/lib/util/dom.ts @@ -162,11 +162,18 @@ const getIconFromElement = (element: HTMLElement): string | undefined => { return existingIcon; }; +const getIconNodeFromPath = (path: string): HTMLElement | undefined => { + return document + .querySelector(`[data-path="${path}"]`) + ?.querySelector('[data-icon]'); +}; + export default { setIconForNode, createIconNode, doesElementHasIconNode, getIconFromElement, + getIconNodeFromPath, removeIconInNode, removeIconInPath, }; diff --git a/src/main.ts b/src/main.ts index 30a33693..3ce29ca7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -50,10 +50,12 @@ import { buildIconPlugin } from './editor/live-preview'; import { PositionField, buildPositionField } from './editor/live-preview/state'; import { calculateInlineTitleSize } from './lib/util/text'; import { processMarkdown } from './editor/markdown-processor'; +import ChangeColorModal from './ui/change-color-modal'; export interface FolderIconObject { iconName: string | null; - inheritanceIcon: string; + inheritanceIcon?: string; + iconColor?: string; } export default class IconFolderPlugin extends Plugin { @@ -135,6 +137,15 @@ export default class IconFolderPlugin extends Plugin { }); }; + const changeColorOfIcon = (item: MenuItem) => { + item.setTitle('Change color of icon'); + item.setIcon('palette'); + item.onClick(() => { + const modal = new ChangeColorModal(this.app, this, file.path); + modal.open(); + }); + }; + menu.addItem(addIconMenuItem); const filePathData = this.getData()[file.path]; @@ -147,12 +158,20 @@ export default class IconFolderPlugin extends Plugin { filePathData && (typeof filePathData === 'string' || inheritanceFolderHasIcon) ) { + const icon = + typeof filePathData === 'string' + ? filePathData + : (filePathData as FolderIconObject).iconName; + if (!emoji.isEmoji(icon)) { + menu.addItem(changeColorOfIcon); + } + menu.addItem(removeIconMenuItem); } const inheritIcon = (item: MenuItem) => { const iconData = this.data[file.path] as FolderIconObject | string; - if (typeof iconData === 'object') { + if (typeof iconData === 'object' && iconData.inheritanceIcon) { item.setTitle('Remove inherit icon'); item.onClick(() => { inheritance.remove(this, file.path, { @@ -280,6 +299,9 @@ export default class IconFolderPlugin extends Plugin { const folderPath = inheritance.getFolderPathByFilePath(this, file.path); const folderInheritance = inheritance.getByPath(this, file.path); const iconName = folderInheritance.inheritanceIcon; + if (!iconName) { + return; + } didUpdate = true; inheritance.add(this, folderPath, iconName, { file, @@ -406,6 +428,11 @@ export default class IconFolderPlugin extends Plugin { const folderInheritance = inheritance.getByPath(this, file.path); const iconName = folderInheritance.inheritanceIcon; dom.removeIconInPath(file.path); + + if (!iconName) { + return; + } + inheritance.add(this, folderPath, iconName, { file, onAdd: (file) => { @@ -486,6 +513,10 @@ export default class IconFolderPlugin extends Plugin { inheritanceFolders.forEach( ([path, obj]: [string, FolderIconObject]) => { + if (!obj.inheritanceIcon) { + return; + } + inheritance.add(this, path, obj.inheritanceIcon, { file, onAdd: (file) => { @@ -747,8 +778,10 @@ export default class IconFolderPlugin extends Plugin { if (icon === null && currentValue && typeof currentValue === 'object') { const folderObject = currentValue as FolderIconObject; - if (folderObject.iconName) { + if (folderObject.iconName && !folderObject.iconColor) { this.data[folderPath] = getNormalizedName(folderObject.iconName); + } else if (folderObject.iconName) { + (currentValue as FolderIconObject).inheritanceIcon = null; } else { delete this.data[folderPath]; } @@ -806,6 +839,48 @@ export default class IconFolderPlugin extends Plugin { this.saveIconFolderData(); } + addIconColor(path: string, iconColor: string): void { + const pathData = this.getData()[path]; + + if (typeof pathData === 'string') { + this.getData()[path] = { + iconName: pathData, + iconColor, + }; + } else { + (pathData as FolderIconObject).iconColor = iconColor; + } + + this.saveIconFolderData(); + } + + getIconColor(path: string): string | undefined { + const pathData = this.getData()[path]; + + if (typeof pathData === 'string') { + return undefined; + } + + return (pathData as FolderIconObject).iconColor; + } + + removeIconColor(path: string): void { + const pathData = this.getData()[path]; + + if (typeof pathData === 'string') { + return; + } + + const currentValue = pathData as FolderIconObject; + if (!currentValue.inheritanceIcon) { + this.data[path] = currentValue.iconName; + } else { + delete currentValue.iconColor; + } + + this.saveIconFolderData(); + } + removeFolderIcon(path: string): void { if (!this.data[path]) { return; @@ -816,10 +891,15 @@ export default class IconFolderPlugin extends Plugin { if (typeof this.data[path] === 'object') { const currentValue = this.data[path] as FolderIconObject; - this.data[path] = { - ...currentValue, - iconName: null, - }; + if (!currentValue.inheritanceIcon) { + delete this.data[path]; + } else { + delete currentValue.iconColor; + this.data[path] = { + ...currentValue, + iconName: null, + }; + } } else { delete this.data[path]; } diff --git a/src/settings/ui/customIconRule.ts b/src/settings/ui/customIconRule.ts index e09acce1..5cf2e821 100644 --- a/src/settings/ui/customIconRule.ts +++ b/src/settings/ui/customIconRule.ts @@ -285,7 +285,7 @@ export default class CustomIconRuleSetting extends IconFolderSetting { // Create modal and its children elements. const modal = new Modal(this.plugin.app); modal.contentEl.style.display = 'block'; - modal.modalEl.classList.add('iconize-custom-rule-modal'); + modal.modalEl.classList.add('iconize-custom-modal'); modal.titleEl.setText('Edit custom rule'); // Create the input for the rule. diff --git a/src/settings/ui/emojiStyle.ts b/src/settings/ui/emojiStyle.ts index a16cde2a..3a2e2bde 100644 --- a/src/settings/ui/emojiStyle.ts +++ b/src/settings/ui/emojiStyle.ts @@ -38,6 +38,10 @@ export default class EmojiStyleSetting extends IconFolderSetting { const inheritanceData = this.plugin.getData()[ path ] as FolderIconObject; + if (!inheritanceData.inheritanceIcon) { + continue; + } + iconName = inheritanceData.iconName; // Handle updating the emoji style for the inheritance icon. diff --git a/src/styles.css b/src/styles.css index 25e700d6..ded0f446 100644 --- a/src/styles.css +++ b/src/styles.css @@ -98,13 +98,13 @@ } /* Custom rule modal. */ -.iconize-custom-rule-modal .modal-content { +.iconize-custom-modal .modal-content { display: flex; align-items: center; justify-content: center; } -.iconize-custom-rule-modal .modal-content input { +.iconize-custom-modal .modal-content input { width: 100%; margin-right: 0.5rem; } diff --git a/src/ui/change-color-modal.ts b/src/ui/change-color-modal.ts new file mode 100644 index 00000000..03516659 --- /dev/null +++ b/src/ui/change-color-modal.ts @@ -0,0 +1,80 @@ +import { App, Modal, ColorComponent, ButtonComponent, Notice } from 'obsidian'; +import IconFolderPlugin from '@app/main'; +import svg from '@app/lib/util/svg'; +import dom from '@app/lib/util/dom'; + +export default class ChangeColorModal extends Modal { + private plugin: IconFolderPlugin; + private path: string; + + private usedColor?: string; + + constructor(app: App, plugin: IconFolderPlugin, path: string) { + super(app); + this.plugin = plugin; + this.path = path; + + this.usedColor = this.plugin.getIconColor(this.path); + + this.contentEl.style.display = 'block'; + this.modalEl.classList.add('iconize-custom-modal'); + this.titleEl.setText('Change color'); + + const description = this.contentEl.createEl('p', { + text: 'Select a color for this icon', + cls: 'setting-item-description', + }); + description.style.marginBottom = 'var(--size-2-2)'; + const colorContainer = this.contentEl.createDiv(); + colorContainer.style.display = 'flex'; + colorContainer.style.alignItems = 'center'; + colorContainer.style.justifyContent = 'space-between'; + const colorPicker = new ColorComponent(colorContainer) + .setValue(this.usedColor ?? '#000000') + .onChange((value) => { + this.usedColor = value; + }); + const defaultColorButton = new ButtonComponent(colorContainer); + defaultColorButton.setTooltip('Set color to the default one'); + defaultColorButton.setButtonText('Reset'); + defaultColorButton.onClick(() => { + colorPicker.setValue('#000000'); + this.usedColor = undefined; + }); + + // Save button. + const button = new ButtonComponent(this.contentEl); + button.buttonEl.style.marginTop = 'var(--size-4-4)'; + button.buttonEl.style.float = 'right'; + button.setButtonText('Save Changes'); + button.onClick(async () => { + new Notice('Color of icon changed.'); + + if (this.usedColor) { + this.plugin.addIconColor(this.path, this.usedColor); + } else { + this.plugin.removeIconColor(this.path); + } + + // Refresh the DOM. + const iconNode = dom.getIconNodeFromPath(this.path); + iconNode.style.color = this.usedColor ?? null; + const colorizedInnerHtml = svg.colorize( + iconNode.innerHTML, + this.usedColor, + ); + iconNode.innerHTML = colorizedInnerHtml; + + this.close(); + }); + } + + onOpen() { + super.onOpen(); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +}