initial commit
This commit is contained in:
commit
58b379d6e3
47 files changed
+8664
No files matched your search
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Settings view — language, font, and display preferences.
|
||||
*/
|
||||
|
||||
import { getTranslations, type LanguageCode } from '../core/i18n';
|
||||
|
||||
const LANGUAGES = [
|
||||
{ code: 'en', name: 'English', local: 'English' },
|
||||
{ code: 'ru', name: 'Russian', local: 'Русский' },
|
||||
{ code: 'cu', name: 'Church Slavonic', local: 'Церковнославянский' },
|
||||
];
|
||||
|
||||
const CU_FONTS = [
|
||||
{ id: '', name: 'System Default' },
|
||||
{ id: 'Ponomar', name: 'Ponomar' },
|
||||
{ id: 'Fedorovsk', name: 'Fedorovsk' },
|
||||
{ id: 'Menaion', name: 'Menaion' },
|
||||
{ id: 'Monomakh', name: 'Monomakh' },
|
||||
{ id: 'Triodion', name: 'Triodion' },
|
||||
{ id: 'Vilnius', name: 'Vilnius' },
|
||||
{ id: 'Voskresensky', name: 'Voskresensky' },
|
||||
];
|
||||
|
||||
const SETTINGS_KEY = 'ponomar-settings';
|
||||
|
||||
interface Settings {
|
||||
language: string;
|
||||
cuFont: string;
|
||||
fontSize: number;
|
||||
defaultBibleVersion: string;
|
||||
showVerseNumbers: boolean;
|
||||
verseNewLine: boolean;
|
||||
calendarType: 'julian' | 'gregorian';
|
||||
serviceRole: 'priest' | 'reader' | 'auto';
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: Settings = {
|
||||
language: 'en',
|
||||
cuFont: 'Ponomar',
|
||||
fontSize: 16,
|
||||
defaultBibleVersion: 'kjv',
|
||||
showVerseNumbers: true,
|
||||
verseNewLine: false,
|
||||
calendarType: 'julian',
|
||||
serviceRole: 'priest',
|
||||
};
|
||||
|
||||
function detectBrowserLanguage(): LanguageCode {
|
||||
const lang = navigator.language || (navigator as any).userLanguage || '';
|
||||
if (lang.startsWith('ru')) return 'ru';
|
||||
if (lang.startsWith('cu')) return 'cu';
|
||||
return 'en';
|
||||
}
|
||||
|
||||
function loadSettings(): Settings {
|
||||
try {
|
||||
const stored = localStorage.getItem(SETTINGS_KEY);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
// Migrate old usePonomarFont to cuFont
|
||||
if (parsed.usePonomarFont !== undefined) {
|
||||
parsed.cuFont = parsed.usePonomarFont ? 'Ponomar' : '';
|
||||
delete parsed.usePonomarFont;
|
||||
}
|
||||
return { ...DEFAULT_SETTINGS, ...parsed };
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return { ...DEFAULT_SETTINGS, language: detectBrowserLanguage() };
|
||||
}
|
||||
|
||||
function saveSettings(settings: Settings) {
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
||||
}
|
||||
|
||||
function fontClass(fontName: string): string {
|
||||
if (!fontName) return 'font-system-slavonic';
|
||||
const lower = fontName.toLowerCase();
|
||||
const map: Record<string, string> = {
|
||||
ponomar: 'font-ponomar',
|
||||
fedorovsk: 'font-fedrovsk',
|
||||
indiction: 'font-indiction',
|
||||
menaion: 'font-menaion',
|
||||
monomakh: 'font-monomakh',
|
||||
triodion: 'font-triodion',
|
||||
vilnius: 'font-vilnius',
|
||||
voskresensky: 'font-voskresensky',
|
||||
};
|
||||
return map[lower] || 'font-slavonic';
|
||||
}
|
||||
|
||||
export { loadSettings, saveSettings, fontClass };
|
||||
export type { Settings };
|
||||
|
||||
export class SettingsView {
|
||||
private container: HTMLElement;
|
||||
private settings: Settings;
|
||||
private onLanguageChange?: () => void;
|
||||
|
||||
constructor(container: HTMLElement, onLanguageChange?: () => void) {
|
||||
this.container = container;
|
||||
this.settings = loadSettings();
|
||||
this.onLanguageChange = onLanguageChange;
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = getTranslations(this.settings.language as LanguageCode);
|
||||
|
||||
this.container.innerHTML = `
|
||||
<div class="p-6 max-w-2xl mx-auto">
|
||||
<h2 class="text-2xl font-bold text-navy mb-6">${t.settings.title}</h2>
|
||||
|
||||
<!-- Language -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-bold text-navy mb-2">${t.settings.language}</label>
|
||||
<select id="language" class="w-full border border-gold/30 rounded p-2 bg-white text-navy">
|
||||
${LANGUAGES.map(l => `
|
||||
<option value="${l.code}" ${l.code === this.settings.language ? 'selected' : ''}>
|
||||
${l.name} (${l.local})
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Font Settings -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-navy mb-3">${t.settings.fontSettings}</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<select id="cu-font" class="w-full border border-gold/30 rounded p-2 bg-white text-navy">
|
||||
${CU_FONTS.map(f => `
|
||||
<option value="${f.id}" ${f.id === this.settings.cuFont ? 'selected' : ''}>
|
||||
${f.id === '' ? t.settings.systemFont : f.name}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-parchment-dark rounded border border-gold/20">
|
||||
<p class="text-sm text-navy mb-2">${t.settings.fontPreview}</p>
|
||||
<div id="font-preview" class="text-lg">
|
||||
Сохрани́ мѧ, гдⷭ҇и, ꙗ҆́кѡ на тѧ̀ ᲂу҆пова́хъ
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Font Size -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-bold text-navy mb-2">
|
||||
${t.settings.fontSize} <span id="font-size-value">${this.settings.fontSize}px</span>
|
||||
</label>
|
||||
<input type="range" id="font-size" min="12" max="32" value="${this.settings.fontSize}"
|
||||
class="w-full accent-gold">
|
||||
</div>
|
||||
|
||||
<!-- Calendar Type -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-navy mb-3">${t.settings.calendarType}</h3>
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="calendar-type" value="julian" ${this.settings.calendarType === 'julian' ? 'checked' : ''}
|
||||
class="accent-gold">
|
||||
<span class="text-sm text-navy">${t.settings.julian}</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="calendar-type" value="gregorian" ${this.settings.calendarType === 'gregorian' ? 'checked' : ''}
|
||||
class="accent-gold">
|
||||
<span class="text-sm text-navy">${t.settings.gregorian}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bible Settings -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-bold text-navy mb-3">${t.settings.bibleSettings}</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-sm font-bold text-navy mb-1">${t.settings.defaultTranslation}</label>
|
||||
<select id="default-bible" class="w-full border border-gold/30 rounded p-2 bg-white text-navy">
|
||||
<optgroup label="English">
|
||||
<option value="kjv" ${this.settings.defaultBibleVersion === 'kjv' ? 'selected' : ''}>KJV (${t.settings.bibleComments.fullBible})</option>
|
||||
<option value="brenton" ${this.settings.defaultBibleVersion === 'brenton' ? 'selected' : ''}>Brenton LXX (${t.settings.bibleComments.otOnly})</option>
|
||||
<option value="nonkjv" ${this.settings.defaultBibleVersion === 'nonkjv' ? 'selected' : ''}>Modern (${t.settings.bibleComments.ntOnly})</option>
|
||||
</optgroup>
|
||||
<optgroup label="Русский">
|
||||
<option value="synod" ${this.settings.defaultBibleVersion === 'synod' ? 'selected' : ''}>Синодальный (${t.settings.bibleComments.fullBible})</option>
|
||||
<option value="kassian" ${this.settings.defaultBibleVersion === 'kassian' ? 'selected' : ''}>Еп. Кассиана (${t.settings.bibleComments.ntOnly})</option>
|
||||
<option value="yungerov" ${this.settings.defaultBibleVersion === 'yungerov' ? 'selected' : ''}>Юнгеров (${t.settings.bibleComments.otProphets})</option>
|
||||
</optgroup>
|
||||
<optgroup label="Церковнославянский">
|
||||
<option value="elis" ${this.settings.defaultBibleVersion === 'elis' ? 'selected' : ''}>Елисаветинская (${t.settings.bibleComments.fullBible})</option>
|
||||
</optgroup>
|
||||
<optgroup label="Ἑλληνικά">
|
||||
<option value="spt" ${this.settings.defaultBibleVersion === 'spt' ? 'selected' : ''}>Ἡ Ἁγία Γραφή (${t.settings.bibleComments.fullBible})</option>
|
||||
</optgroup>
|
||||
<optgroup label="Français">
|
||||
<option value="ls" ${this.settings.defaultBibleVersion === 'ls' ? 'selected' : ''}>Louis Segond (${t.settings.bibleComments.fullBible})</option>
|
||||
</optgroup>
|
||||
<optgroup label="Latina">
|
||||
<option value="vulgate" ${this.settings.defaultBibleVersion === 'vulgate' ? 'selected' : ''}>Vulgata (${t.settings.bibleComments.fullBible})</option>
|
||||
</optgroup>
|
||||
<optgroup label="简体中文">
|
||||
<option value="cuv" ${this.settings.defaultBibleVersion === 'cuv' ? 'selected' : ''}>简体圣经 (${t.settings.bibleComments.fullBible})</option>
|
||||
</optgroup>
|
||||
<optgroup label="正體中文">
|
||||
<option value="cuv-hant" ${this.settings.defaultBibleVersion === 'cuv-hant' ? 'selected' : ''}>正體聖經 (${t.settings.bibleComments.fullBible})</option>
|
||||
</optgroup>
|
||||
<optgroup label="العربية">
|
||||
<option value="svd" ${this.settings.defaultBibleVersion === 'svd' ? 'selected' : ''}>Smith - van Dyck (${t.settings.bibleComments.fullBible})</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="show-verses" ${this.settings.showVerseNumbers ? 'checked' : ''}
|
||||
class="w-4 h-4 accent-gold">
|
||||
<label for="show-verses" class="text-sm text-navy">${t.settings.showVerseNumbers}</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="verse-newline" ${this.settings.verseNewLine ? 'checked' : ''}
|
||||
class="w-4 h-4 accent-gold">
|
||||
<label for="verse-newline" class="text-sm text-navy">${t.settings.verseNewLine}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Install PWA -->
|
||||
<div class="mb-6">
|
||||
<button id="install-pwa" class="w-full bg-navy text-parchment rounded p-3 font-bold hover:bg-navy-light transition-colors ${!('serviceWorker' in navigator) || window.matchMedia('(display-mode: standalone)').matches ? 'hidden' : ''}">
|
||||
${t.settings.installPwa}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- About -->
|
||||
<div class="mt-8 pt-6 border-t border-gold/20">
|
||||
<h3 class="text-lg font-bold text-navy mb-2">${t.settings.about}</h3>
|
||||
<p class="text-sm text-navy-light">
|
||||
${t.settings.aboutText}
|
||||
</p>
|
||||
<p class="text-xs text-navy-light mt-2">
|
||||
<a href="https://github.com/typiconman/ponomar" target="_blank" rel="noopener" class="underline hover:text-gold">
|
||||
${t.settings.aboutLicense}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('language')?.addEventListener('change', (e) => {
|
||||
this.settings.language = (e.target as HTMLSelectElement).value;
|
||||
saveSettings(this.settings);
|
||||
this.onLanguageChange?.();
|
||||
});
|
||||
|
||||
document.getElementById('cu-font')?.addEventListener('change', (e) => {
|
||||
this.settings.cuFont = (e.target as HTMLSelectElement).value;
|
||||
saveSettings(this.settings);
|
||||
this.updateFontPreview();
|
||||
});
|
||||
|
||||
document.getElementById('font-size')?.addEventListener('input', (e) => {
|
||||
this.settings.fontSize = parseInt((e.target as HTMLInputElement).value, 10);
|
||||
document.getElementById('font-size-value')!.textContent = `${this.settings.fontSize}px`;
|
||||
document.documentElement.style.setProperty('--liturgical-font-size', this.settings.fontSize + 'px');
|
||||
saveSettings(this.settings);
|
||||
});
|
||||
|
||||
document.getElementById('default-bible')?.addEventListener('change', (e) => {
|
||||
this.settings.defaultBibleVersion = (e.target as HTMLSelectElement).value;
|
||||
saveSettings(this.settings);
|
||||
});
|
||||
|
||||
document.getElementById('show-verses')?.addEventListener('change', (e) => {
|
||||
this.settings.showVerseNumbers = (e.target as HTMLInputElement).checked;
|
||||
saveSettings(this.settings);
|
||||
});
|
||||
|
||||
document.getElementById('verse-newline')?.addEventListener('change', (e) => {
|
||||
this.settings.verseNewLine = (e.target as HTMLInputElement).checked;
|
||||
saveSettings(this.settings);
|
||||
});
|
||||
|
||||
document.querySelectorAll('input[name="calendar-type"]').forEach(el => {
|
||||
el.addEventListener('change', (e) => {
|
||||
this.settings.calendarType = (e.target as HTMLInputElement).value as 'julian' | 'gregorian';
|
||||
saveSettings(this.settings);
|
||||
});
|
||||
});
|
||||
document.getElementById('install-pwa')?.addEventListener('click', async () => {
|
||||
const prompt = (window as any).__deferredPrompt;
|
||||
if (prompt) {
|
||||
prompt.prompt();
|
||||
await prompt.userChoice;
|
||||
(window as any).__deferredPrompt = null;
|
||||
document.getElementById('install-pwa')?.remove();
|
||||
}
|
||||
});
|
||||
this.updateFontPreview();
|
||||
}
|
||||
|
||||
private updateFontPreview() {
|
||||
const preview = document.getElementById('font-preview');
|
||||
if (preview) {
|
||||
preview.className = `${fontClass(this.settings.cuFont)} text-lg`;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user