Lesemodus auf Mobilgeräten und Start an der zuletzt gelesenen Stelle
- Der Scroll-Beobachter schaltete beim Wechsel ins Vollbild den Tab zurück auf „Buch“; er schweigt jetzt im Lesemodus, und das nr-Ereignis erzwingt keinen Tab mehr. - Die Leseposition merkt sich, welche Ansicht zuletzt gelesen wurde. Buch- ansicht und Lesemodus starten beide an dieser Stelle; die Adresse zählt nur noch als Rückfall. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ class ThemaNeu(BaseModel):
|
||||
class Position(BaseModel):
|
||||
abschnitt: str | None = None
|
||||
lesemodus: str | int | None = None # neu: key des Bildschirms, alt: Index
|
||||
zuletzt: str | None = None # 'buch' oder 'lesen': welche Ansicht zuletzt gelesen wurde
|
||||
|
||||
|
||||
def _thema(slug: str) -> dict:
|
||||
|
||||
@@ -201,7 +201,7 @@ function tokens(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? M
|
||||
@neue-version="neueVersion" />
|
||||
<Guide v-else-if="tab !== 'lauf' && slug && versionAktiv" :slug="slug" :version="versionAktiv" :tab="tab" :nr="nr"
|
||||
:laeuft="laeuft" :fortschritt="fortschritt"
|
||||
@tab="(t) => geheZu({ tab: t })" @nr="(e) => geheZu({ tab: 'buch', nr: e.nr }, !e.push)" />
|
||||
@tab="(t) => geheZu({ tab: t })" @nr="(e) => geheZu({ nr: e.nr }, !e.push)" />
|
||||
<div v-else style="padding: 24px; color: var(--dim)">Thema links anlegen oder wählen.</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,17 @@ function idVonNr(nr) {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
// Lesemodus-Schlüssel (vorwort, k3, a03-4:2, ende) → Element-ID der Buchansicht, wenn zuletzt dort gelesen wurde
|
||||
function idVonLesemodus(pos) {
|
||||
if (pos.zuletzt !== 'lesen' || !pos.lesemodus || !daten.value) return ''
|
||||
const m = pos.lesemodus
|
||||
if (m === 'vorwort') return 'vorwort'
|
||||
const kap = /^k(\d+)$/.exec(m)
|
||||
if (kap) return 'k' + (daten.value.kapitel[Number(kap[1]) - 1]?.id || '')
|
||||
const abs = /^(a[\d-]+):/.exec(m)
|
||||
if (abs) return abs[1]
|
||||
return ''
|
||||
}
|
||||
function nrVonId(id) {
|
||||
if (!id || !daten.value) return ''
|
||||
if (id === 'vorwort') return 'vorwort'
|
||||
@@ -61,7 +72,7 @@ let ladeSeq = 0
|
||||
let merkTimer = null
|
||||
function merken(id) {
|
||||
clearTimeout(merkTimer)
|
||||
merkTimer = setTimeout(() => api.positionSetzen(props.slug, props.version, { abschnitt: id }).catch(() => {}), 1500)
|
||||
merkTimer = setTimeout(() => api.positionSetzen(props.slug, props.version, { abschnitt: id, zuletzt: 'buch' }).catch(() => {}), 1500)
|
||||
}
|
||||
const cache = new Map() // Schlüssel: Abschnitt + Textlänge → unveränderte Abschnitte werden nicht neu gerendert
|
||||
|
||||
@@ -75,8 +86,8 @@ async function laden() {
|
||||
daten.value = d
|
||||
await nextTick()
|
||||
spy()
|
||||
// Abschnitt in der Adresse hat Vorrang vor der gemerkten Position; beides nur beim ersten Laden
|
||||
const ziel = idVonNr(props.nr) || pos.abschnitt
|
||||
// Gemerkte Position hat Vorrang (die Adresse spiegelt nur den letzten Scrollstand); zuletzt im Lesemodus gelesen → dessen Stelle
|
||||
const ziel = idVonLesemodus(pos) || pos.abschnitt || idVonNr(props.nr)
|
||||
if (ziel && !aktiv.value) document.getElementById(ziel)?.scrollIntoView({ block: 'start' })
|
||||
}
|
||||
watch(() => [props.slug, props.version], () => { aktiv.value = ''; cache.clear(); laden() }, { immediate: true })
|
||||
@@ -118,6 +129,7 @@ const kapBadge = { geschrieben: ['gelb', 'geschrieben'], geprueft: ['blau', 'gep
|
||||
function spy() {
|
||||
beobachter?.disconnect()
|
||||
beobachter = new IntersectionObserver((es) => {
|
||||
if (lesemodus.value) return
|
||||
for (const e of es) if (e.isIntersecting) { aktiv.value = e.target.id; merken(e.target.id); emit('nr', { nr: nrVonId(e.target.id), push: false }) }
|
||||
}, { root: lese.value, rootMargin: '0px 0px -75% 0px' })
|
||||
document.querySelectorAll('.buch [data-spy]').forEach((el) => beobachter.observe(el))
|
||||
|
||||
@@ -53,7 +53,7 @@ watch(pos, (p) => {
|
||||
buehne.value?.scrollTo({ top: 0 })
|
||||
clearTimeout(merkTimer)
|
||||
const key = screens.value[p]?.key
|
||||
if (key) merkTimer = setTimeout(() => api.positionSetzen(props.slug, props.version, { lesemodus: key }).catch(() => {}), 1500)
|
||||
if (key) merkTimer = setTimeout(() => api.positionSetzen(props.slug, props.version, { lesemodus: key, zuletzt: 'lesen' }).catch(() => {}), 1500)
|
||||
})
|
||||
|
||||
function taste(e) {
|
||||
@@ -82,9 +82,16 @@ function wischEnde(e) {
|
||||
}
|
||||
onMounted(async () => {
|
||||
window.addEventListener('keydown', taste)
|
||||
const merk = (await api.position(props.slug, props.version).catch(() => ({}))).lesemodus
|
||||
const p = await api.position(props.slug, props.version).catch(() => ({}))
|
||||
const merk = p.lesemodus
|
||||
// Zuletzt in der Buchansicht gelesen → beim passenden Abschnitt bzw. Kapitel beginnen
|
||||
let i = -1
|
||||
if (p.zuletzt === 'buch' && p.abschnitt) {
|
||||
const a = p.abschnitt
|
||||
i = screens.value.findIndex((s) => s.key === a || s.key.startsWith(a + ':') || (s.typ === 'kapitel' && 'k' + s.kap.id === a))
|
||||
}
|
||||
// Alte Positionen waren ein Index, neue sind ein key.
|
||||
const i = typeof merk === 'string' ? screens.value.findIndex((s) => s.key === merk) : Number(merk || 0)
|
||||
if (i < 0) i = typeof merk === 'string' ? screens.value.findIndex((s) => s.key === merk) : Number(merk || 0)
|
||||
pos.value = Math.min(Math.max(i, 0), screens.value.length - 1)
|
||||
})
|
||||
onUnmounted(() => window.removeEventListener('keydown', taste))
|
||||
|
||||
Reference in New Issue
Block a user