mirror of
https://github.com/yazanammar/obsidian-theme-engine.git
synced 2026-07-22 06:44:37 +00:00
refactor: update branding, locales, and switch to Obsidian native debounce
- Update README title and license year to 2026. - Clean up settings labels by removing redundant "settings" word in multiple locales (en, ar, fa, fr). - Refactor profile navigation and theme toggle to use `checkCallback` for better command palette integration. - Replace custom debounce implementation with official Obsidian API across UI components and utils. - Remove redundant debounce code from utils and background scripts.
This commit is contained in:
parent
e8d8e40021
commit
786923c1b7
15 changed files with 64 additions and 79 deletions
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Yazan Ammar
|
||||
Copyright (c) 2026 Yazan Ammar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Obsidian Theme Engine
|
||||
# Theme Engine for Obsidian
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/YazanAmmar/obsidian-theme-engine/releases)
|
||||
|
|
@ -571,7 +571,7 @@ A: Yes, absolutely. The plugin fully supports **Arabic** (العَرَبيَّة
|
|||
MIT — see [LICENSE](https://github.com/YazanAmmar/obsidian-theme-engine/blob/main/LICENSE).
|
||||
|
||||
> [!NOTE]
|
||||
> This license does not grant rights to use the project's logo or visual identity.
|
||||
> This license does not grant rights to use the project's logo or visual identity see [TRADEMARK.md](./TRADEMARK.md).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -582,7 +582,7 @@ The official logo (phoenix emblem) and visual identity of this project are prote
|
|||
- You may use, modify, and distribute the code under the MIT license
|
||||
- You may NOT use the official logo or visual identity in forks, derivatives, or redistributed versions
|
||||
|
||||
> For full trademark and branding rules, see [TRADEMARK.md](./TRADEMARK.md)
|
||||
> For full trademark and branding rules, see [TRADEMARK.md](./TRADEMARK.md).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -39,17 +39,24 @@ export function registerCommands(plugin: ThemeEngine) {
|
|||
plugin.addCommand({
|
||||
id: PLUGIN_COMMAND_SUFFIXES[1],
|
||||
name: t('commands.cycleNext'),
|
||||
callback: async () => {
|
||||
checkCallback: (checking: boolean) => {
|
||||
const names = Object.keys(plugin.settings.profiles || {});
|
||||
if (names.length === 0) {
|
||||
new Notice(t('notices.noProfilesFound'));
|
||||
return;
|
||||
}
|
||||
if (names.length === 0) return false;
|
||||
|
||||
if (checking) return true;
|
||||
|
||||
const idx = names.indexOf(plugin.settings.activeProfile);
|
||||
const next = names[(idx + 1) % names.length];
|
||||
const next = names[((idx >= 0 ? idx : -1) + 1) % names.length];
|
||||
plugin.settings.activeProfile = next;
|
||||
await plugin.saveSettings();
|
||||
new Notice(t('notices.activeProfileSwitched', next));
|
||||
void plugin
|
||||
.saveSettings()
|
||||
.then(() => {
|
||||
new Notice(t('notices.activeProfileSwitched', next));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to switch to the next profile.', error);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -57,19 +64,27 @@ export function registerCommands(plugin: ThemeEngine) {
|
|||
plugin.addCommand({
|
||||
id: PLUGIN_COMMAND_SUFFIXES[2],
|
||||
name: t('commands.cyclePrevious'),
|
||||
callback: async () => {
|
||||
checkCallback: (checking: boolean) => {
|
||||
const names = Object.keys(plugin.settings.profiles || {});
|
||||
if (names.length === 0) {
|
||||
new Notice(t('notices.noProfilesFound'));
|
||||
return;
|
||||
}
|
||||
if (names.length === 0) return false;
|
||||
|
||||
if (checking) return true;
|
||||
|
||||
const currentIndex = names.indexOf(plugin.settings.activeProfile);
|
||||
const previousIndex = (currentIndex - 1 + names.length) % names.length;
|
||||
const previousIndex =
|
||||
currentIndex >= 0 ? (currentIndex - 1 + names.length) % names.length : names.length - 1;
|
||||
const previousProfile = names[previousIndex];
|
||||
|
||||
plugin.settings.activeProfile = previousProfile;
|
||||
await plugin.saveSettings();
|
||||
new Notice(t('notices.activeProfileSwitched', previousProfile));
|
||||
void plugin
|
||||
.saveSettings()
|
||||
.then(() => {
|
||||
new Notice(t('notices.activeProfileSwitched', previousProfile));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to switch to the previous profile.', error);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -88,14 +103,13 @@ export function registerCommands(plugin: ThemeEngine) {
|
|||
plugin.addCommand({
|
||||
id: PLUGIN_COMMAND_SUFFIXES[4],
|
||||
name: t('commands.toggleTheme'),
|
||||
callback: async () => {
|
||||
checkCallback: (checking: boolean) => {
|
||||
const activeProfileName = plugin.settings.activeProfile;
|
||||
const activeProfile = plugin.settings.profiles[activeProfileName];
|
||||
|
||||
if (!activeProfile) {
|
||||
new Notice(t('notices.profileNotFound'));
|
||||
return;
|
||||
}
|
||||
if (!activeProfile) return false;
|
||||
|
||||
if (checking) return true;
|
||||
|
||||
const currentTheme = activeProfile.themeType || 'auto';
|
||||
let nextTheme: 'auto' | 'dark' | 'light';
|
||||
|
|
@ -113,8 +127,15 @@ export function registerCommands(plugin: ThemeEngine) {
|
|||
}
|
||||
|
||||
activeProfile.themeType = nextTheme;
|
||||
await plugin.saveSettings();
|
||||
new Notice(noticeMessage);
|
||||
void plugin
|
||||
.saveSettings()
|
||||
.then(() => {
|
||||
new Notice(noticeMessage);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to toggle the active profile theme.', error);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export default {
|
|||
tooltipCopyJson: 'نسخ JSON للملف الشخصي الحالي إلى الحافظة',
|
||||
},
|
||||
options: {
|
||||
heading: 'إعدادات متقدمة',
|
||||
heading: 'خيارات متقدمة',
|
||||
liveUpdateName: 'معدل التحديث المباشر (إطار بالثانية)',
|
||||
liveUpdateDesc:
|
||||
'يحدد عدد مرات تحديث معاينة الألوان في الثانية أثناء السحب (0 = تعطيل المعاينة). القيم المنخفضة تحسن الأداء.',
|
||||
|
|
@ -82,7 +82,7 @@ export default {
|
|||
resetPluginButton: 'إعادة تعيين كل البيانات...',
|
||||
backgroundName: 'تعيين خلفية مخصصة',
|
||||
backgroundDesc: 'إدارة صورة أو فيديو الخلفية لهذا الملف الشخصي.',
|
||||
backgroundModalSettingsTitle: 'إعدادات الخلفية',
|
||||
backgroundModalSettingsTitle: 'خيارات الخلفية',
|
||||
backgroundEnableName: 'تفعيل الخلفية',
|
||||
backgroundEnableDesc: 'التحكم بظهور أو إخفاء الخلفية المخصصة لهذا الملف الشخصي.',
|
||||
convertImagesName: 'تحويل الصور إلى JPG',
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default {
|
|||
tooltipCopyJson: 'Copy current profile JSON to clipboard',
|
||||
},
|
||||
options: {
|
||||
heading: 'Advanced settings',
|
||||
heading: 'Advanced options',
|
||||
liveUpdateName: 'Live update fps',
|
||||
liveUpdateDesc:
|
||||
'Sets how many times per second the UI previews color changes while dragging (0 = disable live preview). Lower values can improve performance.',
|
||||
|
|
@ -84,7 +84,7 @@ export default {
|
|||
backgroundName: 'Set custom background',
|
||||
backgroundDesc: 'Manage the background image or video for this profile.',
|
||||
|
||||
backgroundModalSettingsTitle: 'Background settings',
|
||||
backgroundModalSettingsTitle: 'Background options',
|
||||
backgroundEnableName: 'Enable background',
|
||||
backgroundEnableDesc: 'Toggle the visibility of the custom background for this profile.',
|
||||
convertImagesName: 'Convert images to JPG',
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
tooltipCopyJson: 'کپی کردن JSON نمایه فعلی در کلیپبورد',
|
||||
},
|
||||
options: {
|
||||
heading: 'تنظیمات پیشرفته',
|
||||
heading: 'گزینههای پیشرفته',
|
||||
liveUpdateName: 'فریم در ثانیه بهروزرسانی زنده',
|
||||
liveUpdateDesc:
|
||||
'تعداد دفعاتی که رابط کاربری در هر ثانیه پیشنمایش تغییرات رنگ را هنگام کشیدن نشان میدهد تنظیم میکند (0 = غیرفعال کردن پیشنمایش زنده). مقادیر کمتر میتواند عملکرد را بهبود بخشد.',
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
resetPluginButton: 'بازنشانی تمام دادهها...',
|
||||
backgroundName: 'تنظیم پسزمینه سفارشی',
|
||||
backgroundDesc: 'مدیریت تصویر یا ویدئو پسزمینه برای این پروفایل.',
|
||||
backgroundModalSettingsTitle: 'تنظیمات پسزمینه',
|
||||
backgroundModalSettingsTitle: 'گزینههای پسزمینه',
|
||||
backgroundEnableName: 'فعال کردن پسزمینه',
|
||||
backgroundEnableDesc: 'نمایش یا پنهان کردن پسزمینه سفارشی برای این پروفایل را تغییر دهید.',
|
||||
convertImagesName: 'تبدیل تصاویر به JPG',
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
tooltipCopyJson: 'Copier le JSON du profil actuel dans le presse-papiers',
|
||||
},
|
||||
options: {
|
||||
heading: 'Paramètres avancés',
|
||||
heading: 'Options avancées',
|
||||
liveUpdateName: 'FPS de la mise à jour en direct',
|
||||
liveUpdateDesc:
|
||||
"Définit le nombre de fois par seconde où l'interface prévisualise les changements de couleur lors du glissement (0 = désactiver l'aperçu en direct). Des valeurs plus basses peuvent améliorer les performances.",
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
resetPluginButton: 'Réinitialiser toutes les données...',
|
||||
backgroundName: 'Définir un arrière-plan personnalisé',
|
||||
backgroundDesc: "Gérer l'image ou la vidéo d'arrière-plan pour ce profil.",
|
||||
backgroundModalSettingsTitle: "Paramètres d'arrière-plan",
|
||||
backgroundModalSettingsTitle: "Options d'arrière-plan",
|
||||
backgroundEnableName: "Activer l'arrière-plan",
|
||||
backgroundEnableDesc:
|
||||
"Activer/désactiver la visibilité de l'arrière-plan personnalisé pour ce profil.",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { App, Notice, Setting, TextComponent } from 'obsidian';
|
||||
import { App, Notice, Setting, TextComponent, debounce } from 'obsidian';
|
||||
import { DEFAULT_PROFILE } from '../../../constants';
|
||||
import { t } from '../../../i18n/strings';
|
||||
import type ThemeEngine from '../../../main';
|
||||
import { debounce } from '../../../utils';
|
||||
import type { ThemeEngineSettingTab } from '../../settingsTab';
|
||||
import { ThemeEngineBaseModal } from '../base';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { App, Notice, Setting, TextComponent, TextAreaComponent } from 'obsidian';
|
||||
import { App, Notice, Setting, TextComponent, TextAreaComponent, debounce } from 'obsidian';
|
||||
import { t } from '../../../i18n/strings';
|
||||
import type ThemeEngine from '../../../main';
|
||||
import type { Snippet } from '../../../types';
|
||||
import { debounce } from '../../../utils';
|
||||
import type { ThemeEngineSettingTab } from '../../settingsTab';
|
||||
import { ThemeEngineBaseModal } from '../base';
|
||||
|
||||
|
|
@ -25,14 +24,6 @@ export class SnippetCssModal extends ThemeEngineBaseModal {
|
|||
isGlobalSnippet: boolean;
|
||||
isSaving: boolean = false;
|
||||
|
||||
_debounce(func: (...args: unknown[]) => void, delay: number) {
|
||||
let timeout: number;
|
||||
return (...args: unknown[]) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = window.setTimeout(() => func.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: ThemeEngine,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { App, Notice, Setting, TextComponent } from 'obsidian';
|
||||
import { App, Notice, Setting, TextComponent, debounce } from 'obsidian';
|
||||
import { t } from '../../../i18n/strings';
|
||||
import type ThemeEngine from '../../../main';
|
||||
import type { Profile, Snippet } from '../../../types';
|
||||
import { debounce } from '../../../utils';
|
||||
import type { ThemeEngineSettingTab } from '../../settingsTab';
|
||||
import { ThemeEngineBaseModal } from '../base';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { App, ButtonComponent, Notice, requestUrl, Setting } from 'obsidian';
|
||||
import { App, ButtonComponent, Notice, debounce, requestUrl, Setting } from 'obsidian';
|
||||
import { t } from '../../../i18n/strings';
|
||||
import type ThemeEngine from '../../../main';
|
||||
import { debounce } from '../../../utils';
|
||||
import type { ThemeEngineSettingTab } from '../../settingsTab';
|
||||
import { ThemeEngineBaseModal } from '../base';
|
||||
|
||||
|
|
@ -168,20 +167,6 @@ export class BackgroundImageSettingsModal extends ThemeEngineBaseModal {
|
|||
});
|
||||
}, 0);
|
||||
|
||||
debounce(func: (...args: unknown[]) => void, wait: number) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null;
|
||||
return (...args: unknown[]) => {
|
||||
const later = () => {
|
||||
timeout = null;
|
||||
func(...args);
|
||||
};
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
App,
|
||||
Notice,
|
||||
SearchComponent,
|
||||
debounce,
|
||||
setIcon,
|
||||
Setting,
|
||||
TextAreaComponent,
|
||||
|
|
@ -17,7 +18,7 @@ import {
|
|||
import { CORE_LANGUAGES, type LocaleCode, type LocalizedValue } from '../../../i18n/types';
|
||||
import type ThemeEngine from '../../../main';
|
||||
import type { CustomTranslation } from '../../../types';
|
||||
import { debounce, unflattenStrings } from '../../../utils';
|
||||
import { unflattenStrings } from '../../../utils';
|
||||
import type { ThemeEngineSettingTab } from '../../settingsTab';
|
||||
import { ThemeEngineBaseModal } from '../base';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ButtonComponent, Notice, Setting, SettingGroup, setIcon } from 'obsidian';
|
||||
import { ButtonComponent, Notice, Setting, SettingGroup, debounce, setIcon } from 'obsidian';
|
||||
import { CORE_LANGUAGES, LocaleCode } from '../../i18n/types';
|
||||
import { loadLanguage, t } from '../../i18n/strings';
|
||||
import type { ThemeEngineSettingTab } from '../settingsTab';
|
||||
|
|
@ -15,7 +15,6 @@ import {
|
|||
LanguageSettingsModal,
|
||||
LanguageTranslatorModal,
|
||||
} from '../modals';
|
||||
import { debounce } from '../../utils';
|
||||
|
||||
export const renderSettingsTab = async (tab: ThemeEngineSettingTab): Promise<void> => {
|
||||
const themeDefaults = await tab.plugin.getThemeDefaults();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { DropdownComponent, SearchComponent, setIcon } from 'obsidian';
|
||||
import { DropdownComponent, SearchComponent, debounce, setIcon } from 'obsidian';
|
||||
import { DEFAULT_VARS } from '../../constants';
|
||||
import { t } from '../../i18n/strings';
|
||||
import type { ThemeEngineSettingTab } from '../settingsTab';
|
||||
import { debounce } from '../../utils';
|
||||
|
||||
export type SearchState = {
|
||||
query: string;
|
||||
|
|
|
|||
|
|
@ -71,15 +71,6 @@ export function isIconizeEnabled(app: App): boolean {
|
|||
return isPluginEnabled(app, iconizeIDs);
|
||||
}
|
||||
|
||||
// Standard debounce to limit function execution rate
|
||||
export function debounce(fn: (...args: unknown[]) => void, ms = 200) {
|
||||
let t: ReturnType<typeof setTimeout> | null = null;
|
||||
return (...args: unknown[]) => {
|
||||
if (t) clearTimeout(t);
|
||||
t = setTimeout(() => fn.apply(this, args), ms);
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-increments filename if path exists (e.g., file-2.png)
|
||||
export async function findNextAvailablePath(adapter: DataAdapter, path: string): Promise<string> {
|
||||
if (!(await adapter.exists(path))) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue