abandoned conversion from original Java Ponomar app, migrated to own text storage format

This commit is contained in:
catarrh committed 2026-07-26 00:20:14 +03:00
1 parent edad73662c
commit 07ff6b5890
5 files changed
+119 -39

No files matched your search

+7 -2
View File
@@ -4,10 +4,15 @@
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "npm run convert && vite dev",
"build": "npm run convert && vite build",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"convert": "tsx scripts/convert-all.ts",
"migrate": "tsx scripts/migrate-data.ts",
"migrate:dry": "tsx scripts/migrate-data.ts --dry-run",
"validate": "tsx scripts/validate-data.ts",
"search": "tsx scripts/tools/search-texts.ts",
"stats": "tsx scripts/tools/stats.ts",
"check": "svelte-kit sync 2>/dev/null; tsc --noEmit"
},
"devDependencies": {
+2 -2
View File
@@ -133,7 +133,7 @@ export class BibleView {
</div>
</div>
<div id="bible-text" class="flex-1 overflow-auto p-2 max-w-3xl mx-auto liturgical-text">
<div id="bible-text" class="flex-1 overflow-auto p-2 max-w-3xl lg:max-w-4xl xl:max-w-5xl mx-auto liturgical-text">
<p class="text-navy-light italic">${this.t.bible.selectBook}</p>
</div>
</div>
@@ -267,7 +267,7 @@ export class BibleView {
const isChurchSlavonic = version?.language === 'cu';
const fnClass = isChurchSlavonic ? fontClass(settings.cuFont) : '';
textEl.className = `flex-1 overflow-auto p-2 max-w-3xl mx-auto liturgical-text ${fnClass}`;
textEl.className = `flex-1 overflow-auto p-2 max-w-3xl lg:max-w-4xl xl:max-w-5xl mx-auto liturgical-text ${fnClass}`;
textEl.innerHTML = html || `<p class="text-navy-light">${this.t.bible.noTextFound}</p>`;
} catch {
textEl.innerHTML = `
+47 -26
View File
@@ -18,6 +18,8 @@ let sharedScriptureIndex: Record<string, unknown[]> | null = null;
let bookAbbrevMap: Record<string, string> | null = null;
let bookCVSep = ':';
let bookAbbrevLang = '';
let cachedTriodion: Record<string, unknown> | null = null;
let cachedPentecostarion: Record<string, unknown> | null = null;
function resolveDefaultBibleVersion(): string {
try {
@@ -130,8 +132,7 @@ interface LifeData {
name?: {
nominative?: string;
short?: string;
'@_Nominative'?: string;
'@_Short'?: string;
shortF?: string;
};
life?: {
text: string;
@@ -307,11 +308,11 @@ async function loadCommemoration(sharedId: string): Promise<CommemorationData |
function getPeriodName(life: LifeData | undefined): string {
if (!life?.name) return '';
return life.name['@_Nominative'] || life.name.nominative || life.name['@_Short'] || life.name.short || '';
return life.name.nominative || life.name.short || '';
}
function getShortFeastName(life: LifeData | undefined, shared: CommemorationData | null | undefined): string {
if (life?.name?.['@_Short']) return life.name['@_Short'];
if (life?.name?.short) return life.name.short;
if (life?.name?.short) return life.name.short;
return getCommsName(shared);
}
@@ -347,24 +348,46 @@ async function loadMenaion(lang: LanguageCode, month: number, day: number): Prom
}
}
async function loadTriodionFile(index: number): Promise<unknown[]> {
async function loadTriodionData(): Promise<Record<string, unknown>> {
if (cachedTriodion) return cachedTriodion;
try {
const resp = await fetch(`/data/shared/triodion/${String(index).padStart(2, '0')}.json`);
if (!resp.ok) return [];
return await resp.json();
const resp = await fetch('/data/shared/calendar/triodion.json');
if (!resp.ok) return {};
const ct = resp.headers.get('content-type');
if (ct && ct.includes('text/html')) return {};
cachedTriodion = await resp.json();
return cachedTriodion!;
} catch {
return [];
return {};
}
}
async function loadTriodionFile(index: number): Promise<unknown[]> {
const data = await loadTriodionData();
const key = String(index).padStart(2, '0');
const entry = data[key];
return entry ? [entry] : [];
}
async function loadPentecostarionData(): Promise<Record<string, unknown>> {
if (cachedPentecostarion) return cachedPentecostarion;
try {
const resp = await fetch('/data/shared/calendar/pentecostarion.json');
if (!resp.ok) return {};
const ct = resp.headers.get('content-type');
if (ct && ct.includes('text/html')) return {};
cachedPentecostarion = await resp.json();
return cachedPentecostarion!;
} catch {
return {};
}
}
async function loadPentecostarionFile(index: number): Promise<unknown[]> {
try {
const resp = await fetch(`/data/shared/pentecostarion/${String(index).padStart(2, '0')}.json`);
if (!resp.ok) return [];
return await resp.json();
} catch {
return [];
}
const data = await loadPentecostarionData();
const key = String(index).padStart(2, '0');
const entry = data[key];
return entry ? [entry] : [];
}
export class CalendarView {
@@ -544,7 +567,7 @@ export class CalendarView {
</div>
<div class="flex-1 p-6 overflow-auto">
<div class="max-w-2xl">
<div class="max-w-2xl lg:max-w-3xl xl:max-w-4xl">
<h2 class="text-base font-bold text-navy mb-1">
${julianStr}
</h2>
@@ -653,7 +676,7 @@ export class CalendarView {
for (const entry of allEntries) {
if (typeof entry === 'object' && entry !== null) {
const e = entry as Record<string, unknown>;
if (e.CId) cids.push(String(e.CId));
if (e.id) cids.push(String(e.id));
}
}
@@ -682,13 +705,11 @@ export class CalendarView {
let feastName = '';
let feastRank = 0;
// Pass 1: Find period name from primary calendar entry (SId ''/'0' + CId 9[0-38]xxxx)
// Pass 1: Find period name from primary calendar entry (id 9[0-38]xxxx)
for (const entry of calendarEntries) {
const e = entry as Record<string, unknown>;
const cid = String(e.CId || '');
const sid = String(e.SId ?? '');
const cid = String(e.id || '');
if (!cid || !/^9[0-38]\d{2}$/.test(cid)) continue;
if (sid !== '' && sid !== '0') continue;
const name = getPeriodName(livesData.get(cid)) || getCommsName(sharedComms.get(cid));
if (!name) continue;
periodName = name;
@@ -699,7 +720,7 @@ export class CalendarView {
// Pass 2: Find great feast (rank >= 6) from all entries
for (const entry of allEntries) {
const e = entry as Record<string, unknown>;
const cid = String(e.CId || '');
const cid = String(e.id || '');
if (!cid || this.skipCids.has(cid)) continue;
const life = livesData.get(cid);
const shared = sharedComms.get(cid);
@@ -749,7 +770,7 @@ export class CalendarView {
let name = '';
if (life?.name) {
name = life.name['@_Nominative'] || life.name.nominative || life.name['@_Short'] || life.name.short || '';
name = life.name.nominative || life.name.short || '';
}
if (!name && shared?.NAME) {
if (typeof shared.NAME === 'object' && 'Long' in shared.NAME) {
@@ -811,7 +832,7 @@ export class CalendarView {
if (cid && isPeriodCid(cid)) return '';
return ` (${shortName})`;
};
loadBookAbbrevs(this.language);
await loadBookAbbrevs(this.language);
for (const [cid, shared] of sharedComms) {
if (shared?.SCRIPTURE) {
const shortN = typeof shared.NAME === 'object' && 'ShortN' in shared.NAME ? (shared.NAME.ShortN || '') : '';
@@ -830,7 +851,7 @@ export class CalendarView {
}
// Also load readings from saint lives (from services structure)
for (const [cid, life] of livesData) {
const shortName = life?.name?.['@_Short'] || life?.name?.short || '';
const shortName = life?.name?.short || '';
const tag = readingContext(shortName, cid);
if (life?.services) {
+62 -8
View File
@@ -88,7 +88,7 @@ export class ServiceView {
`;
this.container.innerHTML = standalone
? `<div class="p-6 max-w-4xl mx-auto">
? `<div class="p-6 max-w-4xl xl:max-w-6xl mx-auto">
<h2 class="text-2xl font-bold text-navy mb-2">${this.t.services.title}</h2>
<p class="text-navy-light mb-4">
${this.currentDate.toString()}
@@ -183,7 +183,7 @@ export class ServiceView {
try {
const tryFetch = async (lang: string) => {
const url = `/data/${lang}/services/Octoecheos/Tone ${toneNum}/${weekday}.json`;
const url = `/data/${lang}/services/octoecheos/tone${toneNum}/${weekday.toLowerCase()}.json`;
const resp = await fetch(url);
if (!resp.ok) return null;
const nodes: ServiceNode[] = await resp.json();
@@ -214,9 +214,9 @@ export class ServiceView {
const resp = await fetch(url);
if (!resp.ok) return null;
const all = await resp.json();
if (Array.isArray(all)) return all.map((e: Record<string, unknown>) => ({ cid: String(e.CId ?? '') }));
if (Array.isArray(all)) return all.map((e: Record<string, unknown>) => ({ cid: String(e.id ?? '') }));
const entries = all[dateKey] as Record<string, unknown>[] | undefined;
if (Array.isArray(entries)) return entries.map((e: Record<string, unknown>) => ({ cid: String(e.CId ?? '') }));
if (Array.isArray(entries)) return entries.map((e: Record<string, unknown>) => ({ cid: String(e.id ?? '') }));
return null;
} catch { return null; }
};
@@ -226,12 +226,24 @@ export class ServiceView {
const menaionEntries = await tryLoad(`/data/${this.serviceLang}/menaion-bundle.json`);
if (menaionEntries) allEntries.push(...menaionEntries);
// Load from merged calendar files
const loadCalendarEntry = async (file: string, index: number): Promise<{ cid: string }[] | null> => {
try {
const resp = await fetch(`/data/shared/calendar/${file}.json`);
if (!resp.ok) return null;
const data = await resp.json();
const key = String(index).padStart(2, '0');
const entry = data[key];
return entry ? [{ cid: String(entry.id ?? '') }] : null;
} catch { return null; }
};
if (dayInfo.triodionFile !== null) {
const triodion = await tryLoad(`/data/shared/triodion/${dayInfo.triodionFile}.json`);
const triodion = await loadCalendarEntry('triodion', dayInfo.triodionFile);
if (triodion) allEntries.push(...triodion);
}
if (dayInfo.pentecostarionFile !== null) {
const pent = await tryLoad(`/data/shared/pentecostarion/${dayInfo.pentecostarionFile}.json`);
const pent = await loadCalendarEntry('pentecostarion', dayInfo.pentecostarionFile);
if (pent) allEntries.push(...pent);
}
@@ -608,7 +620,28 @@ export class ServiceView {
const resolvePath = (lang: string): string | null => {
let fetchPath = basePath;
if (fetchPath.startsWith('Services/')) {
fetchPath = fetchPath.replace('Services/', '').replace('.xml', '.json');
fetchPath = fetchPath.slice('Services/'.length);
// Handle different path patterns
if (fetchPath.startsWith('CommonPrayers/')) {
// CommonPrayers -> prayers
fetchPath = 'prayers/' + fetchPath.slice('CommonPrayers/'.length);
} else if (fetchPath.startsWith('Text/')) {
// Text -> texts
fetchPath = 'texts/' + fetchPath.slice('Text/'.length);
} else if (fetchPath.startsWith('Command/')) {
// Command -> commands
fetchPath = 'commands/' + fetchPath.slice('Command/'.length);
} else if (fetchPath.startsWith('Header/')) {
// Header -> headers
fetchPath = 'headers/' + fetchPath.slice('Header/'.length);
} else if (fetchPath.startsWith('Var/')) {
// Var -> var (lowercase)
fetchPath = 'var/' + fetchPath.slice('Var/'.length);
} else if (fetchPath.startsWith('Octoecheos/')) {
// Octoecheos -> octoecheos
fetchPath = 'octoecheos/' + fetchPath.slice('Octoecheos/'.length);
}
fetchPath = fetchPath.replace('.xml', '.json');
}
return `/data/${lang}/services/${fetchPath}`;
};
@@ -673,7 +706,28 @@ export class ServiceView {
const resolvePath = (lang: string): string | null => {
let fetchPath = path;
if (fetchPath.startsWith('Services/')) {
fetchPath = fetchPath.replace('Services/', '').replace('.xml', '.json');
fetchPath = fetchPath.slice('Services/'.length);
// Service templates are in templates/ subdirectory
// But if path contains a subdirectory, don't add templates/ prefix
if (!fetchPath.includes('/')) {
fetchPath = 'templates/' + fetchPath;
} else {
// Handle subdirectories like Var/, CommonPrayers/, etc.
if (fetchPath.startsWith('CommonPrayers/')) {
fetchPath = 'prayers/' + fetchPath.slice('CommonPrayers/'.length);
} else if (fetchPath.startsWith('Text/')) {
fetchPath = 'texts/' + fetchPath.slice('Text/'.length);
} else if (fetchPath.startsWith('Command/')) {
fetchPath = 'commands/' + fetchPath.slice('Command/'.length);
} else if (fetchPath.startsWith('Header/')) {
fetchPath = 'headers/' + fetchPath.slice('Header/'.length);
} else if (fetchPath.startsWith('Var/')) {
fetchPath = 'var/' + fetchPath.slice('Var/'.length);
} else if (fetchPath.startsWith('Octoecheos/')) {
fetchPath = 'octoecheos/' + fetchPath.slice('Octoecheos/'.length);
}
}
fetchPath = fetchPath.replace('.xml', '.json');
}
return `/data/${lang}/services/${fetchPath}`;
};
+1 -1
View File
@@ -111,7 +111,7 @@ export class SettingsView {
const t = getTranslations(this.settings.language as LanguageCode);
this.container.innerHTML = `
<div class="p-6 max-w-2xl mx-auto">
<div class="p-6 max-w-2xl lg:max-w-3xl xl:max-w-4xl mx-auto">
<h2 class="text-2xl font-bold text-navy mb-6">${t.settings.title}</h2>
<!-- Language -->