Versionen je Thema
Ein Thema hat Versionen storage/<Thema>/v<N>/ mit eigenem Lauf, Buch und Leseposition; bestehende Ablagen wandern beim Start nach v1/. Intern ist „Thema/vN“ der Schlüssel aller Zustandsfunktionen. API: ?v=N an allen Themen-Endpunkten, POST /version, DELETE ?v=N. Oberfläche: Versionsliste unter dem Thema (Klick auf Thema → Lauf der neuesten, auf Version → Buch), Knopf „Neue Version“ mit gleichen Eingaben. make loeschen kennt V=<Nr>. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { api } from '../api.js'
|
||||
import { render, zeit } from '../markdown.js'
|
||||
import Lesemodus from './Lesemodus.vue'
|
||||
|
||||
const props = defineProps({ topic: String, laeuft: Boolean, fortschritt: Number })
|
||||
const props = defineProps({ topic: String, version: Number, laeuft: Boolean, fortschritt: Number })
|
||||
const daten = ref(null)
|
||||
const lesemodus = ref(false)
|
||||
// Vollbild nur aus der Klickgeste heraus (Browser-Vorgabe); iOS-Safari kennt die API nicht.
|
||||
@@ -39,7 +39,7 @@ let ladeSeq = 0
|
||||
let merkTimer = null
|
||||
function merken(id) {
|
||||
clearTimeout(merkTimer)
|
||||
merkTimer = setTimeout(() => api.positionSetzen(props.topic, { abschnitt: id }).catch(() => {}), 1500)
|
||||
merkTimer = setTimeout(() => api.positionSetzen(props.topic, { abschnitt: id }, props.version).catch(() => {}), 1500)
|
||||
}
|
||||
const cache = new Map() // Schlüssel: Abschnitt + Textlänge → unveränderte Abschnitte werden nicht neu gerendert
|
||||
|
||||
@@ -48,7 +48,7 @@ onUnmounted(() => { beobachter?.disconnect() })
|
||||
async function laden() {
|
||||
if (!props.topic) { daten.value = null; return }
|
||||
const seq = ++ladeSeq
|
||||
const [d, pos] = await Promise.all([api.guide(props.topic), api.position(props.topic).catch(() => ({}))])
|
||||
const [d, pos] = await Promise.all([api.guide(props.topic, props.version), api.position(props.topic, props.version).catch(() => ({}))])
|
||||
if (seq !== ladeSeq) return
|
||||
daten.value = d
|
||||
await nextTick()
|
||||
@@ -57,7 +57,7 @@ async function laden() {
|
||||
const ziel = location.hash.slice(1) || pos.abschnitt
|
||||
if (ziel && !aktiv.value) document.getElementById(ziel)?.scrollIntoView({ block: 'start' })
|
||||
}
|
||||
watch(() => props.topic, () => { aktiv.value = ''; cache.clear(); laden() }, { immediate: true })
|
||||
watch(() => [props.topic, props.version], () => { aktiv.value = ''; cache.clear(); laden() }, { immediate: true })
|
||||
watch(() => props.laeuft, (l, alt) => { if (alt && !l) laden() })
|
||||
// Während eines Laufs nur nachladen, wenn sich der Fortschritt geändert hat
|
||||
watch(() => props.fortschritt, (n, alt) => { if (props.laeuft && n !== alt) laden() })
|
||||
@@ -116,8 +116,8 @@ onUnmounted(() => window.removeEventListener('keydown', taste))
|
||||
<button class="schliessen sekundaer klein" @click="navAuf = false">✕ Schließen</button>
|
||||
<div class="werkzeuge">
|
||||
<button class="klein" title="Lesemodus: Abschnitt für Abschnitt" @click="lesemodusStart" :disabled="!abschnitte.length">Lesen ▶</button>
|
||||
<a class="knopf klein sekundaer" title="Als Markdown-Datei herunterladen" :href="api.guideMdUrl(topic)" download>MD</a>
|
||||
<a v-if="daten?.pdf" class="knopf klein sekundaer" title="Als PDF herunterladen" :href="api.pdfUrl(topic)" download>PDF</a>
|
||||
<a class="knopf klein sekundaer" title="Als Markdown-Datei herunterladen" :href="api.guideMdUrl(topic, version)" download>MD</a>
|
||||
<a v-if="daten?.pdf" class="knopf klein sekundaer" title="Als PDF herunterladen" :href="api.pdfUrl(topic, version)" download>PDF</a>
|
||||
</div>
|
||||
<template v-if="daten">
|
||||
<div class="meta">
|
||||
@@ -142,8 +142,8 @@ onUnmounted(() => window.removeEventListener('keydown', taste))
|
||||
<div class="mobil-leiste">
|
||||
<button class="sekundaer klein" @click="navAuf = !navAuf">☰ Inhalt</button>
|
||||
<button class="klein" @click="lesemodusStart" :disabled="!abschnitte.length">Lesen ▶</button>
|
||||
<a class="knopf klein sekundaer" :href="api.guideMdUrl(topic)" download>MD</a>
|
||||
<a v-if="daten?.pdf" class="knopf klein sekundaer" :href="api.pdfUrl(topic)" download>PDF</a>
|
||||
<a class="knopf klein sekundaer" :href="api.guideMdUrl(topic, version)" download>MD</a>
|
||||
<a v-if="daten?.pdf" class="knopf klein sekundaer" :href="api.pdfUrl(topic, version)" download>PDF</a>
|
||||
</div>
|
||||
<article v-if="daten" class="buch">
|
||||
<h1>{{ daten.titel }}</h1>
|
||||
@@ -190,6 +190,6 @@ onUnmounted(() => window.removeEventListener('keydown', taste))
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<Lesemodus v-if="lesemodus && daten" :daten="daten" :topic="topic" @schliessen="lesemodusEnde" />
|
||||
<Lesemodus v-if="lesemodus && daten" :daten="daten" :topic="topic" :version="version" @schliessen="lesemodusEnde" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed } from 'vue'
|
||||
import { render } from '../markdown.js'
|
||||
|
||||
const props = defineProps({ state: Object })
|
||||
const emit = defineEmits(['start', 'pause', 'reset'])
|
||||
const emit = defineEmits(['start', 'pause', 'reset', 'neue-version'])
|
||||
|
||||
const laeuft = computed(() => props.state?.status === 'laeuft')
|
||||
const fertig = computed(() => props.state?.status === 'fertig')
|
||||
@@ -36,6 +36,7 @@ const gruppen = computed(() => {
|
||||
const ereignisse = computed(() => (props.state?.ereignisse || []).slice(-100).reverse())
|
||||
|
||||
function reset() { if (confirm('Lauf zurücksetzen? Alles außer dem Thema wird gelöscht.')) emit('reset') }
|
||||
function neueVersion() { if (confirm('Neue Version erzeugen? Gleiche Eingaben, kompletter neuer Lauf. Die bisherige Version bleibt erhalten.')) emit('neue-version') }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,6 +45,7 @@ function reset() { if (confirm('Lauf zurücksetzen? Alles außer dem Thema wird
|
||||
<button v-if="!laeuft && !fertig" @click="emit('start')">{{ begonnen ? 'Fortsetzen' : 'Buch erzeugen' }}</button>
|
||||
<button v-if="laeuft" class="sekundaer" @click="emit('pause')">Pause</button>
|
||||
<button v-if="!laeuft && begonnen && !fertig" class="sekundaer" @click="reset">Zurücksetzen</button>
|
||||
<button v-if="!laeuft && begonnen" class="sekundaer" title="Gleiches Thema, gleiche Hinweise, neuer Lauf" @click="neueVersion">Neue Version</button>
|
||||
<span class="spacer"></span>
|
||||
<span class="badge" :class="statusFarbe[state.status]">{{ statusText[state.status] || state.status }}<template v-if="state.stufe && laeuft"> · {{ state.stufe }}</template></span>
|
||||
<span class="badge">{{ usd(state.kosten_usd) }}</span>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { render, zeit } from '../markdown.js'
|
||||
import { api } from '../api.js'
|
||||
|
||||
const props = defineProps({ daten: Object, topic: String })
|
||||
const props = defineProps({ daten: Object, topic: String, version: Number })
|
||||
const emit = defineEmits(['schliessen'])
|
||||
const buehne = ref(null)
|
||||
const pos = ref(0)
|
||||
@@ -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.topic, { lesemodus: key }).catch(() => {}), 1500)
|
||||
if (key) merkTimer = setTimeout(() => api.positionSetzen(props.topic, { lesemodus: key }, props.version).catch(() => {}), 1500)
|
||||
})
|
||||
|
||||
function taste(e) {
|
||||
@@ -82,7 +82,7 @@ function wischEnde(e) {
|
||||
}
|
||||
onMounted(async () => {
|
||||
window.addEventListener('keydown', taste)
|
||||
const merk = (await api.position(props.topic).catch(() => ({}))).lesemodus
|
||||
const merk = (await api.position(props.topic, props.version).catch(() => ({}))).lesemodus
|
||||
// Alte Positionen waren ein Index, neue sind ein key.
|
||||
const 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)
|
||||
|
||||
Reference in New Issue
Block a user