diff --git a/data/extract.mjs b/data/extract.mjs
index 906b2c5..b0c6814 100644
--- a/data/extract.mjs
+++ b/data/extract.mjs
@@ -115,13 +115,18 @@ async function details(ids) {
for (;;) {
const id = queue.shift()
if (id === undefined) return
- const text = await fetchPolite(`${BASE}detail?id=${id}`)
- JSON.parse(text)
- writeFileSync(`${CACHE}/detail/${id}.json`, text)
+ try {
+ const text = await fetchPolite(`${BASE}detail?id=${id}`)
+ JSON.parse(text)
+ writeFileSync(`${CACHE}/detail/${id}.json`, text)
+ } catch (err) {
+ // M6: log individual 4xx/parse failures without aborting the whole batch
+ process.stderr.write(` detail ${id}: ${err.message}\n`)
+ }
if (++done % 200 === 0) process.stderr.write(` detail ${done}/${queue.length + done}\n`)
}
}
- await Promise.all(Array.from({ length: 4 }, worker))
+ await Promise.allSettled(Array.from({ length: 4 }, worker))
process.stderr.write(`details: ${ids.length} churches cached\n`)
}
@@ -202,7 +207,9 @@ function transform() {
p: inst.institution_parish_name ?? '',
pa: inst.institution_parish_address ?? '',
// parish-level contacts only; named persons are deliberately not published
- c: (d.contacts ?? []).filter((c) => c.type && c.contact).map((c) => [c.type, c.contact]),
+ // normUrl fixes typo'd schemes (http:\\, http:/) for www contacts.
+ c: (d.contacts ?? []).filter((c) => c.type && c.contact).map((c) =>
+ [c.type, c.type === 'www' ? (normUrl(c.contact) || c.contact) : c.contact]),
s: regular.map(svcRow),
}
// the detail page's Farnost section reads the shard contacts — surface the
@@ -218,7 +225,7 @@ function transform() {
// and shows `generated` as the "aktuální k …" date. (src/lib/dataStore.ts)
writeFileSync(
`${OUT}/version.json`,
- JSON.stringify({ generated: new Date().toISOString().slice(0, 10), churches: index.length }),
+ JSON.stringify({ generated: new Date().toISOString().slice(0, 10), timestamp: new Date().toISOString(), churches: index.length }),
)
let total = statSync(`${OUT}/churches.json`).size
for (const [cell, data] of Object.entries(shards)) {
diff --git a/data/match-photos.mjs b/data/match-photos.mjs
index af303bd..c233213 100644
--- a/data/match-photos.mjs
+++ b/data/match-photos.mjs
@@ -179,7 +179,9 @@ async function main() {
if (args.cell) churches = churches.filter((c) => c.cell.startsWith(args.cell))
churches = churches.slice(0, args.limit)
- const photos = {}
+ // Load existing results so --cell / --limit runs add to, not replace, the file.
+ let photos = {}
+ try { photos = JSON.parse(readFileSync(`${root}public/data/photos.json`, 'utf8')) } catch { /* new file */ }
const licenses = {}
let processed = 0, matched = 0, hadCandidate = 0
for (const c of churches) {
diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj
index ed9190b..35c956b 100644
--- a/ios/App/App.xcodeproj/project.pbxproj
+++ b/ios/App/App.xcodeproj/project.pbxproj
@@ -296,7 +296,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 68;
+ CURRENT_PROJECT_VERSION = 76;
DEVELOPMENT_TEAM = 8L39RHYGVY;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Bohoslužby";
@@ -321,7 +321,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 68;
+ CURRENT_PROJECT_VERSION = 76;
DEVELOPMENT_TEAM = 8L39RHYGVY;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Bohoslužby";
diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist
index ebcdda8..b5c6952 100644
--- a/ios/App/App/Info.plist
+++ b/ios/App/App/Info.plist
@@ -26,7 +26,7 @@
+ {t('partial_data')} +
+ )} {!dataError && route.view === 'church' && index && (@@ -909,6 +914,7 @@ export default function App() { onChange={updateFilters} langs={langs} onReset={resetAll} + clockTick={clockTick} /> {/* not on a live fix (offline / last-known / picked city): search is the main CTA — a visible input-shaped button, not a buried "změnit" link */} @@ -949,6 +955,7 @@ export default function App() { onOpen={openChurch} onNavigate={setNavTarget} fill={mapMode} + reloadKey={reloadKey} /> @@ -970,7 +977,7 @@ export default function App() { onNavigate={setNavTarget} /> {/* the cap is honest: another page of the ordo instead of "evening ends at 18:00" */} - {day === 'now' && rows.length >= listLimit && ( + {(day === 'now' || witnessTags.length > 0) && rows.length >= listLimit && (
- {seasonLabel(season.season)}
+ {t(`season_${season.season}`)}
{' · '}
{lang() === 'cs' ? 'Data: rejstřík' : 'Data: registry'}{' '}
@@ -1057,8 +1064,8 @@ export default function App() {
// The day rubric of the ordo, as a picker: which page are you reading?
// Active day is set in rubric red — day labels are rubrics in a missal.
-function DayPicker({ day, onChange }: { day: DayChoice; onChange: (d: DayChoice) => void }) {
- const options = useMemo(() => dayOptions(new Date()), [])
+function DayPicker({ day, onChange, clockTick = 0 }: { day: DayChoice; onChange: (d: DayChoice) => void; clockTick?: number }) {
+ const options = useMemo(() => dayOptions(new Date()), [clockTick])
// a bookmarked ?den= must not hide its own chip off-screen
const activeRef = useRef {t('day_group').toLowerCase()} {t('rubric_when')}
{t('photo_credit_prefix')}{' '}
- {[photo.credit.split(' (')[0].trim(), photo.license, 'Wikimedia Commons']
+ {[heroPhoto.credit.split(' (')[0].trim(), heroPhoto.license, 'Wikimedia Commons']
.filter(Boolean)
.join(' · ')}
@@ -786,16 +784,9 @@ function ServiceRow({
// Uncertain notes never mute either — they already print loud instead.
const pausedNow = (() => {
if (!s.note) return false
- const rule = parseNote(s.note)
- if (rule.uncertain) return false
- const upcoming = nextOccurrences({ days: s.days, time: s.time }, new Date(), 35)
- return (
- upcoming.length > 0 &&
- upcoming.every((start) => {
- const w = pragueToday(start)
- return !rule.runsOn(w.y, w.m, w.d)
- })
- )
+ if (noteUncertain(s.note)) return false
+ const upcoming = nextOccurrences(s, new Date(), 35)
+ return upcoming.length > 0 && upcoming.every((start) => !noteRunsOn(s.note, start))
})()
return (