chore: apply prettier + normalize line endings

This commit is contained in:
YazanAmmar 2026-02-12 10:09:41 +03:00
parent 67b76ff3b5
commit 345b0c26a3
25 changed files with 3765 additions and 4600 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

7
.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100,
"endOfLine": "lf"
}

View file

@ -1,6 +1,6 @@
import { Notice, App } from "obsidian";
import { t } from "../i18n/strings";
import type ColorMaster from "../main";
import { Notice, App } from 'obsidian';
import { t } from '../i18n/strings';
import type ColorMaster from '../main';
interface AppWithSetting extends App {
setting: {
@ -16,45 +16,43 @@ interface AppWithSetting extends App {
export function registerCommands(plugin: ColorMaster) {
// Toggle plugin on/off
plugin.addCommand({
id: "toggle",
name: t("commands.enableDisable"),
id: 'toggle',
name: t('commands.enableDisable'),
callback: async () => {
plugin.settings.pluginEnabled = !plugin.settings.pluginEnabled;
await plugin.saveSettings();
new Notice(
plugin.settings.pluginEnabled
? t("notices.pluginEnabled")
: t("notices.pluginDisabled"),
plugin.settings.pluginEnabled ? t('notices.pluginEnabled') : t('notices.pluginDisabled'),
);
},
});
// Select next profile
plugin.addCommand({
id: "profile-next",
name: t("commands.cycleNext"),
id: 'profile-next',
name: t('commands.cycleNext'),
callback: async () => {
const names = Object.keys(plugin.settings.profiles || {});
if (names.length === 0) {
new Notice(t("notices.noProfilesFound"));
new Notice(t('notices.noProfilesFound'));
return;
}
const idx = names.indexOf(plugin.settings.activeProfile);
const next = names[(idx + 1) % names.length];
plugin.settings.activeProfile = next;
await plugin.saveSettings();
new Notice(t("notices.activeProfileSwitched", next));
new Notice(t('notices.activeProfileSwitched', next));
},
});
// Select previous profile
plugin.addCommand({
id: "profile-prev",
name: t("commands.cyclePrevious"),
id: 'profile-prev',
name: t('commands.cyclePrevious'),
callback: async () => {
const names = Object.keys(plugin.settings.profiles || {});
if (names.length === 0) {
new Notice(t("notices.noProfilesFound"));
new Notice(t('notices.noProfilesFound'));
return;
}
const currentIndex = names.indexOf(plugin.settings.activeProfile);
@ -63,14 +61,14 @@ export function registerCommands(plugin: ColorMaster) {
plugin.settings.activeProfile = previousProfile;
await plugin.saveSettings();
new Notice(t("notices.activeProfileSwitched", previousProfile));
new Notice(t('notices.activeProfileSwitched', previousProfile));
},
});
// Open plugin settings
plugin.addCommand({
id: "open-settings",
name: t("commands.openSettings"),
id: 'open-settings',
name: t('commands.openSettings'),
callback: () => {
const app = plugin.app as AppWithSetting;
app.setting.open();
@ -80,30 +78,30 @@ export function registerCommands(plugin: ColorMaster) {
// Toggle active profile theme
plugin.addCommand({
id: "toggle-theme",
name: t("commands.toggleTheme"),
id: 'toggle-theme',
name: t('commands.toggleTheme'),
callback: async () => {
const activeProfileName = plugin.settings.activeProfile;
const activeProfile = plugin.settings.profiles[activeProfileName];
if (!activeProfile) {
new Notice(t("notices.profileNotFound"));
new Notice(t('notices.profileNotFound'));
return;
}
const currentTheme = activeProfile.themeType || "auto";
let nextTheme: "auto" | "dark" | "light";
const currentTheme = activeProfile.themeType || 'auto';
let nextTheme: 'auto' | 'dark' | 'light';
let noticeMessage: string;
if (currentTheme === "light") {
nextTheme = "dark";
noticeMessage = t("notices.themeSwitchedDark");
} else if (currentTheme === "dark") {
nextTheme = "auto";
noticeMessage = t("notices.themeSwitchedAuto");
if (currentTheme === 'light') {
nextTheme = 'dark';
noticeMessage = t('notices.themeSwitchedDark');
} else if (currentTheme === 'dark') {
nextTheme = 'auto';
noticeMessage = t('notices.themeSwitchedAuto');
} else {
nextTheme = "light";
noticeMessage = t("notices.themeSwitchedLight");
nextTheme = 'light';
noticeMessage = t('notices.themeSwitchedLight');
}
activeProfile.themeType = nextTheme;

View file

@ -1,90 +1,90 @@
import { PluginSettings } from "./types";
import { PluginSettings } from './types';
export const DEFAULT_VARS = {
"Plugin Integrations": {
"--iconize-icon-color": "#00aaff",
'Plugin Integrations': {
'--iconize-icon-color': '#00aaff',
},
Backgrounds: {
"--background-primary": "#1e1e1e",
"--background-primary-alt": "#252525",
"--background-secondary": "#2a2a2a",
"--background-secondary-alt": "#303030",
"--background-modifier-border": "#444444",
"--background-modifier-border-hover": "#555555",
"--background-modifier-border-focus": "#007acc",
"--background-modifier-flair": "#3a3a3a",
"--background-modifier-hover": "#3a3a3a",
"--background-modifier-active": "#4a4a4a",
'--background-primary': '#1e1e1e',
'--background-primary-alt': '#252525',
'--background-secondary': '#2a2a2a',
'--background-secondary-alt': '#303030',
'--background-modifier-border': '#444444',
'--background-modifier-border-hover': '#555555',
'--background-modifier-border-focus': '#007acc',
'--background-modifier-flair': '#3a3a3a',
'--background-modifier-hover': '#3a3a3a',
'--background-modifier-active': '#4a4a4a',
},
Text: {
"--text-normal": "#e0e0e0",
"--text-muted": "#999999",
"--text-faint": "#666666",
"--text-on-accent": "#ffffff",
"--text-accent": "#00aaff",
"--text-accent-hover": "#33bbff",
"--text-selection": "#007acc66",
"--h1-color": "#e0e0e0",
"--h2-color": "#d0d0d0",
"--h3-color": "#c0c0c0",
"--h4-color": "#b0b0b0",
"--h5-color": "#a0a0a0",
"--h6-color": "#909090",
'--text-normal': '#e0e0e0',
'--text-muted': '#999999',
'--text-faint': '#666666',
'--text-on-accent': '#ffffff',
'--text-accent': '#00aaff',
'--text-accent-hover': '#33bbff',
'--text-selection': '#007acc66',
'--h1-color': '#e0e0e0',
'--h2-color': '#d0d0d0',
'--h3-color': '#c0c0c0',
'--h4-color': '#b0b0b0',
'--h5-color': '#a0a0a0',
'--h6-color': '#909090',
},
Markdown: {
"--hr-color": "#444444",
"--blockquote-border-color": "#007acc",
"--blockquote-color": "#d0d0d0",
"--blockquote-bg": "transparent",
"--tag-color": "#33bbff",
"--tag-color-hover": "#33bbff",
"--tag-bg": "#1e1e1e",
"--checklist-done-color": "#33bbff",
"--code-normal": "#e0e0e0",
"--code-background": "#2d2d2d",
"--text-highlight-bg": "#2c6887",
'--hr-color': '#444444',
'--blockquote-border-color': '#007acc',
'--blockquote-color': '#d0d0d0',
'--blockquote-bg': 'transparent',
'--tag-color': '#33bbff',
'--tag-color-hover': '#33bbff',
'--tag-bg': '#1e1e1e',
'--checklist-done-color': '#33bbff',
'--code-normal': '#e0e0e0',
'--code-background': '#2d2d2d',
'--text-highlight-bg': '#2c6887',
},
Notices: {
"--cm-notice-text-default": "#e0e0e0",
"--cm-notice-bg-default": "#2a2a2a",
'--cm-notice-text-default': '#e0e0e0',
'--cm-notice-bg-default': '#2a2a2a',
},
"Interactive Elements": {
"--interactive-normal": "#5a5a5a",
"--interactive-hover": "#6a6a6a",
"--interactive-accent": "#007acc",
"--interactive-accent-hover": "#3399dd",
"--interactive-success": "#28a745",
"--interactive-error": "#dc3545",
"--interactive-warning": "#ffc107",
'Interactive Elements': {
'--interactive-normal': '#5a5a5a',
'--interactive-hover': '#6a6a6a',
'--interactive-accent': '#007acc',
'--interactive-accent-hover': '#3399dd',
'--interactive-success': '#28a745',
'--interactive-error': '#dc3545',
'--interactive-warning': '#ffc107',
},
"UI Elements": {
"--titlebar-background": "#252525",
"--titlebar-background-focused": "#303030",
"--titlebar-text-color": "#e0e0e0",
"--sidebar-background": "#252525",
"--sidebar-border-color": "#333333",
"--header-background": "#2a2a2a",
"--header-border-color": "#333333",
"--vault-name-color": "#e0e0e0",
'UI Elements': {
'--titlebar-background': '#252525',
'--titlebar-background-focused': '#303030',
'--titlebar-text-color': '#e0e0e0',
'--sidebar-background': '#252525',
'--sidebar-border-color': '#333333',
'--header-background': '#2a2a2a',
'--header-border-color': '#333333',
'--vault-name-color': '#e0e0e0',
},
"Graph View": {
"--graph-line": "#444444",
"--graph-node": "#999999",
"--graph-text": "#e0e0e0",
"--graph-node-unresolved": "#dc3545",
"--graph-node-focused": "#ffffff",
"--graph-node-tag": "#86efac",
"--graph-node-attachment": "#60a5fa",
'Graph View': {
'--graph-line': '#444444',
'--graph-node': '#999999',
'--graph-text': '#e0e0e0',
'--graph-node-unresolved': '#dc3545',
'--graph-node-focused': '#ffffff',
'--graph-node-tag': '#86efac',
'--graph-node-attachment': '#60a5fa',
},
Misc: {
"--scrollbar-thumb-bg": "#444444",
"--scrollbar-bg": "#2a2a2a",
"--divider-color": "#444444",
'--scrollbar-thumb-bg': '#444444',
'--scrollbar-bg': '#2a2a2a',
'--divider-color': '#444444',
},
};
export const DEFAULT_PROFILE = {
vars: {},
themeType: "auto",
themeType: 'auto',
snippets: [],
convertImagesToJpg: false,
jpgQuality: 85,
@ -92,55 +92,55 @@ export const DEFAULT_PROFILE = {
const OLED_MATRIX_PROFILE = {
vars: {
"--iconize-icon-color": "#00ff00",
"--background-primary": "#000000",
"--background-primary-alt": "#0a0a0a",
"--background-secondary": "#050505",
"--background-secondary-alt": "#101010",
"--background-modifier-border": "#223322",
"--background-modifier-border-hover": "#44ff44",
"--background-modifier-border-focus": "#00ff00",
"--background-modifier-flair": "#111111",
"--background-modifier-hover": "#1a1a1a",
"--background-modifier-active": "#002a00",
"--text-normal": "#f0f0f0",
"--text-muted": "#666666",
"--text-faint": "#444444",
"--text-on-accent": "#000000",
"--text-accent": "#00ff00",
"--text-accent-hover": "#88ff88",
"--text-selection": "#00ff004d",
"--text-highlight-bg": "#00640080",
"--interactive-normal": "#1c1c1c",
"--interactive-hover": "#2c2c2c",
"--interactive-accent": "#008a00",
"--interactive-accent-hover": "#00ff00",
"--interactive-success": "#28a745",
"--interactive-error": "#dc3545",
"--interactive-warning": "#ffc107",
"--titlebar-background": "#000000",
"--titlebar-background-focused": "#000000",
"--titlebar-text-color": "#f0f0f0",
"--sidebar-background": "#000000",
"--sidebar-border-color": "#002a00",
"--header-background": "#050505",
"--header-border-color": "#223322",
"--vault-name-color": "#00ff00",
"--scrollbar-thumb-bg": "#222222",
"--scrollbar-bg": "#000000",
"--divider-color": "#223322",
"--checklist-done-color": "#00ff00",
"--graph-line": "#000000",
"--graph-node": "#00ff00",
"--graph-text": "#ffffff",
"--graph-node-unresolved": "#00ff00",
"--graph-node-focused": "#000000",
"--graph-node-tag": "#000000",
"--graph-node-attachment": "#54ee20",
"--blockquote-color": "#54ee20",
"--tag-color": "#0bd62d",
'--iconize-icon-color': '#00ff00',
'--background-primary': '#000000',
'--background-primary-alt': '#0a0a0a',
'--background-secondary': '#050505',
'--background-secondary-alt': '#101010',
'--background-modifier-border': '#223322',
'--background-modifier-border-hover': '#44ff44',
'--background-modifier-border-focus': '#00ff00',
'--background-modifier-flair': '#111111',
'--background-modifier-hover': '#1a1a1a',
'--background-modifier-active': '#002a00',
'--text-normal': '#f0f0f0',
'--text-muted': '#666666',
'--text-faint': '#444444',
'--text-on-accent': '#000000',
'--text-accent': '#00ff00',
'--text-accent-hover': '#88ff88',
'--text-selection': '#00ff004d',
'--text-highlight-bg': '#00640080',
'--interactive-normal': '#1c1c1c',
'--interactive-hover': '#2c2c2c',
'--interactive-accent': '#008a00',
'--interactive-accent-hover': '#00ff00',
'--interactive-success': '#28a745',
'--interactive-error': '#dc3545',
'--interactive-warning': '#ffc107',
'--titlebar-background': '#000000',
'--titlebar-background-focused': '#000000',
'--titlebar-text-color': '#f0f0f0',
'--sidebar-background': '#000000',
'--sidebar-border-color': '#002a00',
'--header-background': '#050505',
'--header-border-color': '#223322',
'--vault-name-color': '#00ff00',
'--scrollbar-thumb-bg': '#222222',
'--scrollbar-bg': '#000000',
'--divider-color': '#223322',
'--checklist-done-color': '#00ff00',
'--graph-line': '#000000',
'--graph-node': '#00ff00',
'--graph-text': '#ffffff',
'--graph-node-unresolved': '#00ff00',
'--graph-node-focused': '#000000',
'--graph-node-tag': '#000000',
'--graph-node-attachment': '#54ee20',
'--blockquote-color': '#54ee20',
'--tag-color': '#0bd62d',
},
themeType: "dark",
themeType: 'dark',
snippets: [],
convertImagesToJpg: false,
jpgQuality: 85,
@ -148,53 +148,53 @@ const OLED_MATRIX_PROFILE = {
const CITRUS_ZEST_PROFILE = {
vars: {
"--iconize-icon-color": "#FF8C00",
"--background-primary": "#F5F5F5",
"--background-primary-alt": "#f2eded",
"--background-secondary": "#f2eded",
"--background-secondary-alt": "#F0F0F0",
"--background-modifier-border": "#E0E0E0",
"--background-modifier-border-hover": "#FF8C00",
"--background-modifier-border-focus": "#F57C00",
"--background-modifier-flair": "#FFFFFF",
"--background-modifier-hover": "#EEEEEE",
"--background-modifier-active": "#E0E0E0",
"--text-normal": "#3c3434",
"--text-muted": "#ff7300",
"--text-faint": "#ff7b00",
"--text-on-accent": "#FFFFFF",
"--text-accent": "#F57C00",
"--text-accent-hover": "#FF9800",
"--text-selection": "#f27602",
"--text-highlight-bg": "#E0E0E0",
"--interactive-normal": "#F5F5F5",
"--interactive-hover": "#EEEEEE",
"--interactive-accent": "#FF8C00",
"--interactive-accent-hover": "#FFA726",
"--interactive-success": "#4CAF50",
"--interactive-error": "#F44336",
"--interactive-warning": "#FFC107",
"--titlebar-background": "#FFFFFF",
"--titlebar-background-focused": "#F7F7F7",
"--titlebar-text-color": "#e0e0e0",
"--sidebar-background": "#FFFFFF",
"--sidebar-border-color": "#E0E0E0",
"--header-background": "#FDFDFD",
"--header-border-color": "#E0E0E0",
"--vault-name-color": "#F57C00",
"--scrollbar-thumb-bg": "#BDBDBD",
"--scrollbar-bg": "#F5F5F5",
"--divider-color": "#E0E0E0",
"--checklist-done-color": "#757575",
"--graph-line": "#F57C00",
"--graph-node": "#999999",
"--graph-text": "#e0e0e0",
"--graph-node-unresolved": "#F57C00",
"--graph-node-focused": "#ffffff",
"--graph-node-tag": "#F57C00",
"--graph-node-attachment": "#F57C00",
'--iconize-icon-color': '#FF8C00',
'--background-primary': '#F5F5F5',
'--background-primary-alt': '#f2eded',
'--background-secondary': '#f2eded',
'--background-secondary-alt': '#F0F0F0',
'--background-modifier-border': '#E0E0E0',
'--background-modifier-border-hover': '#FF8C00',
'--background-modifier-border-focus': '#F57C00',
'--background-modifier-flair': '#FFFFFF',
'--background-modifier-hover': '#EEEEEE',
'--background-modifier-active': '#E0E0E0',
'--text-normal': '#3c3434',
'--text-muted': '#ff7300',
'--text-faint': '#ff7b00',
'--text-on-accent': '#FFFFFF',
'--text-accent': '#F57C00',
'--text-accent-hover': '#FF9800',
'--text-selection': '#f27602',
'--text-highlight-bg': '#E0E0E0',
'--interactive-normal': '#F5F5F5',
'--interactive-hover': '#EEEEEE',
'--interactive-accent': '#FF8C00',
'--interactive-accent-hover': '#FFA726',
'--interactive-success': '#4CAF50',
'--interactive-error': '#F44336',
'--interactive-warning': '#FFC107',
'--titlebar-background': '#FFFFFF',
'--titlebar-background-focused': '#F7F7F7',
'--titlebar-text-color': '#e0e0e0',
'--sidebar-background': '#FFFFFF',
'--sidebar-border-color': '#E0E0E0',
'--header-background': '#FDFDFD',
'--header-border-color': '#E0E0E0',
'--vault-name-color': '#F57C00',
'--scrollbar-thumb-bg': '#BDBDBD',
'--scrollbar-bg': '#F5F5F5',
'--divider-color': '#E0E0E0',
'--checklist-done-color': '#757575',
'--graph-line': '#F57C00',
'--graph-node': '#999999',
'--graph-text': '#e0e0e0',
'--graph-node-unresolved': '#F57C00',
'--graph-node-focused': '#ffffff',
'--graph-node-tag': '#F57C00',
'--graph-node-attachment': '#F57C00',
},
themeType: "light",
themeType: 'light',
snippets: [],
convertImagesToJpg: false,
jpgQuality: 85,
@ -202,14 +202,14 @@ const CITRUS_ZEST_PROFILE = {
export const BUILT_IN_PROFILES_VARS = {
Default: DEFAULT_PROFILE.vars,
"OLED Matrix": OLED_MATRIX_PROFILE.vars,
"Citrus Zest": CITRUS_ZEST_PROFILE.vars,
'OLED Matrix': OLED_MATRIX_PROFILE.vars,
'Citrus Zest': CITRUS_ZEST_PROFILE.vars,
};
export const BUILT_IN_PROFILES_DATA = {
Default: DEFAULT_PROFILE,
"OLED Matrix": OLED_MATRIX_PROFILE,
"Citrus Zest": CITRUS_ZEST_PROFILE,
'OLED Matrix': OLED_MATRIX_PROFILE,
'Citrus Zest': CITRUS_ZEST_PROFILE,
};
export const DEFAULT_SETTINGS: PluginSettings = {
@ -220,12 +220,12 @@ export const DEFAULT_SETTINGS: PluginSettings = {
deleteSettings: false,
},
pluginEnabled: true,
language: "auto",
language: 'auto',
useRtlLayout: true,
overrideIconizeColors: true,
cleanupInterval: 5,
colorUpdateFPS: 10,
activeProfile: "Default",
activeProfile: 'Default',
profiles: JSON.parse(JSON.stringify(BUILT_IN_PROFILES_DATA)),
globalSnippets: [],
pinnedSnapshots: {},
@ -233,25 +233,25 @@ export const DEFAULT_SETTINGS: PluginSettings = {
// Maps text color variables to their common backgrounds for contrast checking.
export const TEXT_TO_BG_MAP = {
"--text-normal": "--background-primary",
"--text-muted": "--background-primary",
"--text-faint": "--background-primary",
"--text-accent": "--background-primary",
"--text-accent-hover": "--background-primary",
"--h1-color": "--background-primary",
"--h2-color": "--background-primary",
"--h3-color": "--background-primary",
"--h4-color": "--background-primary",
"--h5-color": "--background-primary",
"--h6-color": "--background-primary",
"--blockquote-color": "--background-primary",
"--text-on-accent": "--interactive-accent",
"--vault-name-color": "--sidebar-background",
"--titlebar-text-color": "--titlebar-background",
"--graph-text": "--graph-node",
"--checklist-done-color": "--background-primary",
"--text-highlight-bg": "--text-normal",
"--tag-color": "--tag-bg",
'--text-normal': '--background-primary',
'--text-muted': '--background-primary',
'--text-faint': '--background-primary',
'--text-accent': '--background-primary',
'--text-accent-hover': '--background-primary',
'--h1-color': '--background-primary',
'--h2-color': '--background-primary',
'--h3-color': '--background-primary',
'--h4-color': '--background-primary',
'--h5-color': '--background-primary',
'--h6-color': '--background-primary',
'--blockquote-color': '--background-primary',
'--text-on-accent': '--interactive-accent',
'--vault-name-color': '--sidebar-background',
'--titlebar-text-color': '--titlebar-background',
'--graph-text': '--graph-node',
'--checklist-done-color': '--background-primary',
'--text-highlight-bg': '--text-normal',
'--tag-color': '--tag-bg',
// "--iconize-icon-color": "--background-secondary",
// "--cm-notice-text-default": "--cm-notice-bg-default",
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
import type ColorMaster from "../main";
import type { PluginSettings } from "../types";
import type { LocaleStrings } from "./types";
import { DEFAULT_LOCALE, LocaleCode } from "./types";
import type { LocaleFunc } from "./types";
import arStrings from "./locales/ar";
import enStrings from "./locales/en";
import faStrings from "./locales/fa";
import frStrings from "./locales/fr";
import type ColorMaster from '../main';
import type { PluginSettings } from '../types';
import type { LocaleStrings } from './types';
import { DEFAULT_LOCALE, LocaleCode } from './types';
import type { LocaleFunc } from './types';
import arStrings from './locales/ar';
import enStrings from './locales/en';
import faStrings from './locales/fa';
import frStrings from './locales/fr';
let T: ColorMaster;
@ -55,9 +55,7 @@ export function loadLanguage(settings: PluginSettings) {
console.debug(`Color Master: Loading custom language "${langCode}"`);
ACTIVE_STRINGS = customLang.translations;
} else {
console.debug(
`Color Master: Language "${langCode}" not found, using default.`,
);
console.debug(`Color Master: Language "${langCode}" not found, using default.`);
ACTIVE_STRINGS = FALLBACK_STRINGS;
}
}
@ -82,7 +80,7 @@ export const t = (key: string, ...args: (string | number)[]): string => {
}
// Handle dynamic strings with arguments
if (typeof string === "function") {
if (typeof string === 'function') {
return string(...args);
}
@ -92,7 +90,7 @@ export const t = (key: string, ...args: (string | number)[]): string => {
// Recursively flattens nested objects into dot-notation keys
export function flattenStrings(
obj: Record<string, unknown>,
parentKey = "",
parentKey = '',
result: Record<string, string | LocaleFunc> = {},
): Record<string, string | LocaleFunc> {
for (const key in obj) {
@ -101,10 +99,10 @@ export function flattenStrings(
const value = obj[key];
if (
typeof value === "object" &&
typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
typeof value !== "function"
typeof value !== 'function'
) {
flattenStrings(value, newKey, result);
} else if (!Array.isArray(value)) {

View file

@ -1,20 +1,18 @@
export type LocaleCode = "en" | "ar" | "fa" | "fr";
export type LocaleCode = 'en' | 'ar' | 'fa' | 'fr';
export type LocaleFunc = (...args: (string | number)[]) => string;
export type LocalizedValue =
| string
| ((...args: (string | number)[]) => string);
export type LocalizedValue = string | ((...args: (string | number)[]) => string);
/**
* A dictionary of core language names to display in the list.
*/
export const CORE_LANGUAGES: Record<LocaleCode, string> = {
en: "English",
ar: "العَرَبيَّةُ",
fa: "فارسی",
fr: "Français",
en: 'English',
ar: 'العَرَبيَّةُ',
fa: 'فارسی',
fr: 'Français',
};
export const DEFAULT_LOCALE: LocaleCode = "en";
export const DEFAULT_LOCALE: LocaleCode = 'en';
export interface LocaleStrings {
plugin: {

File diff suppressed because it is too large Load diff

View file

@ -94,20 +94,13 @@
background: none;
&::before {
content: "";
content: '';
position: absolute;
top: 0;
left: 0;
width: 400%;
height: 100%;
background: linear-gradient(
115deg,
#33ff00,
#ffcc00,
#8000ff,
#00b7ff,
#00ff66
);
background: linear-gradient(115deg, #33ff00, #ffcc00, #8000ff, #00b7ff, #00ff66);
background-size: 25% 100%;
animation: cm-border-animation 2s linear infinite;
}
@ -129,20 +122,13 @@
width: 100%;
&::before {
content: "";
content: '';
position: absolute;
top: 0;
left: 0;
width: 400%;
height: 100%;
background: linear-gradient(
115deg,
#33ff00,
#ffcc00,
#8000ff,
#00b7ff,
#00ff66
);
background: linear-gradient(115deg, #33ff00, #ffcc00, #8000ff, #00b7ff, #00ff66);
background-size: 25% 100%;
animation: cm-border-animation 1.5s linear infinite;
animation-play-state: running;
@ -411,7 +397,7 @@
}
}
.color-master-settings-tab[dir="ltr"] .cm-flyout-menu-buttons {
.color-master-settings-tab[dir='ltr'] .cm-flyout-menu-buttons {
transform: translateX(15px);
&.is-open {
opacity: 1;
@ -420,7 +406,7 @@
}
}
.color-master-settings-tab[dir="rtl"] .cm-flyout-menu-buttons {
.color-master-settings-tab[dir='rtl'] .cm-flyout-menu-buttons {
transform: translateX(-15px);
&.is-open {
opacity: 1;

View file

@ -131,26 +131,10 @@
outline: none;
/* Active Border Pattern */
background-image:
linear-gradient(
to right,
var(--interactive-accent) 50%,
transparent 50%
),
linear-gradient(
to bottom,
var(--interactive-accent) 50%,
transparent 50%
),
linear-gradient(
to right,
var(--interactive-accent) 50%,
transparent 50%
),
linear-gradient(
to bottom,
var(--interactive-accent) 50%,
transparent 50%
);
linear-gradient(to right, var(--interactive-accent) 50%, transparent 50%),
linear-gradient(to bottom, var(--interactive-accent) 50%, transparent 50%),
linear-gradient(to right, var(--interactive-accent) 50%, transparent 50%),
linear-gradient(to bottom, var(--interactive-accent) 50%, transparent 50%);
}
&:focus,
@ -484,7 +468,7 @@
}
/* Transparent swatch fix */
input[type="color"].is-transparent {
input[type='color'].is-transparent {
background-color: transparent;
&::-webkit-color-swatch {
background-image:
@ -635,7 +619,7 @@
}
/* RTL Override */
[dir="rtl"] & {
[dir='rtl'] & {
.setting-item-control {
order: 1;
justify-content: flex-start;
@ -809,7 +793,7 @@
.setting-item-control {
flex: 1 1 50%;
padding-left: 10px;
input[type="text"],
input[type='text'],
textarea {
width: 100%;
}

View file

@ -6,7 +6,7 @@
.color-master-settings-tab {
/* --- LTR/RTL Localization Overrides --- */
&[dir="ltr"] {
&[dir='ltr'] {
direction: ltr !important;
.setting-item {
@ -52,7 +52,7 @@
}
}
&[dir="rtl"] {
&[dir='rtl'] {
.cm-search-input-icon {
left: auto;
right: 10px;
@ -370,7 +370,7 @@
}
/* Color Swatch Styling */
.setting-item-control input[type="color"] {
.setting-item-control input[type='color'] {
appearance: none;
-webkit-appearance: none;
width: 28px;
@ -553,7 +553,7 @@
}
}
&[dir="rtl"] {
&[dir='rtl'] {
.setting-item-control select,
.cm-search-small {
padding-right: 7px !important;
@ -567,7 +567,7 @@
}
/* Range Slider Styling */
input[type="range"] {
input[type='range'] {
-webkit-appearance: none;
appearance: none;
background: transparent;

View file

@ -1,4 +1,4 @@
@use "base";
@use "components";
@use "modals";
@use "settings";
@use 'base';
@use 'components';
@use 'modals';
@use 'settings';

View file

@ -1,4 +1,4 @@
export type CustomVarType = "color" | "size" | "text" | "number";
export type CustomVarType = 'color' | 'size' | 'text' | 'number';
export interface Snippet {
id: string;
@ -18,7 +18,7 @@ export interface NoticeRule {
export interface Profile {
vars: { [key: string]: string };
themeType: "auto" | "dark" | "light";
themeType: 'auto' | 'dark' | 'light';
snippets: Snippet[];
isCssProfile?: boolean;
customCss?: string;
@ -35,7 +35,7 @@ export interface Profile {
};
};
backgroundPath?: string;
backgroundType?: "image" | "video";
backgroundType?: 'image' | 'video';
videoOpacity?: number;
videoMuted?: boolean;
backgroundEnabled?: boolean;

View file

@ -1,14 +1,14 @@
import { Notice, Setting } from "obsidian";
import { DEFAULT_VARS, TEXT_TO_BG_MAP } from "../../constants";
import { t } from "../../i18n/strings";
import { Notice, Setting } from 'obsidian';
import { DEFAULT_VARS, TEXT_TO_BG_MAP } from '../../constants';
import { t } from '../../i18n/strings';
import {
flattenVars,
getAccessibilityRating,
getContrastRatio,
isIconizeEnabled,
} from "../../utils";
import { IconizeSettingsModal, NoticeRulesModal } from "../modals";
import type { ColorMasterSettingTab } from "../settingsTab";
} from '../../utils';
import { IconizeSettingsModal, NoticeRulesModal } from '../modals';
import type { ColorMasterSettingTab } from '../settingsTab';
export function drawColorPickers(
containerEl: HTMLElement,
@ -23,90 +23,79 @@ export function drawColorPickers(
let categoryKey: string;
const lowerCategory = category.toLowerCase();
if (lowerCategory === "interactive elements") {
categoryKey = "interactive";
} else if (lowerCategory === "ui elements") {
categoryKey = "ui";
} else if (lowerCategory === "graph view") {
categoryKey = "graph";
} else if (lowerCategory === "plugin integrations") {
categoryKey = "pluginintegrations";
if (lowerCategory === 'interactive elements') {
categoryKey = 'interactive';
} else if (lowerCategory === 'ui elements') {
categoryKey = 'ui';
} else if (lowerCategory === 'graph view') {
categoryKey = 'graph';
} else if (lowerCategory === 'plugin integrations') {
categoryKey = 'pluginintegrations';
} else {
categoryKey = lowerCategory.replace(/ /g, "");
categoryKey = lowerCategory.replace(/ /g, '');
}
const headingText = t(`categories.${categoryKey}`) || category;
const categoryContainerClasses = ["cm-category-container"];
const categoryContainerClasses = ['cm-category-container'];
const categoryContainer = containerEl.createDiv({
cls: categoryContainerClasses.join(" "),
cls: categoryContainerClasses.join(' '),
});
categoryContainer.dataset.category = category;
categoryContainer.createEl("h3", { text: headingText });
categoryContainer.createEl('h3', { text: headingText });
for (const [varName, originalDefaultValue] of Object.entries(vars)) {
const defaultValue = themeDefaults[varName] || "";
const defaultValue = themeDefaults[varName] || '';
const description = t(`colors.descriptions.${varName}`) || "";
const description = t(`colors.descriptions.${varName}`) || '';
const setting = new Setting(containerEl)
.setName(t(`colors.names.${varName}`) || varName)
.setDesc(description);
if (
categoryKey === "pluginintegrations" &&
varName === "--iconize-icon-color"
) {
if (categoryKey === 'pluginintegrations' && varName === '--iconize-icon-color') {
if (!isIconizeEnabled(plugin.app)) {
const badgeEl = setting.nameEl.createSpan({
text: t("options.badgeNotInstalled"),
cls: "cm-not-installed-badge",
text: t('options.badgeNotInstalled'),
cls: 'cm-not-installed-badge',
});
badgeEl.setAttr("aria-label", t("tooltips.iconizeNotInstalled"));
badgeEl.setAttr('aria-label', t('tooltips.iconizeNotInstalled'));
}
}
if (varName === "--iconize-icon-color") {
if (varName === '--iconize-icon-color') {
setting.addExtraButton((button) => {
button
.setIcon("settings")
.setTooltip(t("tooltips.iconizeSettings"))
.setIcon('settings')
.setTooltip(t('tooltips.iconizeSettings'))
.onClick(() => {
new IconizeSettingsModal(settingTab.app, plugin).open();
});
});
}
if (category === "Notices") {
if (category === 'Notices') {
setting.addExtraButton((button) => {
button.setIcon("settings").onClick(() => {
const ruleType = varName.includes("-text-") ? "text" : "background";
new NoticeRulesModal(
settingTab.app,
plugin,
settingTab,
ruleType,
).open();
button.setIcon('settings').onClick(() => {
const ruleType = varName.includes('-text-') ? 'text' : 'background';
new NoticeRulesModal(settingTab.app, plugin, settingTab, ruleType).open();
});
});
}
setting.settingEl.classList.add("cm-var-row");
setting.settingEl.classList.add('cm-var-row');
setting.settingEl.dataset.var = varName;
setting.settingEl.dataset.category = category;
setting.nameEl.classList.add("cm-var-name");
setting.nameEl.classList.add('cm-var-name');
const bgVarForTextColor =
TEXT_TO_BG_MAP[varName as keyof typeof TEXT_TO_BG_MAP];
const bgVarForTextColor = TEXT_TO_BG_MAP[varName as keyof typeof TEXT_TO_BG_MAP];
if (bgVarForTextColor) {
let textColor = activeProfileVars[varName] || defaultValue;
let bgColor =
activeProfileVars[bgVarForTextColor] ||
themeDefaults[bgVarForTextColor] ||
"#ffffff";
activeProfileVars[bgVarForTextColor] || themeDefaults[bgVarForTextColor] || '#ffffff';
if (varName === "--text-highlight-bg") {
if (varName === '--text-highlight-bg') {
[textColor, bgColor] = [bgColor, textColor];
}
@ -120,31 +109,24 @@ export function drawColorPickers(
checkerEl.setText(`${rating.text} (${rating.score})`);
}
const colorPicker = setting.controlEl.createEl("input", {
type: "color",
const colorPicker = setting.controlEl.createEl('input', {
type: 'color',
});
const textInput = setting.controlEl.createEl("input", {
type: "text",
cls: "color-master-text-input",
const textInput = setting.controlEl.createEl('input', {
type: 'text',
cls: 'color-master-text-input',
});
const isModified = Object.prototype.hasOwnProperty.call(
activeProfileVars,
varName,
);
const isModified = Object.prototype.hasOwnProperty.call(activeProfileVars, varName);
const initialValue = isModified
? activeProfileVars[varName]
: defaultValue;
const initialValue = isModified ? activeProfileVars[varName] : defaultValue;
setting.settingEl.classList.add(
isModified ? "is-modified" : "is-pristine",
);
setting.settingEl.classList.add(isModified ? 'is-modified' : 'is-pristine');
textInput.value = initialValue;
settingTab.updateColorPickerAppearance(textInput, colorPicker);
colorPicker.addEventListener("input", (e) => {
colorPicker.addEventListener('input', (e) => {
const newColor = (e.target as HTMLInputElement).value;
textInput.value = newColor;
if (plugin.settings.colorUpdateFPS > 0) {
@ -159,10 +141,7 @@ export function drawColorPickers(
// 2. Get profile and old color (for history)
const profile = plugin.settings.profiles[plugin.settings.activeProfile];
const oldColor = Object.prototype.hasOwnProperty.call(
activeProfileVars,
varName,
)
const oldColor = Object.prototype.hasOwnProperty.call(activeProfileVars, varName)
? activeProfileVars[varName]
: defaultValue;
@ -175,24 +154,20 @@ export function drawColorPickers(
}
// 4. Decide UI state (Pristine/Modified) and update data
if (
newColor.trim() === "" ||
newColor.toLowerCase() === defaultValue.toLowerCase()
) {
setting.settingEl.classList.remove("is-modified");
setting.settingEl.classList.add("is-pristine");
if (newColor.trim() === '' || newColor.toLowerCase() === defaultValue.toLowerCase()) {
setting.settingEl.classList.remove('is-modified');
setting.settingEl.classList.add('is-pristine');
delete activeProfileVars[varName];
} else {
// If it's a new and custom color
setting.settingEl.classList.remove("is-pristine");
setting.settingEl.classList.add("is-modified");
setting.settingEl.classList.remove('is-pristine');
setting.settingEl.classList.add('is-modified');
activeProfileVars[varName] = newColor;
}
// 5. Apply changes and save
const valueToApply =
newColor.trim() === "" ||
newColor.toLowerCase() === defaultValue.toLowerCase()
? ""
newColor.trim() === '' || newColor.toLowerCase() === defaultValue.toLowerCase()
? ''
: newColor;
if (plugin.settings.colorUpdateFPS === 0) {
@ -205,23 +180,23 @@ export function drawColorPickers(
plugin.pendingVarUpdates[varName] = valueToApply;
void plugin.saveSettings();
settingTab.updateAccessibilityCheckers();
setTimeout(() => settingTab.app.workspace.trigger("css-change"), 50);
setTimeout(() => settingTab.app.workspace.trigger('css-change'), 50);
};
colorPicker.addEventListener("change", (e) => {
colorPicker.addEventListener('change', (e) => {
handleFinalChange((e.target as HTMLInputElement).value);
});
textInput.addEventListener("change", (e) => {
textInput.addEventListener('change', (e) => {
handleFinalChange((e.target as HTMLInputElement).value);
});
setting.addExtraButton((button) => {
button
.setIcon("eraser")
.setTooltip(t("tooltips.setTransparent"))
.setIcon('eraser')
.setTooltip(t('tooltips.setTransparent'))
.onClick(() => {
const newColor = "transparent";
const newColor = 'transparent';
textInput.value = newColor;
handleFinalChange(newColor);
settingTab.updateColorPickerAppearance(textInput, colorPicker);
@ -230,15 +205,13 @@ export function drawColorPickers(
setting.addExtraButton((button) => {
button
.setIcon("reset")
.setTooltip(t("tooltips.undoChange"))
.setIcon('reset')
.setTooltip(t('tooltips.undoChange'))
.onClick(async () => {
const profile =
plugin.settings.profiles[plugin.settings.activeProfile];
const profile = plugin.settings.profiles[plugin.settings.activeProfile];
const history = profile.history?.[varName];
const themeDefaultValue =
themeDefaults[varName] || originalDefaultValue || "#ffffff";
const themeDefaultValue = themeDefaults[varName] || originalDefaultValue || '#ffffff';
if (history && history.length > 0) {
const restoredColor = history.shift();
@ -248,20 +221,20 @@ export function drawColorPickers(
textInput.value = safeRestoredColor;
settingTab.updateColorPickerAppearance(textInput, colorPicker);
if (safeRestoredColor.trim() === "") {
setting.settingEl.classList.remove("is-modified");
setting.settingEl.classList.add("is-pristine");
if (safeRestoredColor.trim() === '') {
setting.settingEl.classList.remove('is-modified');
setting.settingEl.classList.add('is-pristine');
delete activeProfileVars[varName];
} else {
setting.settingEl.classList.remove("is-pristine");
setting.settingEl.classList.add("is-modified");
setting.settingEl.classList.remove('is-pristine');
setting.settingEl.classList.add('is-modified');
}
await plugin.saveSettings();
settingTab.updateAccessibilityCheckers();
new Notice(t("notices.colorRestored", safeRestoredColor));
new Notice(t('notices.colorRestored', safeRestoredColor));
} else {
new Notice(t("notices.noColorHistory"));
new Notice(t('notices.noColorHistory'));
}
});
});
@ -279,49 +252,43 @@ export function drawColorPickers(
if (Object.keys(customVars).length > 0) {
const customVarsContainer = containerEl.createDiv({
cls: "cm-category-container",
cls: 'cm-category-container',
});
customVarsContainer.dataset.category = "Custom";
customVarsContainer.dataset.category = 'Custom';
customVarsContainer.createEl("h3", {
text: t("categories.custom"),
cls: "cm-section-heading",
customVarsContainer.createEl('h3', {
text: t('categories.custom'),
cls: 'cm-section-heading',
});
const activeProfile =
plugin.settings.profiles[plugin.settings.activeProfile];
const activeProfile = plugin.settings.profiles[plugin.settings.activeProfile];
for (const [varName, varValue] of Object.entries(customVars)) {
// Get metadata, falling back to defaults if not set
const meta = activeProfile.customVarMetadata?.[varName];
const varType = meta?.type || "color";
const varType = meta?.type || 'color';
const displayName = meta?.name || varName;
const displayDesc = meta?.desc || t("categories.customDesc");
const displayDesc = meta?.desc || t('categories.customDesc');
const isModified = varValue !== "";
const isModified = varValue !== '';
const setting = new Setting(customVarsContainer)
.setName(displayName)
.setDesc(displayDesc);
const setting = new Setting(customVarsContainer).setName(displayName).setDesc(displayDesc);
setting.settingEl.classList.add(
"cm-var-row",
isModified ? "is-modified" : "is-pristine",
);
setting.settingEl.classList.add('cm-var-row', isModified ? 'is-modified' : 'is-pristine');
setting.settingEl.dataset.var = varName;
setting.settingEl.dataset.category = "Custom";
setting.nameEl.classList.add("cm-var-name");
setting.settingEl.dataset.category = 'Custom';
setting.nameEl.classList.add('cm-var-name');
const handleCustomVarChange = (newValue: string) => {
activeProfile.vars[varName] = newValue;
if (newValue === "") {
setting.settingEl.classList.remove("is-modified");
setting.settingEl.classList.add("is-pristine");
if (newValue === '') {
setting.settingEl.classList.remove('is-modified');
setting.settingEl.classList.add('is-pristine');
} else {
setting.settingEl.classList.remove("is-pristine");
setting.settingEl.classList.add("is-modified");
setting.settingEl.classList.remove('is-pristine');
setting.settingEl.classList.add('is-modified');
}
if (plugin.settings.colorUpdateFPS > 0) {
@ -340,19 +307,19 @@ export function drawColorPickers(
};
switch (varType) {
case "color": {
const colorPicker = setting.controlEl.createEl("input", {
type: "color",
case 'color': {
const colorPicker = setting.controlEl.createEl('input', {
type: 'color',
});
const textInput = setting.controlEl.createEl("input", {
type: "text",
cls: "color-master-text-input",
const textInput = setting.controlEl.createEl('input', {
type: 'text',
cls: 'color-master-text-input',
});
textInput.value = varValue;
settingTab.updateColorPickerAppearance(textInput, colorPicker);
colorPicker.addEventListener("input", (e) => {
colorPicker.addEventListener('input', (e) => {
const newColor = (e.target as HTMLInputElement).value;
textInput.value = newColor;
if (plugin.settings.colorUpdateFPS > 0) {
@ -360,12 +327,12 @@ export function drawColorPickers(
}
});
colorPicker.addEventListener("change", (e) => {
colorPicker.addEventListener('change', (e) => {
handleCustomVarChange((e.target as HTMLInputElement).value);
settingTab.updateColorPickerAppearance(textInput, colorPicker);
});
textInput.addEventListener("change", (e) => {
textInput.addEventListener('change', (e) => {
const newColor = (e.target as HTMLInputElement).value;
handleCustomVarChange(newColor);
settingTab.updateColorPickerAppearance(textInput, colorPicker);
@ -373,10 +340,10 @@ export function drawColorPickers(
setting.addExtraButton((button) => {
button
.setIcon("eraser")
.setTooltip(t("tooltips.setTransparent"))
.setIcon('eraser')
.setTooltip(t('tooltips.setTransparent'))
.onClick(() => {
const newColor = "transparent";
const newColor = 'transparent';
textInput.value = newColor;
handleCustomVarChange(newColor);
settingTab.updateColorPickerAppearance(textInput, colorPicker);
@ -385,83 +352,80 @@ export function drawColorPickers(
break;
}
case "size": {
const sizeMatch = varValue.match(
/(-?\d*\.?\d+)\s*(px|em|rem|%|vw|vh|auto)?/,
);
case 'size': {
const sizeMatch = varValue.match(/(-?\d*\.?\d+)\s*(px|em|rem|%|vw|vh|auto)?/);
let numValue = sizeMatch?.[1] || "10";
let unitValue = sizeMatch?.[2] || "px";
let numValue = sizeMatch?.[1] || '10';
let unitValue = sizeMatch?.[2] || 'px';
if (varValue === "auto") {
numValue = "";
unitValue = "auto";
if (varValue === 'auto') {
numValue = '';
unitValue = 'auto';
}
const sizeInput = setting.controlEl.createEl("input", {
type: "number",
cls: "color-master-text-input",
const sizeInput = setting.controlEl.createEl('input', {
type: 'number',
cls: 'color-master-text-input',
});
sizeInput.value = numValue;
sizeInput.setCssProps({ width: "80px" });
sizeInput.setCssProps({ width: '80px' });
const unitDropdown = setting.controlEl.createEl("select", {
cls: "dropdown cm-search-small",
const unitDropdown = setting.controlEl.createEl('select', {
cls: 'dropdown cm-search-small',
});
const units = ["px", "em", "rem", "%", "vw", "vh", "auto"];
if (!units.includes(unitValue)) unitValue = "px";
const units = ['px', 'em', 'rem', '%', 'vw', 'vh', 'auto'];
if (!units.includes(unitValue)) unitValue = 'px';
units.forEach((unit) => {
unitDropdown.createEl("option", { text: unit, value: unit });
unitDropdown.createEl('option', { text: unit, value: unit });
});
unitDropdown.value = unitValue;
const updateSize = () => {
const newNum = sizeInput.value || "0";
const newNum = sizeInput.value || '0';
const newUnit = unitDropdown.value;
const newValue =
newUnit === "auto" ? "auto" : `${newNum}${newUnit}`;
sizeInput.disabled = newUnit === "auto";
const newValue = newUnit === 'auto' ? 'auto' : `${newNum}${newUnit}`;
sizeInput.disabled = newUnit === 'auto';
handleCustomVarChange(newValue);
};
sizeInput.addEventListener("change", updateSize);
unitDropdown.addEventListener("change", updateSize);
sizeInput.addEventListener('change', updateSize);
unitDropdown.addEventListener('change', updateSize);
sizeInput.disabled = unitValue === "auto";
sizeInput.disabled = unitValue === 'auto';
break;
}
case "text": {
setting.controlEl.classList.add("cm-full-width-control");
case 'text': {
setting.controlEl.classList.add('cm-full-width-control');
const textInputArea = setting.controlEl.createEl("input", {
type: "text",
cls: "cm-wide-text-input",
const textInputArea = setting.controlEl.createEl('input', {
type: 'text',
cls: 'cm-wide-text-input',
});
textInputArea.value = varValue;
textInputArea.placeholder = "Enter text value...";
textInputArea.placeholder = 'Enter text value...';
textInputArea.addEventListener("change", (e) => {
textInputArea.addEventListener('change', (e) => {
handleCustomVarChange((e.target as HTMLInputElement).value);
});
break;
}
case "number": {
const numInput = setting.controlEl.createEl("input", {
type: "number",
cls: "color-master-text-input",
case 'number': {
const numInput = setting.controlEl.createEl('input', {
type: 'number',
cls: 'color-master-text-input',
});
numInput.value = varValue;
numInput.setCssProps({ width: "100px" });
numInput.setCssProps({ width: '100px' });
numInput.addEventListener("change", (e) => {
numInput.addEventListener('change', (e) => {
handleCustomVarChange((e.target as HTMLInputElement).value);
});
@ -469,15 +433,15 @@ export function drawColorPickers(
}
default: {
setting.controlEl.classList.add("cm-full-width-control");
setting.controlEl.classList.add('cm-full-width-control');
const defaultInput = setting.controlEl.createEl("input", {
type: "text",
cls: "cm-wide-text-input",
const defaultInput = setting.controlEl.createEl('input', {
type: 'text',
cls: 'cm-wide-text-input',
});
defaultInput.value = varValue;
defaultInput.addEventListener("change", (e) => {
defaultInput.addEventListener('change', (e) => {
handleCustomVarChange((e.target as HTMLInputElement).value);
});
@ -487,8 +451,8 @@ export function drawColorPickers(
setting.addExtraButton((button) => {
button
.setIcon("trash")
.setTooltip(t("tooltips.deleteCustomVar"))
.setIcon('trash')
.setTooltip(t('tooltips.deleteCustomVar'))
.onClick(async () => {
delete activeProfile.vars[varName];
// Remove the property from the DOM

View file

@ -1,60 +1,48 @@
import { t } from "../../i18n/strings";
import { PasteCssModal, ProfileJsonImportModal } from "../modals";
import type { ColorMasterSettingTab } from "../settingsTab";
import { t } from '../../i18n/strings';
import { PasteCssModal, ProfileJsonImportModal } from '../modals';
import type { ColorMasterSettingTab } from '../settingsTab';
export function drawImportExport(
containerEl: HTMLElement,
settingTab: ColorMasterSettingTab,
) {
const actionsEl = containerEl.createDiv("cm-profile-actions");
export function drawImportExport(containerEl: HTMLElement, settingTab: ColorMasterSettingTab) {
const actionsEl = containerEl.createDiv('cm-profile-actions');
// --- 1. Output Actions (Export / Copy) ---
actionsEl
.createEl("button", {
text: t("buttons.exportFile"),
cls: "cm-profile-action-btn",
.createEl('button', {
text: t('buttons.exportFile'),
cls: 'cm-profile-action-btn',
})
.addEventListener("click", () => settingTab._exportProfileToFile());
.addEventListener('click', () => settingTab._exportProfileToFile());
actionsEl
.createEl("button", {
text: t("buttons.copyJson"),
cls: "cm-profile-action-btn",
.createEl('button', {
text: t('buttons.copyJson'),
cls: 'cm-profile-action-btn',
})
.addEventListener("click", () => {
.addEventListener('click', () => {
void settingTab._copyProfileToClipboard().catch((err) => {
console.error("Failed to copy profile to clipboard:", err);
console.error('Failed to copy profile to clipboard:', err);
});
});
// Spacer to push input actions to the right
actionsEl.createDiv({ cls: "cm-profile-action-spacer" });
actionsEl.createDiv({ cls: 'cm-profile-action-spacer' });
// --- 2. Input Actions (Import CSS / JSON) ---
const pasteCssBtn = actionsEl.createEl("button", {
text: t("buttons.importCss"),
cls: "cm-profile-action-btn cm-paste-css-btn",
const pasteCssBtn = actionsEl.createEl('button', {
text: t('buttons.importCss'),
cls: 'cm-profile-action-btn cm-paste-css-btn',
});
pasteCssBtn.addEventListener("click", () => {
new PasteCssModal(
settingTab.app,
settingTab.plugin,
settingTab,
null,
).open();
pasteCssBtn.addEventListener('click', () => {
new PasteCssModal(settingTab.app, settingTab.plugin, settingTab, null).open();
});
actionsEl
.createEl("button", {
text: t("buttons.importJson"),
cls: "cm-profile-action-btn mod-cta",
.createEl('button', {
text: t('buttons.importJson'),
cls: 'cm-profile-action-btn mod-cta',
})
.addEventListener("click", () =>
new ProfileJsonImportModal(
settingTab.app,
settingTab.plugin,
settingTab,
).open(),
.addEventListener('click', () =>
new ProfileJsonImportModal(settingTab.app, settingTab.plugin, settingTab).open(),
);
}

View file

@ -1,25 +1,20 @@
import { DEFAULT_VARS } from "../../constants";
import { t } from "../../i18n/strings";
import { flattenVars } from "../../utils";
import type { ColorMasterSettingTab } from "../settingsTab";
import { DEFAULT_VARS } from '../../constants';
import { t } from '../../i18n/strings';
import { flattenVars } from '../../utils';
import type { ColorMasterSettingTab } from '../settingsTab';
function createStatBar(
parentEl: HTMLElement,
label: string,
value: number,
max: number,
) {
const skillBox = parentEl.createDiv("cm-stat-box");
const header = skillBox.createDiv("cm-stat-header");
header.createEl("span", { cls: "title", text: label });
header.createEl("span", { cls: "value", text: String(value) });
const skillBar = skillBox.createDiv("skill-bar");
function createStatBar(parentEl: HTMLElement, label: string, value: number, max: number) {
const skillBox = parentEl.createDiv('cm-stat-box');
const header = skillBox.createDiv('cm-stat-header');
header.createEl('span', { cls: 'title', text: label });
header.createEl('span', { cls: 'value', text: String(value) });
const skillBar = skillBox.createDiv('skill-bar');
const percentage = Math.min(100, Math.round((value / max) * 100));
const skillPer = skillBar.createEl("span", {
cls: "skill-per cm-skill-gradient",
const skillPer = skillBar.createEl('span', {
cls: 'skill-per cm-skill-gradient',
});
skillPer.setCssProps({
"--skill-percentage": `${percentage}%`,
'--skill-percentage': `${percentage}%`,
});
}
@ -31,9 +26,7 @@ function calcSnippetsCount(settingTab: ColorMasterSettingTab): number {
const settings = settingTab.plugin.settings;
const profiles = settings.profiles || {};
let totalSnippets = settings.globalSnippets
? settings.globalSnippets.length
: 0;
let totalSnippets = settings.globalSnippets ? settings.globalSnippets.length : 0;
for (const profileName in profiles) {
const profile = profiles[profileName];
@ -47,9 +40,7 @@ function calcSnippetsCount(settingTab: ColorMasterSettingTab): number {
function calcVarsCount(settingTab: ColorMasterSettingTab): number {
const allVars = new Set(Object.keys(flattenVars(DEFAULT_VARS)));
const activeProfile =
settingTab.plugin.settings.profiles[
settingTab.plugin.settings.activeProfile
];
settingTab.plugin.settings.profiles[settingTab.plugin.settings.activeProfile];
if (activeProfile && activeProfile.vars) {
Object.keys(activeProfile.vars).forEach((varName) => allVars.add(varName));
}
@ -58,21 +49,18 @@ function calcVarsCount(settingTab: ColorMasterSettingTab): number {
function calcPluginIntegrations(): number {
try {
if (DEFAULT_VARS && DEFAULT_VARS["Plugin Integrations"]) {
return Object.keys(DEFAULT_VARS["Plugin Integrations"]).length;
if (DEFAULT_VARS && DEFAULT_VARS['Plugin Integrations']) {
return Object.keys(DEFAULT_VARS['Plugin Integrations']).length;
}
} catch (e) {
console.error("Color Master: Failed to calculate plugin integrations.", e);
console.error('Color Master: Failed to calculate plugin integrations.', e);
}
return 0;
}
export function drawLikePluginCard(
containerEl: HTMLElement,
settingTab: ColorMasterSettingTab,
) {
const likeCardEl = containerEl.createDiv("cm-like-card");
const bannerContainer = likeCardEl.createDiv("cm-banner-container");
export function drawLikePluginCard(containerEl: HTMLElement, settingTab: ColorMasterSettingTab) {
const likeCardEl = containerEl.createDiv('cm-like-card');
const bannerContainer = likeCardEl.createDiv('cm-banner-container');
const bannerSvgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="240" viewBox="0 0 1280 240" role="img" aria-label="Color Master banner" class="cm-banner-svg">
<defs>
@ -100,78 +88,64 @@ export function drawLikePluginCard(
</svg>`;
const parser = new DOMParser();
const svgDoc = parser.parseFromString(bannerSvgString, "image/svg+xml");
const svgDoc = parser.parseFromString(bannerSvgString, 'image/svg+xml');
const svgElement = svgDoc.documentElement;
bannerContainer.appendChild(svgElement);
const contentWrapper = likeCardEl.createDiv("cm-content-wrapper");
const contentWrapper = likeCardEl.createDiv('cm-content-wrapper');
const statsContainer = contentWrapper.createDiv("cm-like-stats");
const statsContainer = contentWrapper.createDiv('cm-like-stats');
const profilesCount = calcProfilesCount(settingTab);
const snippetsCount = calcSnippetsCount(settingTab);
const sinceInstalled =
settingTab.plugin.settings.installDate || new Date().toISOString();
const sinceInstalled = settingTab.plugin.settings.installDate || new Date().toISOString();
const days = Math.max(
1,
Math.floor(
(Date.now() - new Date(sinceInstalled).getTime()) / (1000 * 60 * 60 * 24),
),
Math.floor((Date.now() - new Date(sinceInstalled).getTime()) / (1000 * 60 * 60 * 24)),
);
createStatBar(
statsContainer,
t("likeCard.profilesStat", profilesCount, snippetsCount),
t('likeCard.profilesStat', profilesCount, snippetsCount),
profilesCount + snippetsCount,
50,
);
createStatBar(
statsContainer,
t("likeCard.colorsStat"),
t('likeCard.colorsStat'),
calcVarsCount(settingTab),
calcVarsCount(settingTab),
);
createStatBar(
statsContainer,
t("likeCard.integrationsStat"),
calcPluginIntegrations(),
5,
);
createStatBar(statsContainer, t("likeCard.daysStat"), days, 365);
createStatBar(statsContainer, t('likeCard.integrationsStat'), calcPluginIntegrations(), 5);
createStatBar(statsContainer, t('likeCard.daysStat'), days, 365);
const actions = contentWrapper.createDiv("cm-like-actions");
const starButtonWrapper = actions.createDiv({ cls: "codepen-button" });
starButtonWrapper.createEl("span", { text: t("likeCard.starButton") });
starButtonWrapper.addEventListener("click", () => {
window.open(
"https://github.com/YazanAmmar/obsidian-color-master",
"_blank",
);
const actions = contentWrapper.createDiv('cm-like-actions');
const starButtonWrapper = actions.createDiv({ cls: 'codepen-button' });
starButtonWrapper.createEl('span', { text: t('likeCard.starButton') });
starButtonWrapper.addEventListener('click', () => {
window.open('https://github.com/YazanAmmar/obsidian-color-master', '_blank');
});
const reportButtonWrapper = actions.createDiv({ cls: "codepen-button" });
reportButtonWrapper.createEl("span", { text: t("likeCard.issueButton") });
reportButtonWrapper.addEventListener("click", () => {
window.open(
"https://github.com/YazanAmmar/obsidian-color-master/issues",
"_blank",
);
const reportButtonWrapper = actions.createDiv({ cls: 'codepen-button' });
reportButtonWrapper.createEl('span', { text: t('likeCard.issueButton') });
reportButtonWrapper.addEventListener('click', () => {
window.open('https://github.com/YazanAmmar/obsidian-color-master/issues', '_blank');
});
const syncPromoContainer = actions.createDiv({ cls: "cm-promo-container" });
const syncPromoContainer = actions.createDiv({ cls: 'cm-promo-container' });
const syncButtonWrapper = syncPromoContainer.createDiv({
cls: "codepen-button",
cls: 'codepen-button',
});
syncButtonWrapper.createEl("span", { text: t("likeCard.syncButton") });
syncButtonWrapper.addEventListener("click", () => {
window.open("https://github.com/YazanAmmar/SyncEveryThing", "_blank");
syncButtonWrapper.createEl('span', { text: t('likeCard.syncButton') });
syncButtonWrapper.addEventListener('click', () => {
window.open('https://github.com/YazanAmmar/SyncEveryThing', '_blank');
});
const myGithubButtonWrapper = actions.createDiv({ cls: "codepen-button" });
myGithubButtonWrapper.createEl("span", {
text: t("likeCard.telegramButton"),
const myGithubButtonWrapper = actions.createDiv({ cls: 'codepen-button' });
myGithubButtonWrapper.createEl('span', {
text: t('likeCard.telegramButton'),
});
myGithubButtonWrapper.addEventListener("click", () => {
window.open("https://t.me/ObsidianColorMaster", "_blank");
myGithubButtonWrapper.addEventListener('click', () => {
window.open('https://t.me/ObsidianColorMaster', '_blank');
});
}

View file

@ -1,6 +1,6 @@
import { Setting, Notice, setIcon } from "obsidian";
import { t } from "../../i18n/strings";
import type { ColorMasterSettingTab } from "../settingsTab";
import { Setting, Notice, setIcon } from 'obsidian';
import { t } from '../../i18n/strings';
import type { ColorMasterSettingTab } from '../settingsTab';
import {
CustomVariableMetaModal,
ConfirmationModal,
@ -8,25 +8,20 @@ import {
AddBackgroundModal,
ProfileImageBrowserModal,
AdvancedResetModal,
} from "../modals";
} from '../modals';
export function drawOptionsSection(
containerEl: HTMLElement,
settingTab: ColorMasterSettingTab,
) {
export function drawOptionsSection(containerEl: HTMLElement, settingTab: ColorMasterSettingTab) {
const plugin = settingTab.plugin;
const activeProfile = plugin.settings.profiles[plugin.settings.activeProfile];
containerEl.createEl("h3", { text: t("options.heading") });
containerEl.createEl("hr");
const advancedSettingsGrid = containerEl.createDiv(
"cm-advanced-settings-grid",
);
containerEl.createEl('h3', { text: t('options.heading') });
containerEl.createEl('hr');
const advancedSettingsGrid = containerEl.createDiv('cm-advanced-settings-grid');
// --- Live Update FPS ---
const fpsSetting = new Setting(advancedSettingsGrid)
.setName(t("options.liveUpdateName"))
.setDesc(t("options.liveUpdateDesc"))
.setName(t('options.liveUpdateName'))
.setDesc(t('options.liveUpdateDesc'))
.addSlider((slider) => {
slider
.setLimits(0, 60, 1)
@ -36,90 +31,83 @@ export function drawOptionsSection(
plugin.settings.colorUpdateFPS = value;
await plugin.saveSettings();
plugin.restartColorUpdateLoop();
new Notice(t("notices.fpsUpdated", value));
new Notice(t('notices.fpsUpdated', value));
});
});
fpsSetting.nameEl.appendChild(fpsSetting.controlEl);
fpsSetting.settingEl.classList.add("cm-card-with-header-control");
fpsSetting.settingEl.classList.add('cm-card-with-header-control');
// --- Add Custom Variable ---
const customVarSetting = new Setting(advancedSettingsGrid)
.setName(t("options.addCustomVarName"))
.setDesc(t("options.addCustomVarDesc"))
.setName(t('options.addCustomVarName'))
.setDesc(t('options.addCustomVarDesc'))
.addButton((button) => {
button
.setButtonText(t("options.addNewVarButton"))
.setClass("cm-add-variable-button")
.setButtonText(t('options.addNewVarButton'))
.setClass('cm-add-variable-button')
.onClick(() => {
new CustomVariableMetaModal(
settingTab.app,
plugin,
settingTab,
(result) => {
void (async () => {
if (!activeProfile.customVarMetadata) {
activeProfile.customVarMetadata = {};
}
new CustomVariableMetaModal(settingTab.app, plugin, settingTab, (result) => {
void (async () => {
if (!activeProfile.customVarMetadata) {
activeProfile.customVarMetadata = {};
}
activeProfile.vars[result.varName] = result.varValue;
activeProfile.customVarMetadata[result.varName] = {
name: result.displayName,
desc: result.description,
type: result.varType,
};
activeProfile.vars[result.varName] = result.varValue;
activeProfile.customVarMetadata[result.varName] = {
name: result.displayName,
desc: result.description,
type: result.varType,
};
await plugin.saveSettings();
new Notice(t("notices.varAdded", result.varName));
settingTab.display();
})().catch((err) => {
console.error("Failed to add custom variable:", err);
});
},
).open();
await plugin.saveSettings();
new Notice(t('notices.varAdded', result.varName));
settingTab.display();
})().catch((err) => {
console.error('Failed to add custom variable:', err);
});
}).open();
});
});
customVarSetting.nameEl.appendChild(customVarSetting.controlEl);
customVarSetting.settingEl.classList.add("cm-card-with-header-control");
customVarSetting.settingEl.classList.add('cm-card-with-header-control');
// --- Reset Plugin Settings ---
const resetSetting = new Setting(advancedSettingsGrid)
.setName(t("options.resetPluginName"))
.setDesc(t("options.resetPluginDesc"))
.setName(t('options.resetPluginName'))
.setDesc(t('options.resetPluginDesc'))
.addButton((button) => {
button
.setButtonText(t("options.resetPluginButton"))
.setButtonText(t('options.resetPluginButton'))
.setWarning()
.onClick(() => {
new AdvancedResetModal(settingTab.app, plugin).open();
});
setIcon(button.buttonEl, "database-backup");
button.buttonEl.classList.add("cm-reset-plugin-icon-button");
setIcon(button.buttonEl, 'database-backup');
button.buttonEl.classList.add('cm-reset-plugin-icon-button');
});
resetSetting.nameEl.appendChild(resetSetting.controlEl);
resetSetting.settingEl.classList.add("cm-card-with-header-control");
const resetButtonText = resetSetting.controlEl.querySelector(
".setting-editor-button",
);
resetSetting.settingEl.classList.add('cm-card-with-header-control');
const resetButtonText = resetSetting.controlEl.querySelector('.setting-editor-button');
if (resetButtonText) {
resetButtonText.textContent = "";
resetButtonText.textContent = '';
}
// --- Set Background Image ---
const backgroundSetting = new Setting(advancedSettingsGrid)
.setName(t("options.backgroundName"))
.setDesc(t("options.backgroundDesc"));
.setName(t('options.backgroundName'))
.setDesc(t('options.backgroundDesc'));
backgroundSetting.nameEl.appendChild(backgroundSetting.controlEl);
backgroundSetting.settingEl.classList.add("cm-card-with-header-control");
backgroundSetting.settingEl.classList.add('cm-card-with-header-control');
backgroundSetting
.addButton((button) => {
// Add/Choose Button
button
.setIcon("plus")
.setTooltip(t("tooltips.addBg"))
.setClass("cm-control-icon-button")
.setIcon('plus')
.setTooltip(t('tooltips.addBg'))
.setClass('cm-control-icon-button')
.onClick(() => {
// Pass the settingTab instance to the modal
new AddBackgroundModal(settingTab.app, plugin, settingTab).open();
@ -128,73 +116,64 @@ export function drawOptionsSection(
.addButton((button) => {
// Browse Button
button
.setIcon("package-search")
.setTooltip(t("tooltips.browseBg"))
.setClass("cm-control-icon-button")
.setIcon('package-search')
.setTooltip(t('tooltips.browseBg'))
.setClass('cm-control-icon-button')
.onClick(() => {
// Directly open the browser modal
new ProfileImageBrowserModal(
settingTab.app,
plugin,
settingTab,
() => {},
).open();
new ProfileImageBrowserModal(settingTab.app, plugin, settingTab, () => {}).open();
});
})
.addButton((button) => {
// Remove Button
button
.setIcon("trash")
.setTooltip(t("tooltips.removeBg"))
.setClass("cm-control-icon-button")
.setIcon('trash')
.setTooltip(t('tooltips.removeBg'))
.setClass('cm-control-icon-button')
.onClick(() => {
const profile =
plugin.settings.profiles[plugin.settings.activeProfile];
const profile = plugin.settings.profiles[plugin.settings.activeProfile];
const imagePathToDelete = profile?.backgroundPath;
if (!imagePathToDelete) {
new Notice(t("notices.noBgToRemove"));
new Notice(t('notices.noBgToRemove'));
return;
}
const profilesUsingImage: string[] = [];
for (const profileName in plugin.settings.profiles) {
if (
plugin.settings.profiles[profileName].backgroundPath ===
imagePathToDelete
) {
if (plugin.settings.profiles[profileName].backgroundPath === imagePathToDelete) {
profilesUsingImage.push(profileName);
}
}
const messageFragment = new DocumentFragment();
messageFragment.append(t("modals.confirmation.deleteGlobalBgDesc"));
messageFragment.append(t('modals.confirmation.deleteGlobalBgDesc'));
if (profilesUsingImage.length > 0) {
const profileListEl = messageFragment.createEl("ul", {
cls: "cm-profile-list-modal",
const profileListEl = messageFragment.createEl('ul', {
cls: 'cm-profile-list-modal',
});
profilesUsingImage.forEach((name) => {
profileListEl.createEl("li").createEl("strong", { text: name });
profileListEl.createEl('li').createEl('strong', { text: name });
});
}
new ConfirmationModal(
settingTab.app,
plugin,
t("modals.confirmation.deleteGlobalBgTitle"),
t('modals.confirmation.deleteGlobalBgTitle'),
messageFragment,
() => {
void (async () => {
await plugin.removeBackgroundMediaByPath(imagePathToDelete);
new Notice(t("notices.bgDeleted"));
new Notice(t('notices.bgDeleted'));
})().catch((err) => {
console.error("Failed to delete background image:", err);
console.error('Failed to delete background image:', err);
});
},
{
buttonText: t("buttons.deleteAnyway"),
buttonClass: "mod-warning",
buttonText: t('buttons.deleteAnyway'),
buttonClass: 'mod-warning',
},
).open();
});
@ -202,9 +181,9 @@ export function drawOptionsSection(
.addButton((button) => {
// Settings Button
button
.setIcon("settings")
.setTooltip(t("tooltips.bgSettings"))
.setClass("cm-control-icon-button")
.setIcon('settings')
.setTooltip(t('tooltips.bgSettings'))
.setClass('cm-control-icon-button')
.onClick(() => {
new BackgroundImageSettingsModal(settingTab.app, plugin).open();
});

View file

@ -1,7 +1,7 @@
import { App, Notice, setIcon, Setting } from "obsidian";
import { t } from "../../i18n/strings";
import { ConfirmationModal, NewProfileModal, PasteCssModal } from "../modals";
import type { ColorMasterSettingTab } from "../settingsTab";
import { App, Notice, setIcon, Setting } from 'obsidian';
import { t } from '../../i18n/strings';
import { ConfirmationModal, NewProfileModal, PasteCssModal } from '../modals';
import type { ColorMasterSettingTab } from '../settingsTab';
interface AppWithCustomCss extends App {
customCss: {
@ -9,29 +9,26 @@ interface AppWithCustomCss extends App {
};
}
export function drawProfileManager(
containerEl: HTMLElement,
settingTab: ColorMasterSettingTab,
) {
export function drawProfileManager(containerEl: HTMLElement, settingTab: ColorMasterSettingTab) {
const plugin = settingTab.plugin;
containerEl.createEl("h3", {
text: t("profileManager.heading"),
cls: "cm-section-heading",
containerEl.createEl('h3', {
text: t('profileManager.heading'),
cls: 'cm-section-heading',
});
const activeProfileName = plugin.settings.activeProfile;
const activeProfile = plugin.settings.profiles[activeProfileName];
const isCssProfile = activeProfile?.isCssProfile;
const profileSetting = new Setting(containerEl)
.setName(t("profileManager.activeProfile"))
.setDesc(t("profileManager.activeProfileDesc"))
.setClass("cm-profile-manager-controls")
.setName(t('profileManager.activeProfile'))
.setDesc(t('profileManager.activeProfileDesc'))
.setClass('cm-profile-manager-controls')
.addDropdown((dropdown) => {
for (const profileName in plugin.settings.profiles) {
let displayName = profileName;
if (plugin.settings.profiles[profileName]?.isCssProfile) {
displayName += " (CSS)";
displayName += ' (CSS)';
}
dropdown.addOption(profileName, displayName);
}
@ -46,33 +43,26 @@ export function drawProfileManager(
})
.addButton((button) => {
const activeProfile =
plugin.settings.profiles[plugin.settings.activeProfile];
const activeProfile = plugin.settings.profiles[plugin.settings.activeProfile];
if (!activeProfile) {
button.buttonEl.hide();
return;
}
button.buttonEl.classList.add("cm-control-button");
button.buttonEl.classList.add('cm-control-button');
// Helper function to update the button's appearance
const updateButtonAppearance = (themeType: "auto" | "dark" | "light") => {
const updateButtonAppearance = (themeType: 'auto' | 'dark' | 'light') => {
switch (themeType) {
case "light":
button
.setIcon("sun")
.setTooltip(t("profileManager.tooltipThemeLight"));
case 'light':
button.setIcon('sun').setTooltip(t('profileManager.tooltipThemeLight'));
break;
case "dark":
button
.setIcon("moon")
.setTooltip(t("profileManager.tooltipThemeDark"));
case 'dark':
button.setIcon('moon').setTooltip(t('profileManager.tooltipThemeDark'));
break;
case "auto":
case 'auto':
default:
button
.setIcon("sun-moon")
.setTooltip(t("profileManager.tooltipThemeAuto"));
button.setIcon('sun-moon').setTooltip(t('profileManager.tooltipThemeAuto'));
break;
}
};
@ -81,20 +71,20 @@ export function drawProfileManager(
updateButtonAppearance(activeProfile.themeType);
// Add click logic (Simplified - No Animation)
button.buttonEl.addEventListener("click", () => {
button.buttonEl.addEventListener('click', () => {
void (async () => {
const currentType = activeProfile.themeType;
let nextType: "auto" | "dark" | "light";
let nextType: 'auto' | 'dark' | 'light';
if (currentType === "light") {
nextType = "dark";
new Notice(t("notices.themeSwitchedDark"));
} else if (currentType === "dark") {
nextType = "auto";
new Notice(t("notices.themeSwitchedAuto"));
if (currentType === 'light') {
nextType = 'dark';
new Notice(t('notices.themeSwitchedDark'));
} else if (currentType === 'dark') {
nextType = 'auto';
new Notice(t('notices.themeSwitchedAuto'));
} else {
nextType = "light";
new Notice(t("notices.themeSwitchedLight"));
nextType = 'light';
new Notice(t('notices.themeSwitchedLight'));
}
activeProfile.themeType = nextType;
@ -102,7 +92,7 @@ export function drawProfileManager(
await plugin.saveSettings();
})().catch((err) => {
console.error("Failed to switch theme type:", err);
console.error('Failed to switch theme type:', err);
});
});
})
@ -110,78 +100,73 @@ export function drawProfileManager(
.addButton((button) => {
if (isCssProfile && activeProfile) {
button
.setIcon("pencil")
.setTooltip(t("tooltips.editCssProfile"))
.setIcon('pencil')
.setTooltip(t('tooltips.editCssProfile'))
.onClick(() => {
const profileData = {
name: activeProfileName,
css: activeProfile.customCss || "",
css: activeProfile.customCss || '',
isProfile: true,
};
new PasteCssModal(
settingTab.app,
plugin,
settingTab,
profileData,
).open();
new PasteCssModal(settingTab.app, plugin, settingTab, profileData).open();
});
} else {
button.buttonEl.hide();
}
button.buttonEl.classList.add("cm-control-button");
button.buttonEl.classList.add('cm-control-button');
})
.addButton((button) => {
settingTab.pinBtn = button;
button
.setIcon("pin")
.setTooltip(t("tooltips.pinSnapshot"))
.setIcon('pin')
.setTooltip(t('tooltips.pinSnapshot'))
.onClick(async () => {
await plugin.pinProfileSnapshot(plugin.settings.activeProfile);
new Notice(t("notices.profilePinned"));
new Notice(t('notices.profilePinned'));
settingTab._updatePinButtons();
plugin.settings.profiles[plugin.settings.activeProfile].vars = {};
plugin.pendingVarUpdates = {};
settingTab.display();
});
button.buttonEl.classList.add("cm-control-button");
button.buttonEl.classList.add('cm-control-button');
})
.addButton((button) => {
settingTab.resetPinBtn = button;
button
.setIcon("history")
.setTooltip(t("tooltips.resetToPinned"))
.setIcon('history')
.setTooltip(t('tooltips.resetToPinned'))
.onClick(() => {
const name = plugin.settings.activeProfile;
new ConfirmationModal(
settingTab.app,
plugin,
t("modals.confirmation.resetProfileTitle"),
t("modals.confirmation.resetProfileDesc"),
t('modals.confirmation.resetProfileTitle'),
t('modals.confirmation.resetProfileDesc'),
() => {
void (async () => {
await plugin.resetProfileToPinned(name);
new Notice(t("notices.profileReset"));
new Notice(t('notices.profileReset'));
settingTab.display();
})().catch((err) => {
console.error("Failed to reset profile:", err);
console.error('Failed to reset profile:', err);
});
},
{ buttonText: t("buttons.reset"), buttonClass: "mod-cta" },
{ buttonText: t('buttons.reset'), buttonClass: 'mod-cta' },
).open();
});
button.buttonEl.classList.add("cm-control-button");
button.buttonEl.classList.add('cm-control-button');
})
.addButton((button) => {
button.setButtonText(t("buttons.new")).onClick(() => {
button.setButtonText(t('buttons.new')).onClick(() => {
new NewProfileModal(settingTab.app, plugin, (result) => {
void (async () => {
if (!result || !result.name) return;
if (plugin.settings.profiles[result.name]) {
new Notice(t("notices.profileNameExists", result.name));
new Notice(t('notices.profileNameExists', result.name));
return;
}
@ -190,10 +175,9 @@ export function drawProfileManager(
const themeType = result.themeType;
let finalThemeType = themeType;
if (themeType === "auto") {
const isCurrentlyDark =
document.body.classList.contains("theme-dark");
finalThemeType = isCurrentlyDark ? "dark" : "light";
if (themeType === 'auto') {
const isCurrentlyDark = document.body.classList.contains('theme-dark');
finalThemeType = isCurrentlyDark ? 'dark' : 'light';
}
plugin.settings.profiles[result.name] = {
@ -209,57 +193,49 @@ export function drawProfileManager(
await plugin.saveSettings();
settingTab.display();
})().catch((err) => {
console.error("Failed to create new profile:", err);
console.error('Failed to create new profile:', err);
});
}).open();
});
button.buttonEl.classList.add("cm-control-button");
button.buttonEl.classList.add('cm-control-button');
})
.addButton((button) => {
button.setButtonText(t("buttons.delete")).onClick(() => {
button.setButtonText(t('buttons.delete')).onClick(() => {
if (Object.keys(plugin.settings.profiles).length <= 1) {
new Notice(t("notices.cannotDeleteLastProfile"));
new Notice(t('notices.cannotDeleteLastProfile'));
return;
}
const message = t(
"modals.deleteProfile.confirmation",
plugin.settings.activeProfile,
);
const message = t('modals.deleteProfile.confirmation', plugin.settings.activeProfile);
new ConfirmationModal(
settingTab.app,
plugin,
t("modals.deleteProfile.title"),
t('modals.deleteProfile.title'),
message,
() => {
plugin.removeInjectedCustomCss();
delete plugin.settings.profiles[plugin.settings.activeProfile];
plugin.settings.activeProfile = Object.keys(
plugin.settings.profiles,
)[0];
plugin.settings.activeProfile = Object.keys(plugin.settings.profiles)[0];
plugin.pendingVarUpdates = {};
void plugin.saveSettings();
settingTab.display();
new Notice(t("notices.profileDeleted"));
new Notice(t('notices.profileDeleted'));
},
).open();
});
button.buttonEl.classList.add("cm-control-button");
button.buttonEl.classList.add('cm-control-button');
});
const currentTheme = (plugin.app as AppWithCustomCss).customCss.theme;
if (currentTheme) {
const iconEl = profileSetting.nameEl.createSpan({
cls: "cm-theme-warning-icon",
cls: 'cm-theme-warning-icon',
});
setIcon(iconEl, "alert-triangle");
iconEl.setAttr(
"aria-label",
t("settings.themeWarningTooltip", currentTheme),
);
setIcon(iconEl, 'alert-triangle');
iconEl.setAttr('aria-label', t('settings.themeWarningTooltip', currentTheme));
}
profileSetting.settingEl.classList.add("cm-active-profile-controls");
profileSetting.settingEl.classList.add('cm-active-profile-controls');
settingTab._updatePinButtons();
}

View file

@ -1,37 +1,28 @@
import {
ButtonComponent,
Notice,
Setting,
ToggleComponent,
setIcon,
} from "obsidian";
import { t } from "../../i18n/strings";
import type { Snippet } from "../../types";
import { ConfirmationModal, SnippetCssModal } from "../modals";
import type { ColorMasterSettingTab } from "../settingsTab";
import Sortable from "sortablejs";
import { ButtonComponent, Notice, Setting, ToggleComponent, setIcon } from 'obsidian';
import { t } from '../../i18n/strings';
import type { Snippet } from '../../types';
import { ConfirmationModal, SnippetCssModal } from '../modals';
import type { ColorMasterSettingTab } from '../settingsTab';
import Sortable from 'sortablejs';
function initSnippetDrag(
containerEl: HTMLElement,
settingTab: ColorMasterSettingTab,
) {
function initSnippetDrag(containerEl: HTMLElement, settingTab: ColorMasterSettingTab) {
const plugin = settingTab.plugin;
if (settingTab.snippetSortable) {
settingTab.snippetSortable.destroy();
}
if (!Sortable) {
console.warn("Color Master: SortableJS library not found.");
console.warn('Color Master: SortableJS library not found.');
return;
}
const isLocked = (plugin.settings as unknown).snippetsLocked || false;
settingTab.snippetSortable = new Sortable(containerEl, {
handle: ".cm-snippet-drag-handle",
handle: '.cm-snippet-drag-handle',
animation: 160,
ghostClass: "cm-snippet-ghost",
dragClass: "cm-snippet-dragged",
ghostClass: 'cm-snippet-ghost',
dragClass: 'cm-snippet-dragged',
disabled: isLocked, // Disable dragging via Sortable API
onEnd: (evt: unknown) => {
@ -44,14 +35,13 @@ function initSnippetDrag(
const globalSnippets = plugin.settings.globalSnippets || [];
const profileSnippets =
plugin.settings.profiles[plugin.settings.activeProfile].snippets ||
[];
plugin.settings.profiles[plugin.settings.activeProfile].snippets || [];
const numGlobal = globalSnippets.length;
if (oldIndex < numGlobal) {
if (newIndex >= numGlobal) {
settingTab.display();
new Notice(t("notices.snippetScopeMove"));
new Notice(t('notices.snippetScopeMove'));
return;
}
const [movedItem] = globalSnippets.splice(oldIndex, 1);
@ -59,7 +49,7 @@ function initSnippetDrag(
} else {
if (newIndex < numGlobal) {
settingTab.display();
new Notice(t("notices.snippetScopeMove"));
new Notice(t('notices.snippetScopeMove'));
return;
}
@ -73,59 +63,48 @@ function initSnippetDrag(
await plugin.saveSettings();
settingTab.display();
})().catch((err) => {
console.error("Failed to reorder snippets:", err);
console.error('Failed to reorder snippets:', err);
});
},
});
}
export function drawCssSnippetsUI(
containerEl: HTMLElement,
settingTab: ColorMasterSettingTab,
) {
export function drawCssSnippetsUI(containerEl: HTMLElement, settingTab: ColorMasterSettingTab) {
const plugin = settingTab.plugin;
const headerContainer = containerEl.createDiv({
cls: "cm-snippets-header",
cls: 'cm-snippets-header',
});
const isLocked = (plugin.settings as unknown).snippetsLocked || false;
headerContainer.createEl("h3", { text: t("snippets.heading") });
headerContainer.createEl('h3', { text: t('snippets.heading') });
const controlsContainer = new Setting(headerContainer).setClass(
"cm-snippets-add-button",
);
const controlsContainer = new Setting(headerContainer).setClass('cm-snippets-add-button');
// --- Lock Button ---
controlsContainer.addExtraButton((btn) => {
btn
.setIcon(isLocked ? "lock" : "lock-open")
.setTooltip(
isLocked ? t("tooltips.unlockSnippets") : t("tooltips.lockSnippets"),
)
.setIcon(isLocked ? 'lock' : 'lock-open')
.setTooltip(isLocked ? t('tooltips.unlockSnippets') : t('tooltips.lockSnippets'))
.onClick(async () => {
(plugin.settings as unknown).snippetsLocked = !isLocked;
await plugin.saveSettings();
settingTab.display();
new Notice(
isLocked
? t("notices.snippetsUnlocked")
: t("notices.snippetsLocked"),
);
new Notice(isLocked ? t('notices.snippetsUnlocked') : t('notices.snippetsLocked'));
});
btn.extraSettingsEl.classList.add("cm-snippet-lock-btn");
btn.extraSettingsEl.classList.add('cm-snippet-lock-btn');
if (isLocked) {
btn.extraSettingsEl.classList.add("is-locked");
btn.extraSettingsEl.classList.add('is-locked');
}
});
controlsContainer.addButton((button) => {
button
.setButtonText(t("snippets.createButton"))
.setButtonText(t('snippets.createButton'))
.setCta()
.onClick(() => {
new SnippetCssModal(settingTab.app, plugin, settingTab, null).open();
@ -136,118 +115,109 @@ export function drawCssSnippetsUI(
if (!activeProfile) return;
const globalSnippets = plugin.settings.globalSnippets || [];
const profileSnippets = Array.isArray(activeProfile.snippets)
? activeProfile.snippets
: [];
const profileSnippets = Array.isArray(activeProfile.snippets) ? activeProfile.snippets : [];
const snippetsContainer = containerEl.createDiv({
cls: "cm-snippets-list-container",
cls: 'cm-snippets-list-container',
});
if (globalSnippets.length === 0 && profileSnippets.length === 0) {
const emptyState = snippetsContainer.createDiv("cm-snippets-empty-state");
emptyState.setText(t("snippets.noSnippetsDesc"));
const emptyState = snippetsContainer.createDiv('cm-snippets-empty-state');
emptyState.setText(t('snippets.noSnippetsDesc'));
return;
}
const renderSnippet = (
snippet: Snippet,
index: number,
isGlobal: boolean,
) => {
const renderSnippet = (snippet: Snippet, index: number, isGlobal: boolean) => {
const snippetEl = snippetsContainer.createDiv({
cls: "setting-item cm-snippet-item",
cls: 'setting-item cm-snippet-item',
});
const dragContainer = snippetEl.createDiv({
cls: "cm-snippet-drag-container",
cls: 'cm-snippet-drag-container',
});
const handle = dragContainer.createDiv({
cls: "cm-snippet-drag-handle",
cls: 'cm-snippet-drag-handle',
});
setIcon(handle, "grip-vertical");
setIcon(handle, 'grip-vertical');
if (isLocked) {
handle.classList.add("is-locked");
handle.classList.add('is-locked');
}
dragContainer.createEl("span", {
cls: "cm-snippet-divider",
text: "|",
dragContainer.createEl('span', {
cls: 'cm-snippet-divider',
text: '|',
});
dragContainer.createDiv({
cls: "cm-snippet-order-number",
cls: 'cm-snippet-order-number',
text: `${index + 1}`,
});
const infoEl = snippetEl.createDiv("setting-item-info");
const infoEl = snippetEl.createDiv('setting-item-info');
const nameEl = infoEl.createDiv({
cls: "setting-item-name",
cls: 'setting-item-name',
});
nameEl.createSpan({ text: snippet.name });
if (isGlobal) {
nameEl.createSpan({
text: t("snippets.global"),
cls: "cm-snippet-global-badge",
text: t('snippets.global'),
cls: 'cm-snippet-global-badge',
});
}
const controlEl = snippetEl.createDiv("setting-item-control");
const controlEl = snippetEl.createDiv('setting-item-control');
new ToggleComponent(controlEl)
.setValue(snippet.enabled)
.onChange(async (value) => {
snippet.enabled = value;
await plugin.saveSettings();
});
new ToggleComponent(controlEl).setValue(snippet.enabled).onChange(async (value) => {
snippet.enabled = value;
await plugin.saveSettings();
});
new ButtonComponent(controlEl)
.setIcon("pencil")
.setTooltip(t("tooltips.editSnippet"))
.setIcon('pencil')
.setTooltip(t('tooltips.editSnippet'))
.onClick(() => {
new SnippetCssModal(settingTab.app, plugin, settingTab, snippet).open();
});
new ButtonComponent(controlEl)
.setIcon("copy")
.setTooltip(t("tooltips.copySnippetCss"))
.setIcon('copy')
.setTooltip(t('tooltips.copySnippetCss'))
.onClick(async (evt) => {
if (!snippet.css) {
new Notice(t("notices.snippetEmpty"));
new Notice(t('notices.snippetEmpty'));
return;
}
await navigator.clipboard.writeText(snippet.css);
new Notice(t("notices.snippetCssCopied"));
new Notice(t('notices.snippetCssCopied'));
const buttonEl = evt.currentTarget as HTMLElement;
if (!buttonEl) return;
setIcon(buttonEl, "check");
buttonEl.classList.add("is-success");
setIcon(buttonEl, 'check');
buttonEl.classList.add('is-success');
(buttonEl as unknown).disabled = true;
setTimeout(() => {
buttonEl.classList.remove("is-success");
setIcon(buttonEl, "copy");
buttonEl.classList.remove('is-success');
setIcon(buttonEl, 'copy');
(buttonEl as unknown).disabled = false;
}, 1000);
});
new ButtonComponent(controlEl)
.setIcon("trash")
.setTooltip(t("tooltips.deleteSnippet"))
.setIcon('trash')
.setTooltip(t('tooltips.deleteSnippet'))
.onClick(() => {
new ConfirmationModal(
settingTab.app,
plugin,
t("modals.confirmation.deleteSnippetTitle", snippet.name),
t("modals.confirmation.deleteSnippetDesc"),
t('modals.confirmation.deleteSnippetTitle', snippet.name),
t('modals.confirmation.deleteSnippetDesc'),
() => {
void (async () => {
const list = isGlobal
? plugin.settings.globalSnippets
: plugin.settings.profiles[plugin.settings.activeProfile]
.snippets;
: plugin.settings.profiles[plugin.settings.activeProfile].snippets;
const snippetIndex = list.findIndex((s) => s.id === snippet.id);
if (snippetIndex > -1) {
@ -256,18 +226,16 @@ export function drawCssSnippetsUI(
await plugin.saveSettings();
settingTab.display();
new Notice(t("notices.snippetDeleted"));
new Notice(t('notices.snippetDeleted'));
})().catch((err) => {
console.error("Failed to delete snippet:", err);
console.error('Failed to delete snippet:', err);
});
},
).open();
});
};
globalSnippets.forEach((snippet, index) =>
renderSnippet(snippet, index, true),
);
globalSnippets.forEach((snippet, index) => renderSnippet(snippet, index, true));
profileSnippets.forEach((snippet, index) =>
renderSnippet(snippet, index + globalSnippets.length, false),

File diff suppressed because it is too large Load diff

View file

@ -7,32 +7,27 @@ import {
Setting,
setIcon,
SearchComponent,
} from "obsidian";
import { DEFAULT_VARS, TEXT_TO_BG_MAP } from "../constants";
import { t } from "../i18n/strings";
import type ColorMaster from "../main";
import {
flattenVars,
getAccessibilityRating,
getContrastRatio,
debounce,
} from "../utils";
import { drawColorPickers } from "./components/color-pickers";
import { drawImportExport } from "./components/import-export";
import { drawLikePluginCard } from "./components/like-plugin-card";
import { drawOptionsSection } from "./components/options-section";
import { drawProfileManager } from "./components/profile-manager";
import { drawCssSnippetsUI } from "./components/snippets-ui";
import { LanguageSettingsModal } from "./modals";
import Sortable from "sortablejs";
import { loadLanguage } from "../i18n/strings";
import { CORE_LANGUAGES, LocaleCode } from "../i18n/types";
} from 'obsidian';
import { DEFAULT_VARS, TEXT_TO_BG_MAP } from '../constants';
import { t } from '../i18n/strings';
import type ColorMaster from '../main';
import { flattenVars, getAccessibilityRating, getContrastRatio, debounce } from '../utils';
import { drawColorPickers } from './components/color-pickers';
import { drawImportExport } from './components/import-export';
import { drawLikePluginCard } from './components/like-plugin-card';
import { drawOptionsSection } from './components/options-section';
import { drawProfileManager } from './components/profile-manager';
import { drawCssSnippetsUI } from './components/snippets-ui';
import { LanguageSettingsModal } from './modals';
import Sortable from 'sortablejs';
import { loadLanguage } from '../i18n/strings';
import { CORE_LANGUAGES, LocaleCode } from '../i18n/types';
import {
AddNewLanguageModal,
LanguageTranslatorModal,
ConfirmationModal,
LanguageInfoModal,
} from "./modals";
} from './modals';
export class ColorMasterSettingTab extends PluginSettingTab {
plugin: ColorMaster;
@ -58,22 +53,16 @@ export class ColorMasterSettingTab extends PluginSettingTab {
this.snippetSortable = null;
}
updateColorPickerAppearance(
textInput: HTMLInputElement,
colorPicker: HTMLInputElement,
) {
updateColorPickerAppearance(textInput: HTMLInputElement, colorPicker: HTMLInputElement) {
const value = textInput.value.toLowerCase().trim();
if (value === "transparent" || value === "") {
colorPicker.classList.add("is-transparent");
colorPicker.value = "#ffffff";
} else if (
value.startsWith("#") &&
(value.length === 9 || value.length === 5)
) {
colorPicker.classList.remove("is-transparent");
if (value === 'transparent' || value === '') {
colorPicker.classList.add('is-transparent');
colorPicker.value = '#ffffff';
} else if (value.startsWith('#') && (value.length === 9 || value.length === 5)) {
colorPicker.classList.remove('is-transparent');
let rgbHex = "#ffffff";
let rgbHex = '#ffffff';
if (value.length === 9) {
rgbHex = value.substring(0, 7);
} else if (value.length === 5) {
@ -87,14 +76,14 @@ export class ColorMasterSettingTab extends PluginSettingTab {
colorPicker.value = rgbHex;
} catch {
console.warn(`Color Master: Invalid HEX for color picker: ${rgbHex}`);
colorPicker.value = "#ffffff";
colorPicker.value = '#ffffff';
}
} else {
colorPicker.classList.remove("is-transparent");
colorPicker.classList.remove('is-transparent');
try {
colorPicker.value = value;
} catch {
colorPicker.value = "#ffffff";
colorPicker.value = '#ffffff';
}
}
}
@ -102,29 +91,24 @@ export class ColorMasterSettingTab extends PluginSettingTab {
_clearSearchAndFilters() {
if (!this.searchInput || !this.sectionSelect) return;
this.searchInput.value = "";
this.sectionSelect.value = "";
this.searchInput.value = '';
this.sectionSelect.value = '';
this._searchState.query = "";
this._searchState.section = "";
this._searchState.query = '';
this._searchState.section = '';
// Clear saved settings
this.plugin.settings.lastSearchQuery = "";
this.plugin.settings.lastSearchSection = "";
this.plugin.settings.lastSearchQuery = '';
this.plugin.settings.lastSearchSection = '';
void this.plugin.saveData(this.plugin.settings).catch((err) => {
console.error("Failed to save search input state:", err);
console.error('Failed to save search input state:', err);
});
const filterButton = this.containerEl.querySelector(
'button[data-cm-action="filter"]',
);
const filterOptionsContainer = this.containerEl.querySelector(
".cm-search-filter-options",
);
const filterButton = this.containerEl.querySelector('button[data-cm-action="filter"]');
const filterOptionsContainer = this.containerEl.querySelector('.cm-search-filter-options');
if (filterButton) filterButton.classList.remove("is-active");
if (filterOptionsContainer)
filterOptionsContainer.classList.add("is-hidden");
if (filterButton) filterButton.classList.remove('is-active');
if (filterOptionsContainer) filterOptionsContainer.classList.add('is-hidden');
this._applySearchFilter();
}
@ -132,38 +116,38 @@ export class ColorMasterSettingTab extends PluginSettingTab {
initSearchUI(containerEl: HTMLElement) {
// Load saved section here
this._searchState = {
query: this.plugin.settings.lastSearchQuery || "",
query: this.plugin.settings.lastSearchQuery || '',
regex: false,
caseSensitive: false,
section: this.plugin.settings.lastSearchSection || "",
section: this.plugin.settings.lastSearchSection || '',
};
const searchBarContainer = containerEl.createDiv({
cls: "cm-search-bar-container",
cls: 'cm-search-bar-container',
});
// --- 1. Input field section ---
const searchInputContainer = searchBarContainer.createDiv({
cls: "cm-search-input-container",
cls: 'cm-search-input-container',
});
const searchComponent = new SearchComponent(searchInputContainer)
.setPlaceholder(t("settings.searchPlaceholder"))
.setPlaceholder(t('settings.searchPlaceholder'))
.setValue(this._searchState.query)
.onChange((value) => {
if (this._searchState.regex) return;
this._searchState.query = value;
this.plugin.settings.lastSearchQuery = value;
void this.plugin.saveData(this.plugin.settings).catch((err) => {
console.error("Failed to save search/filter reset state:", err);
console.error('Failed to save search/filter reset state:', err);
});
debouncedFilter();
});
this.searchInput = searchComponent.inputEl;
this.searchInput.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.key === "Enter" && this._searchState.regex) {
this.searchInput.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Enter' && this._searchState.regex) {
this._searchState.query = (e.target as HTMLInputElement).value;
debouncedFilter();
}
@ -171,27 +155,27 @@ export class ColorMasterSettingTab extends PluginSettingTab {
// --- 2. Control buttons section ---
const searchActions = searchBarContainer.createDiv({
cls: "cm-search-actions",
cls: 'cm-search-actions',
});
this.caseToggle = searchActions.createEl("button", {
cls: "cm-search-action-btn",
this.caseToggle = searchActions.createEl('button', {
cls: 'cm-search-action-btn',
});
this.caseToggle.textContent = "Aa";
this.caseToggle.setAttr("aria-label", t("settings.ariaCase"));
this.caseToggle.textContent = 'Aa';
this.caseToggle.setAttr('aria-label', t('settings.ariaCase'));
this.regexToggle = searchActions.createEl("button", {
cls: "cm-search-action-btn",
this.regexToggle = searchActions.createEl('button', {
cls: 'cm-search-action-btn',
});
setIcon(this.regexToggle, "regex");
this.regexToggle.setAttr("aria-label", t("settings.ariaRegex"));
setIcon(this.regexToggle, 'regex');
this.regexToggle.setAttr('aria-label', t('settings.ariaRegex'));
const filterOptionsContainer = searchActions.createDiv({
cls: "cm-search-filter-options is-hidden",
cls: 'cm-search-filter-options is-hidden',
});
const dropdown = new DropdownComponent(filterOptionsContainer)
.addOption("", t("settings.allSections"))
.addOption('', t('settings.allSections'))
.onChange(async (value) => {
this._searchState.section = value;
@ -199,17 +183,14 @@ export class ColorMasterSettingTab extends PluginSettingTab {
this.plugin.settings.lastSearchSection = value;
await this.plugin.saveData(this.plugin.settings);
filterOptionsContainer.classList.toggle(
"is-filter-active",
value !== "",
);
filterOptionsContainer.classList.toggle('is-filter-active', value !== '');
debouncedFilter();
});
// Restore filter state
dropdown.setValue(this._searchState.section);
if (this._searchState.section) {
filterOptionsContainer.classList.remove("is-hidden");
filterOptionsContainer.classList.remove('is-hidden');
}
try {
@ -217,13 +198,11 @@ export class ColorMasterSettingTab extends PluginSettingTab {
let categoryKey: string;
const lowerCategory = category.toLowerCase();
if (lowerCategory === "interactive elements")
categoryKey = "interactive";
else if (lowerCategory === "ui elements") categoryKey = "ui";
else if (lowerCategory === "graph view") categoryKey = "graph";
else if (lowerCategory === "plugin integrations")
categoryKey = "pluginintegrations";
else categoryKey = lowerCategory.replace(/ /g, "");
if (lowerCategory === 'interactive elements') categoryKey = 'interactive';
else if (lowerCategory === 'ui elements') categoryKey = 'ui';
else if (lowerCategory === 'graph view') categoryKey = 'graph';
else if (lowerCategory === 'plugin integrations') categoryKey = 'pluginintegrations';
else categoryKey = lowerCategory.replace(/ /g, '');
const translatedCategory = t(`categories.${categoryKey}`) || category;
dropdown.addOption(category, translatedCategory);
@ -232,57 +211,56 @@ export class ColorMasterSettingTab extends PluginSettingTab {
// ignore invalid category translation
}
const activeProfile =
this.plugin.settings.profiles[this.plugin.settings.activeProfile];
const activeProfile = this.plugin.settings.profiles[this.plugin.settings.activeProfile];
if (
activeProfile?.customVarMetadata &&
Object.keys(activeProfile.customVarMetadata).length > 0
) {
dropdown.addOption("Custom", t("categories.custom"));
dropdown.addOption('Custom', t('categories.custom'));
}
dropdown.selectEl.classList.add("cm-search-small");
dropdown.selectEl.classList.add('cm-search-small');
this.sectionSelect = dropdown.selectEl;
const filterButton = searchActions.createEl("button", {
cls: "cm-search-action-btn",
const filterButton = searchActions.createEl('button', {
cls: 'cm-search-action-btn',
});
setIcon(filterButton, "sliders-horizontal");
filterButton.dataset.cmAction = "filter";
setIcon(filterButton, 'sliders-horizontal');
filterButton.dataset.cmAction = 'filter';
// Activate button if section is saved
if (this._searchState.section) filterButton.classList.add("is-active");
if (this._searchState.section) filterButton.classList.add('is-active');
const debouncedFilter = debounce(() => this._applySearchFilter(), 180);
this.caseToggle.addEventListener("click", () => {
this.caseToggle.addEventListener('click', () => {
this._searchState.caseSensitive = !this._searchState.caseSensitive;
this.caseToggle.toggleClass("is-active", this._searchState.caseSensitive);
this.caseToggle.toggleClass('is-active', this._searchState.caseSensitive);
debouncedFilter();
});
this.regexToggle.addEventListener("click", () => {
this.regexToggle.addEventListener('click', () => {
this._searchState.regex = !this._searchState.regex;
this.regexToggle.toggleClass("is-active", this._searchState.regex);
this.regexToggle.toggleClass('is-active', this._searchState.regex);
if (this._searchState.regex) {
searchComponent.setPlaceholder(t("settings.regexPlaceholder"));
searchComponent.setPlaceholder(t('settings.regexPlaceholder'));
} else {
searchComponent.setPlaceholder(t("settings.searchPlaceholder"));
searchComponent.setPlaceholder(t('settings.searchPlaceholder'));
}
this._searchState.query = this.searchInput.value;
debouncedFilter();
});
filterButton.addEventListener("click", () => {
if (filterButton.classList.contains("is-active")) {
filterButton.addEventListener('click', () => {
if (filterButton.classList.contains('is-active')) {
this._clearSearchAndFilters();
} else {
filterOptionsContainer.classList.remove("is-hidden");
filterButton.classList.add("is-active");
filterOptionsContainer.classList.remove('is-hidden');
filterButton.classList.add('is-active');
}
});
this.sectionSelect.addEventListener("change", (e: Event) => {
this.sectionSelect.addEventListener('change', (e: Event) => {
this._searchState.section = (e.target as HTMLSelectElement).value;
debouncedFilter();
});
@ -290,16 +268,13 @@ export class ColorMasterSettingTab extends PluginSettingTab {
_applySearchFilter() {
const s = this._searchState;
const activeProfile =
this.plugin.settings.profiles[this.plugin.settings.activeProfile];
const activeProfile = this.plugin.settings.profiles[this.plugin.settings.activeProfile];
if (this.staticContentContainer) {
const isSearching = s.query.trim().length > 0 || s.section !== "";
this.staticContentContainer.toggleClass("cm-hidden", isSearching);
const isSearching = s.query.trim().length > 0 || s.section !== '';
this.staticContentContainer.toggleClass('cm-hidden', isSearching);
}
const rows = Array.from(
this.containerEl.querySelectorAll<HTMLElement>(
".cm-var-row, .cm-searchable-row",
),
this.containerEl.querySelectorAll<HTMLElement>('.cm-var-row, .cm-searchable-row'),
);
let qRegex: RegExp | null = null;
@ -307,7 +282,7 @@ export class ColorMasterSettingTab extends PluginSettingTab {
if (s.query && s.query.trim()) {
if (s.regex) {
try {
qRegex = new RegExp(s.query, s.caseSensitive ? "" : "i");
qRegex = new RegExp(s.query, s.caseSensitive ? '' : 'i');
} catch {
qRegex = null;
}
@ -315,14 +290,13 @@ export class ColorMasterSettingTab extends PluginSettingTab {
}
rows.forEach((row) => {
const varName = row.dataset.var || "";
const snippetName = row.dataset.name || "";
const textInput =
row.querySelector<HTMLInputElement>("input[type='text']");
const varValue = textInput ? textInput.value.trim() : "";
const varName = row.dataset.var || '';
const snippetName = row.dataset.name || '';
const textInput = row.querySelector<HTMLInputElement>("input[type='text']");
const varValue = textInput ? textInput.value.trim() : '';
let displayName = "";
let description = "";
let displayName = '';
let description = '';
const customMeta = activeProfile?.customVarMetadata?.[varName];
if (customMeta) {
@ -330,11 +304,11 @@ export class ColorMasterSettingTab extends PluginSettingTab {
description = customMeta.desc;
} else {
displayName = t(`colors.names.${varName}`) || snippetName;
description = t(`colors.descriptions.${varName}`) || "";
description = t(`colors.descriptions.${varName}`) || '';
}
if (s.section && s.section !== row.dataset.category) {
row.classList.add("cm-hidden");
row.classList.add('cm-hidden');
return;
}
@ -351,15 +325,9 @@ export class ColorMasterSettingTab extends PluginSettingTab {
} else {
const queryLower = s.caseSensitive ? q : q.toLowerCase();
const nameLower = s.caseSensitive ? varName : varName.toLowerCase();
const valueLower = s.caseSensitive
? varValue
: varValue.toLowerCase();
const displayNameLower = s.caseSensitive
? displayName
: displayName.toLowerCase();
const descriptionLower = s.caseSensitive
? description
: description.toLowerCase();
const valueLower = s.caseSensitive ? varValue : varValue.toLowerCase();
const displayNameLower = s.caseSensitive ? displayName : displayName.toLowerCase();
const descriptionLower = s.caseSensitive ? description : description.toLowerCase();
isMatch =
nameLower.includes(queryLower) ||
@ -369,18 +337,16 @@ export class ColorMasterSettingTab extends PluginSettingTab {
}
if (!isMatch) {
row.classList.add("cm-hidden");
row.classList.add('cm-hidden');
return;
}
}
row.classList.remove("cm-hidden");
row.classList.remove('cm-hidden');
this._highlightRowMatches(row, s);
});
const headings = this.containerEl.querySelectorAll<HTMLElement>(
".cm-category-container",
);
const headings = this.containerEl.querySelectorAll<HTMLElement>('.cm-category-container');
headings.forEach((heading) => {
const category = heading.dataset.category;
const hasVisibleRows = this.containerEl.querySelector(
@ -388,9 +354,9 @@ export class ColorMasterSettingTab extends PluginSettingTab {
);
if (hasVisibleRows) {
heading.classList.remove("cm-hidden");
heading.classList.remove('cm-hidden');
} else {
heading.classList.add("cm-hidden");
heading.classList.add('cm-hidden');
}
});
}
@ -402,7 +368,7 @@ export class ColorMasterSettingTab extends PluginSettingTab {
if (!element) return;
if (!element.dataset.originalText) {
element.dataset.originalText = element.textContent || "";
element.dataset.originalText = element.textContent || '';
}
const originalText = element.dataset.originalText;
@ -413,13 +379,13 @@ export class ColorMasterSettingTab extends PluginSettingTab {
return;
}
const flags = state.caseSensitive ? "g" : "gi";
const flags = state.caseSensitive ? 'g' : 'gi';
let regex: RegExp;
try {
regex = state.regex
? new RegExp(query, flags)
: new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags);
: new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);
let lastIndex = 0;
let match;
@ -428,7 +394,7 @@ export class ColorMasterSettingTab extends PluginSettingTab {
if (match.index > lastIndex) {
element.appendText(originalText.substring(lastIndex, match.index));
}
element.createSpan({ cls: "cm-search-match", text: match[0] });
element.createSpan({ cls: 'cm-search-match', text: match[0] });
lastIndex = regex.lastIndex;
}
@ -440,8 +406,8 @@ export class ColorMasterSettingTab extends PluginSettingTab {
}
};
const nameEl = row.querySelector<HTMLElement>(".cm-var-name");
const descEl = row.querySelector<HTMLElement>(".setting-item-description");
const nameEl = row.querySelector<HTMLElement>('.cm-var-name');
const descEl = row.querySelector<HTMLElement>('.setting-item-description');
highlightElement(nameEl);
highlightElement(descEl);
@ -452,9 +418,7 @@ export class ColorMasterSettingTab extends PluginSettingTab {
const snapshot = this.plugin.settings.pinnedSnapshots?.[name];
if (this.resetPinBtn) {
this.resetPinBtn
.setTooltip(t("tooltips.resetToPinned"))
.setDisabled(!snapshot);
this.resetPinBtn.setTooltip(t('tooltips.resetToPinned')).setDisabled(!snapshot);
}
if (this.pinBtn) {
@ -465,16 +429,15 @@ export class ColorMasterSettingTab extends PluginSettingTab {
const day = dateObj.getDate();
const formattedDate = `${year}-${month}-${day}`;
this.pinBtn.setTooltip(t("tooltips.pinSnapshotDate", formattedDate));
this.pinBtn.setTooltip(t('tooltips.pinSnapshotDate', formattedDate));
} else {
this.pinBtn.setTooltip(t("tooltips.pinSnapshot"));
this.pinBtn.setTooltip(t('tooltips.pinSnapshot'));
}
}
}
_getCurrentProfileJson() {
const p =
this.plugin.settings.profiles?.[this.plugin.settings.activeProfile];
const p = this.plugin.settings.profiles?.[this.plugin.settings.activeProfile];
if (!p) return null;
return {
name: this.plugin.settings.activeProfile,
@ -486,30 +449,30 @@ export class ColorMasterSettingTab extends PluginSettingTab {
async _copyProfileToClipboard() {
const payload = this._getCurrentProfileJson();
if (!payload) {
new Notice(t("notices.noActiveProfileToCopy"));
new Notice(t('notices.noActiveProfileToCopy'));
return;
}
await navigator.clipboard.writeText(JSON.stringify(payload, null, 2));
new Notice(t("notices.jsonCopied"));
new Notice(t('notices.jsonCopied'));
}
_exportProfileToFile() {
const payload = this._getCurrentProfileJson();
if (!payload) {
new Notice(t("notices.noActiveProfileToExport"));
new Notice(t('notices.noActiveProfileToExport'));
return;
}
const blob = new Blob([JSON.stringify(payload, null, 2)], {
type: "application/json",
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
const a = document.createElement('a');
a.download = `${this.plugin.settings.activeProfile}.profile.json`;
a.href = url;
a.click();
URL.revokeObjectURL(url);
a.remove();
new Notice(t("notices.exportSuccess"));
new Notice(t('notices.exportSuccess'));
}
updateAccessibilityCheckers() {
@ -517,24 +480,19 @@ export class ColorMasterSettingTab extends PluginSettingTab {
this.plugin.settings.profiles[this.plugin.settings.activeProfile].vars;
const allDefaultVars = flattenVars(DEFAULT_VARS);
const checkerElements = this.containerEl.querySelectorAll(
".cm-accessibility-checker",
);
const checkerElements = this.containerEl.querySelectorAll('.cm-accessibility-checker');
checkerElements.forEach((checkerEl) => {
const varName = (checkerEl as HTMLElement).dataset.varName;
if (!varName) return;
const bgVarForTextColor =
TEXT_TO_BG_MAP[varName as keyof typeof TEXT_TO_BG_MAP];
const bgVarForTextColor = TEXT_TO_BG_MAP[varName as keyof typeof TEXT_TO_BG_MAP];
if (!bgVarForTextColor) return;
let textColor = activeProfileVars[varName] || allDefaultVars[varName];
let bgColor =
activeProfileVars[bgVarForTextColor] ||
allDefaultVars[bgVarForTextColor];
let bgColor = activeProfileVars[bgVarForTextColor] || allDefaultVars[bgVarForTextColor];
if (varName === "--text-highlight-bg") {
if (varName === '--text-highlight-bg') {
[textColor, bgColor] = [bgColor, textColor];
}
@ -549,7 +507,7 @@ export class ColorMasterSettingTab extends PluginSettingTab {
display(): void {
// call the async renderer and handle any error
void this.renderDisplay().catch((err) => {
console.error("Error in SettingsTab.renderDisplay:", err);
console.error('Error in SettingsTab.renderDisplay:', err);
});
}
@ -557,13 +515,13 @@ export class ColorMasterSettingTab extends PluginSettingTab {
const themeDefaults = await this.plugin.getThemeDefaults();
const { containerEl } = this;
containerEl.classList.add("color-master-settings-tab");
containerEl.classList.add('color-master-settings-tab');
containerEl.empty();
containerEl.classList.add("color-master-hidden");
containerEl.classList.add('color-master-hidden');
const langCode = this.plugin.settings.language;
const customLang = this.plugin.settings.customLanguages?.[langCode];
const isCoreRtlLang = langCode === "ar" || langCode === "fa";
const isCoreRtlLang = langCode === 'ar' || langCode === 'fa';
const isCustomRtlLang = customLang?.isRtl === true;
const isRtlCapable = isCoreRtlLang || isCustomRtlLang;
const isRtlEnabled = this.plugin.settings.useRtlLayout;
@ -571,92 +529,80 @@ export class ColorMasterSettingTab extends PluginSettingTab {
const isCustom = !!customLang;
const isCore = !!CORE_LANGUAGES[langCode as LocaleCode];
this.containerEl.setAttribute("dir", isRTL ? "rtl" : "ltr");
this.containerEl.setAttribute('dir', isRTL ? 'rtl' : 'ltr');
new Setting(containerEl).setName(t("plugin.name")).setHeading();
new Setting(containerEl).setName(t('plugin.name')).setHeading();
new Setting(containerEl)
.setName(t("settings.enablePlugin"))
.setDesc(t("settings.enablePluginDesc"))
.setName(t('settings.enablePlugin'))
.setDesc(t('settings.enablePluginDesc'))
.addToggle((toggle) => {
toggle
.setValue(this.plugin.settings.pluginEnabled)
.onChange(async (value) => {
this.plugin.settings.pluginEnabled = value;
toggle.setValue(this.plugin.settings.pluginEnabled).onChange(async (value) => {
this.plugin.settings.pluginEnabled = value;
if (value) {
this.plugin.enableObservers();
} else {
this.plugin.disableObservers();
}
if (value) {
this.plugin.enableObservers();
} else {
this.plugin.disableObservers();
}
await this.plugin.saveSettings();
this.plugin.restartColorUpdateLoop();
new Notice(
value ? t("notices.pluginEnabled") : t("notices.pluginDisabled"),
);
});
await this.plugin.saveSettings();
this.plugin.restartColorUpdateLoop();
new Notice(value ? t('notices.pluginEnabled') : t('notices.pluginDisabled'));
});
});
const languageSetting = new Setting(containerEl)
.setName(t("settings.language"))
.setDesc(t("settings.languageDesc"));
.setName(t('settings.language'))
.setDesc(t('settings.languageDesc'));
const langIcon = languageSetting.nameEl.createSpan({
cls: "cm-setting-icon",
cls: 'cm-setting-icon',
});
setIcon(langIcon, "languages");
setIcon(langIcon, 'languages');
languageSetting.nameEl.prepend(langIcon);
const flyoutContainer = languageSetting.controlEl.createDiv({
cls: "cm-flyout-menu-container",
cls: 'cm-flyout-menu-container',
});
const buttonListEl = flyoutContainer.createDiv({
cls: "cm-flyout-menu-buttons",
cls: 'cm-flyout-menu-buttons',
});
const triggerBtn = new ButtonComponent(flyoutContainer)
.setIcon("settings")
.setTooltip(t("tooltips.langMenu"))
.setClass("cm-flyout-trigger-btn")
.setIcon('settings')
.setTooltip(t('tooltips.langMenu'))
.setClass('cm-flyout-trigger-btn')
.onClick(() => {
buttonListEl.toggleClass(
"is-open",
!buttonListEl.classList.contains("is-open"),
);
buttonListEl.toggleClass('is-open', !buttonListEl.classList.contains('is-open'));
});
triggerBtn.buttonEl.classList.add("cm-control-icon-button");
triggerBtn.buttonEl.classList.add('cm-control-icon-button');
// Info Button
new ButtonComponent(buttonListEl)
.setIcon("info")
.setTooltip(t("tooltips.langInfo"))
.setClass("cm-control-icon-button")
.setIcon('info')
.setTooltip(t('tooltips.langInfo'))
.setClass('cm-control-icon-button')
.onClick(() => {
new LanguageInfoModal(this.app, this.plugin).open();
});
// Edit Button
new ButtonComponent(buttonListEl)
.setIcon("pencil")
.setTooltip(t("tooltips.editLang"))
.setClass("cm-control-icon-button")
.setIcon('pencil')
.setTooltip(t('tooltips.editLang'))
.setClass('cm-control-icon-button')
.onClick(() => {
const langCode = this.plugin.settings.language;
new LanguageTranslatorModal(
this.app,
this.plugin,
this,
langCode,
).open();
new LanguageTranslatorModal(this.app, this.plugin, this, langCode).open();
});
// Add Button
new ButtonComponent(buttonListEl)
.setIcon("plus")
.setTooltip(t("modals.addLang.title"))
.setClass("cm-control-icon-button")
.setIcon('plus')
.setTooltip(t('modals.addLang.title'))
.setClass('cm-control-icon-button')
.onClick(() => {
new AddNewLanguageModal(this.app, this.plugin, this).open();
});
@ -664,9 +610,9 @@ export class ColorMasterSettingTab extends PluginSettingTab {
// RTL Button
if (isRtlCapable) {
new ButtonComponent(buttonListEl)
.setIcon("settings-2") //
.setTooltip(t("settings.languageSettingsModalTitle"))
.setClass("cm-control-icon-button")
.setIcon('settings-2') //
.setTooltip(t('settings.languageSettingsModalTitle'))
.setClass('cm-control-icon-button')
.onClick(() => {
new LanguageSettingsModal(this.app, this.plugin).open();
});
@ -675,18 +621,15 @@ export class ColorMasterSettingTab extends PluginSettingTab {
// History/Restore Button for Core Languages that are customized
if (isCore && isCustom) {
const restoreBtn = new ButtonComponent(buttonListEl)
.setIcon("history")
.setTooltip(t("tooltips.restoreDefaultLang"))
.setClass("mod-warning")
.setIcon('history')
.setTooltip(t('tooltips.restoreDefaultLang'))
.setClass('mod-warning')
.onClick(() => {
new ConfirmationModal(
this.app,
this.plugin,
t("modals.confirmation.restoreLangTitle"),
t(
"modals.confirmation.restoreLangDesc",
CORE_LANGUAGES[langCode as LocaleCode],
),
t('modals.confirmation.restoreLangTitle'),
t('modals.confirmation.restoreLangDesc', CORE_LANGUAGES[langCode as LocaleCode]),
() => {
void (async () => {
if (this.plugin.settings.customLanguages) {
@ -694,47 +637,47 @@ export class ColorMasterSettingTab extends PluginSettingTab {
loadLanguage(this.plugin.settings);
await this.plugin.saveSettings();
this.display();
new Notice(t("notices.langRestored"));
new Notice(t('notices.langRestored'));
}
})().catch((err) => {
// Short human comment in English (as you requested)
console.error("Failed to restore language:", err);
console.error('Failed to restore language:', err);
});
},
{ buttonText: t("buttons.restore"), buttonClass: "mod-warning" },
{ buttonText: t('buttons.restore'), buttonClass: 'mod-warning' },
).open();
});
restoreBtn.buttonEl.classList.add("cm-control-icon-button");
restoreBtn.buttonEl.classList.add('cm-control-icon-button');
}
if (isCustom && !isCore) {
const deleteBtn = new ButtonComponent(buttonListEl)
.setIcon("trash")
.setTooltip(t("buttons.delete") + ` (${customLang.languageName})`)
.setClass("mod-warning")
.setIcon('trash')
.setTooltip(t('buttons.delete') + ` (${customLang.languageName})`)
.setClass('mod-warning')
.onClick(() => {
new ConfirmationModal(
this.app,
this.plugin,
t("modals.confirmation.deleteLangTitle"),
t("modals.confirmation.deleteLangDesc", customLang.languageName),
t('modals.confirmation.deleteLangTitle'),
t('modals.confirmation.deleteLangDesc', customLang.languageName),
() => {
void (async () => {
if (!this.plugin.settings.customLanguages) return;
delete this.plugin.settings.customLanguages[langCode];
this.plugin.settings.language = "en";
this.plugin.settings.language = 'en';
loadLanguage(this.plugin.settings);
await this.plugin.saveSettings();
this.display();
new Notice(t("notices.langDeleted", customLang.languageName));
new Notice(t('notices.langDeleted', customLang.languageName));
})().catch((err) => {
console.error("Failed to delete custom language:", err);
console.error('Failed to delete custom language:', err);
});
},
{ buttonText: t("buttons.delete"), buttonClass: "mod-warning" },
{ buttonText: t('buttons.delete'), buttonClass: 'mod-warning' },
).open();
});
deleteBtn.buttonEl.classList.add("cm-control-icon-button");
deleteBtn.buttonEl.classList.add('cm-control-icon-button');
}
languageSetting.addDropdown((dropdown) => {
@ -771,15 +714,15 @@ export class ColorMasterSettingTab extends PluginSettingTab {
this.initSearchUI(containerEl);
this.staticContentContainer = containerEl.createDiv({
cls: "cm-static-sections",
cls: 'cm-static-sections',
});
drawProfileManager(this.staticContentContainer, this);
drawImportExport(this.staticContentContainer, this);
drawOptionsSection(this.staticContentContainer, this);
this.staticContentContainer.createEl("hr");
this.staticContentContainer.createEl('hr');
drawCssSnippetsUI(this.staticContentContainer, this);
drawColorPickers(this.containerEl, this, themeDefaults);
containerEl.createEl("hr");
containerEl.createEl('hr');
drawLikePluginCard(containerEl, this);
//---Implement automatic search and scrolling---
@ -791,45 +734,39 @@ export class ColorMasterSettingTab extends PluginSettingTab {
setTimeout(() => {
// Find the first "visible" line
const firstVisibleRow = this.containerEl.querySelector<HTMLElement>(
".cm-var-row:not(.cm-hidden)",
'.cm-var-row:not(.cm-hidden)',
);
if (firstVisibleRow) {
firstVisibleRow.scrollIntoView({
behavior: "smooth",
block: "center",
behavior: 'smooth',
block: 'center',
});
}
}, 0);
}
const scrollContainer = this.containerEl.closest<HTMLElement>(
".vertical-tab-content",
);
const scrollContainer = this.containerEl.closest<HTMLElement>('.vertical-tab-content');
if (!scrollContainer) {
console.warn("Color Master: Could not find scroll container.");
console.warn('Color Master: Could not find scroll container.');
} else {
const debouncedScrollSave = debounce(() => {
this.plugin.settings.lastScrollPosition = scrollContainer.scrollTop;
void this.plugin.saveData(this.plugin.settings).catch((err) => {
console.error("Failed to save scroll position:", err);
console.error('Failed to save scroll position:', err);
});
}, 200);
this.plugin.registerDomEvent(
scrollContainer,
"scroll",
debouncedScrollSave,
);
this.plugin.registerDomEvent(scrollContainer, 'scroll', debouncedScrollSave);
if (!this._searchState.query && this.plugin.settings.lastScrollPosition) {
scrollContainer.scrollTo({
top: this.plugin.settings.lastScrollPosition,
behavior: "auto",
behavior: 'auto',
});
}
}
containerEl.classList.remove("color-master-hidden");
containerEl.classList.remove('color-master-hidden');
}
}

View file

@ -1,5 +1,5 @@
import { App, DataAdapter } from "obsidian";
import type { Profile } from "./types";
import { App, DataAdapter } from 'obsidian';
import type { Profile } from './types';
// Flattens nested variable objects into a single level map
export function flattenVars(varsObject: { [key: string]: unknown }): {
@ -14,7 +14,7 @@ export function flattenVars(varsObject: { [key: string]: unknown }): {
// Standard relative luminance calculation
export function getLuminance(hex: string): number {
const rgb = parseInt(hex.startsWith("#") ? hex.substring(1) : hex, 16);
const rgb = parseInt(hex.startsWith('#') ? hex.substring(1) : hex, 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
@ -40,22 +40,19 @@ export function getContrastRatio(hex1: string, hex2: string): number {
export function getAccessibilityRating(ratio: number) {
const score = ratio.toFixed(2);
if (ratio >= 7) {
return { text: "AAA", score, cls: "cm-accessibility-pass" };
return { text: 'AAA', score, cls: 'cm-accessibility-pass' };
}
if (ratio >= 4.5) {
return { text: "AA", score, cls: "cm-accessibility-pass" };
return { text: 'AA', score, cls: 'cm-accessibility-pass' };
}
if (ratio >= 3) {
return { text: "AA Large", score, cls: "cm-accessibility-warn" };
return { text: 'AA Large', score, cls: 'cm-accessibility-warn' };
}
return { text: "Fail", score, cls: "cm-accessibility-fail" };
return { text: 'Fail', score, cls: 'cm-accessibility-fail' };
}
// Checks if a plugin is both installed AND enabled
export function isPluginEnabled(
app: App,
pluginIds: string | string[],
): boolean {
export function isPluginEnabled(app: App, pluginIds: string | string[]): boolean {
const pluginManager = (app as unknown).plugins;
const idsToCheck = Array.isArray(pluginIds) ? pluginIds : [pluginIds];
@ -64,7 +61,7 @@ export function isPluginEnabled(
// Helper check for Iconize or Icon Folder
export function isIconizeEnabled(app: App): boolean {
const iconizeIDs = ["obsidian-icon-folder", "iconize"];
const iconizeIDs = ['obsidian-icon-folder', 'iconize'];
return isPluginEnabled(app, iconizeIDs);
}
@ -78,33 +75,26 @@ export function debounce(fn: (...args: unknown[]) => void, ms = 200) {
}
// Auto-increments filename if path exists (e.g., file-2.png)
export async function findNextAvailablePath(
adapter: DataAdapter,
path: string,
): Promise<string> {
export async function findNextAvailablePath(adapter: DataAdapter, path: string): Promise<string> {
if (!(await adapter.exists(path))) {
return path;
}
const pathParts = path.split("/");
const pathParts = path.split('/');
const fullFileName = pathParts.pop();
if (!fullFileName) return path;
const dir = pathParts.join("/");
const fileNameParts = fullFileName.split(".");
const ext = fileNameParts.length > 1 ? fileNameParts.pop() : "";
const baseName = fileNameParts.join(".");
const dir = pathParts.join('/');
const fileNameParts = fullFileName.split('.');
const ext = fileNameParts.length > 1 ? fileNameParts.pop() : '';
const baseName = fileNameParts.join('.');
let counter = 2;
let newPath = ext
? `${dir}/${baseName}-${counter}.${ext}`
: `${dir}/${baseName}-${counter}`;
let newPath = ext ? `${dir}/${baseName}-${counter}.${ext}` : `${dir}/${baseName}-${counter}`;
while (await adapter.exists(newPath)) {
counter++;
newPath = ext
? `${dir}/${baseName}-${counter}.${ext}`
: `${dir}/${baseName}-${counter}`;
newPath = ext ? `${dir}/${baseName}-${counter}.${ext}` : `${dir}/${baseName}-${counter}`;
}
return newPath;
}
@ -115,16 +105,11 @@ export async function maybeConvertToJpg(
arrayBuffer: ArrayBuffer,
fileName: string,
): Promise<{ arrayBuffer: ArrayBuffer; fileName: string }> {
const fileExt = fileName.split(".").pop()?.toLowerCase();
const isAlreadyJpg = fileExt === "jpg" || fileExt === "jpeg";
const supportedToConvert = ["png", "webp", "bmp"].includes(fileExt || "");
const fileExt = fileName.split('.').pop()?.toLowerCase();
const isAlreadyJpg = fileExt === 'jpg' || fileExt === 'jpeg';
const supportedToConvert = ['png', 'webp', 'bmp'].includes(fileExt || '');
if (
!activeProfile ||
!activeProfile.convertImagesToJpg ||
isAlreadyJpg ||
!supportedToConvert
) {
if (!activeProfile || !activeProfile.convertImagesToJpg || isAlreadyJpg || !supportedToConvert) {
return { arrayBuffer, fileName };
}
@ -136,18 +121,18 @@ export async function maybeConvertToJpg(
const image = new Image();
image.onload = () => {
const canvas = document.createElement("canvas");
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext("2d");
const ctx = canvas.getContext('2d');
if (!ctx) {
URL.revokeObjectURL(url);
return reject(new Error("Failed to get canvas context"));
return reject(new Error('Failed to get canvas context'));
}
// Fill background white since JPG doesn't support alpha
ctx.fillStyle = "#FFFFFF";
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0);
@ -158,15 +143,14 @@ export async function maybeConvertToJpg(
canvas.toBlob(
(jpgBlob) => {
if (!jpgBlob) {
reject(new Error("Failed to create JPG blob"));
reject(new Error('Failed to create JPG blob'));
return;
}
jpgBlob
.arrayBuffer()
.then((newArrayBuffer) => {
const newFileName =
fileName.substring(0, fileName.lastIndexOf(".")) + ".jpg";
const newFileName = fileName.substring(0, fileName.lastIndexOf('.')) + '.jpg';
console.debug(
`Color Master: Conversion complete. New size: ${newArrayBuffer.byteLength} bytes`,
@ -178,14 +162,14 @@ export async function maybeConvertToJpg(
reject(err instanceof Error ? err : new Error(String(err)));
});
},
"image/jpeg",
'image/jpeg',
quality,
);
};
image.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error("Failed to load image for conversion"));
reject(new Error('Failed to load image for conversion'));
};
image.src = url;
@ -193,14 +177,12 @@ export async function maybeConvertToJpg(
}
// Reconstructs nested object from dot-notation keys
export function unflattenStrings(
flatObject: Record<string, string>,
): Record<string, unknown> {
export function unflattenStrings(flatObject: Record<string, string>): Record<string, unknown> {
const nestedResult: Record<string, unknown> = {};
for (const key in flatObject) {
if (Object.prototype.hasOwnProperty.call(flatObject, key)) {
const keys = key.split(".");
const keys = key.split('.');
let currentLevel = nestedResult;
for (let i = 0; i < keys.length; i++) {
@ -209,7 +191,7 @@ export function unflattenStrings(
if (i === keys.length - 1) {
currentLevel[part] = flatObject[key] as unknown;
} else {
if (!currentLevel[part] || typeof currentLevel[part] !== "object") {
if (!currentLevel[part] || typeof currentLevel[part] !== 'object') {
currentLevel[part] = {};
}
currentLevel = currentLevel[part] as Record<string, unknown>;
@ -222,19 +204,19 @@ export function unflattenStrings(
function componentToHex(c: number): string {
const hex = Math.round(c).toString(16);
return hex.length == 1 ? "0" + hex : hex;
return hex.length == 1 ? '0' + hex : hex;
}
// Normalizes any CSS color string to Hex/HexA format
export function convertColorToHex(colorString: string): string {
const s = colorString.toLowerCase().trim();
if (s === "transparent") {
return "#00000000";
if (s === 'transparent') {
return '#00000000';
}
// Expand short hex codes if needed
if (s.startsWith("#")) {
if (s.startsWith('#')) {
if (s.length === 4) {
const r = s[1];
const g = s[2];
@ -252,7 +234,7 @@ export function convertColorToHex(colorString: string): string {
}
// Use the DOM to normalize other formats (rgb, hsl, names)
const d = document.createElement("div");
const d = document.createElement('div');
d.style.color = s;
document.body.appendChild(d);
@ -260,9 +242,7 @@ export function convertColorToHex(colorString: string): string {
const computedColor = getComputedStyle(d).color;
document.body.removeChild(d);
const match = computedColor.match(
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/,
);
const match = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
if (match) {
const r = parseInt(match[1]);
@ -297,7 +277,7 @@ if (!HTMLElement.prototype.setCssProps) {
for (const key in props) {
const value = props[key];
if (value === null || value === undefined || value === "") {
if (value === null || value === undefined || value === '') {
this.style.removeProperty(key);
} else {
try {