update
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<script setup>
|
||||
import BausteinPanel from './BausteinPanel.vue'
|
||||
import { renderMarkdown } from '../markdown.js'
|
||||
import { renderMarkdown, renderBloecke } from '../markdown.js'
|
||||
import { stufeFuer, STUFEN } from '../stufen.js'
|
||||
import { pruefeBlock, uebernehmeBlock } from '../api.js'
|
||||
|
||||
const props = defineProps({
|
||||
baustein: { type: Object, required: true }, // { title, md, num }
|
||||
@@ -9,6 +10,7 @@ const props = defineProps({
|
||||
provider: { type: String, default: 'claude' },
|
||||
status: { type: Object, default: null },
|
||||
cap: { type: Number, default: 6 },
|
||||
guideId: { type: String, default: '' },
|
||||
tab: { type: String, default: 'pruefung' },
|
||||
ansicht: { type: String, default: 'kompakt' }, // kompakt | erklärend — linke Guide-Spalte
|
||||
hasPrev: { type: Boolean, default: false },
|
||||
@@ -16,9 +18,9 @@ const props = defineProps({
|
||||
fortschritt: { type: Object, default: () => ({ total: 0, anfaenger: 0, fortgeschritten: 0, experte: 0, meister: 0 }) },
|
||||
})
|
||||
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
|
||||
const emit = defineEmits(['close', 'prev', 'next', 'statusChanged', 'setAnsicht', 'openSidebar'])
|
||||
const emit = defineEmits(['close', 'prev', 'next', 'statusChanged', 'setAnsicht', 'openSidebar', 'sectionUpdated'])
|
||||
|
||||
const guideEl = ref(null) // linke Guide-Spalte (Scroll-Ziel für ALT+↑/↓)
|
||||
const rightEl = ref(null) // rechte Spalte (Prüfungs-Panel mit Eingabefeld)
|
||||
@@ -68,6 +70,58 @@ const stufe = computed(() => {
|
||||
const s = props.status || {}
|
||||
return stufeFuer(s.gute_antworten || 0, s.cap || props.cap)
|
||||
})
|
||||
|
||||
// --- Block-Prüfen (Rechtsklick auf einen Abschnitt) ---
|
||||
const angezeigterText = computed(() =>
|
||||
props.ansicht === 'kompakt' ? (props.baustein.kompakt || props.baustein.md) : props.baustein.md,
|
||||
)
|
||||
const bloecke = computed(() => renderBloecke(angezeigterText.value))
|
||||
// Feld, in das übernommen wird (muss zum angezeigten Text passen).
|
||||
const stelle = computed(() => (props.ansicht === 'kompakt' && props.baustein.kompakt) ? 'kompakt' : 'ausführlich')
|
||||
|
||||
const menu = reactive({ show: false, x: 0, y: 0, index: null })
|
||||
function blockMenue(i, e) { menu.show = true; menu.x = e.clientX; menu.y = e.clientY; menu.index = i }
|
||||
function menueZu() { menu.show = false }
|
||||
|
||||
// Vorschlag je Block-Index: { neu, laeuft, fehler, editOffen, hinweis }
|
||||
const vorschlaege = reactive({})
|
||||
|
||||
async function blockPruefen(i, zusatz = '') {
|
||||
const raw = bloecke.value[i]?.raw
|
||||
if (!raw || !props.guideId) return
|
||||
menueZu()
|
||||
vorschlaege[i] = { neu: '', laeuft: true, fehler: '', editOffen: false, hinweis: '' }
|
||||
try {
|
||||
const res = await pruefeBlock(props.guideId, { baustein: props.baustein.title, stelle: stelle.value, block: raw, hinweis: zusatz, provider: props.provider })
|
||||
vorschlaege[i] = { neu: res.neu, laeuft: false, fehler: '', editOffen: false, hinweis: '' }
|
||||
} catch (e) {
|
||||
vorschlaege[i] = { neu: '', laeuft: false, fehler: e.message || 'Prüfung fehlgeschlagen', editOffen: false, hinweis: '' }
|
||||
}
|
||||
}
|
||||
|
||||
async function blockUebernehmen(i) {
|
||||
const v = vorschlaege[i]; const raw = bloecke.value[i]?.raw
|
||||
if (!v || v.laeuft || !raw) return
|
||||
v.laeuft = true; v.fehler = ''
|
||||
try {
|
||||
const res = await uebernehmeBlock(props.guideId, { baustein: props.baustein.title, stelle: stelle.value, alt: raw, neu: v.neu, provider: props.provider })
|
||||
if (res.gefunden) emit('sectionUpdated', { title: props.baustein.title, kompakt: res.kompakt, md: res.md })
|
||||
else { v.laeuft = false; v.fehler = 'Stelle nicht gefunden — evtl. schon geändert.' }
|
||||
} catch (e) { v.laeuft = false; v.fehler = e.message || 'Übernehmen fehlgeschlagen' }
|
||||
}
|
||||
|
||||
function blockVerwerfen(i) { delete vorschlaege[i] }
|
||||
function blockEdit(i) { const v = vorschlaege[i]; if (v) v.editOffen = !v.editOffen }
|
||||
function blockEditSenden(i) {
|
||||
const z = (vorschlaege[i]?.hinweis || '').trim()
|
||||
if (z) blockPruefen(i, z)
|
||||
}
|
||||
|
||||
// Baustein/Inhalt-Wechsel → Vorschläge + Menü zurücksetzen (Block-Indizes neu).
|
||||
watch(() => `${props.baustein.title}|${props.baustein.md}|${props.baustein.kompakt || ''}`, () => {
|
||||
for (const k of Object.keys(vorschlaege)) delete vorschlaege[k]
|
||||
menueZu()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -91,7 +145,32 @@ const stufe = computed(() => {
|
||||
</div>
|
||||
<div class="fokus-body">
|
||||
<div ref="guideEl" class="fokus-col left">
|
||||
<div class="markdown" v-html="renderMarkdown(ansicht === 'kompakt' ? (baustein.kompakt || baustein.md) : baustein.md)"></div>
|
||||
<div class="markdown">
|
||||
<template v-for="(b, i) in bloecke" :key="i">
|
||||
<div class="md-block" v-html="b.html" @contextmenu.prevent="blockMenue(i, $event)"></div>
|
||||
<div v-if="vorschlaege[i]" class="block-vorschlag">
|
||||
<div v-if="vorschlaege[i].laeuft" class="bv-status">Prüfe Abschnitt…</div>
|
||||
<template v-else>
|
||||
<p v-if="vorschlaege[i].fehler" class="bv-fehler">{{ vorschlaege[i].fehler }}</p>
|
||||
<div class="markdown bv-neu" v-html="renderMarkdown(vorschlaege[i].neu)"></div>
|
||||
<div class="bv-aktionen">
|
||||
<button class="bv-btn ja" title="Übernehmen" @click="blockUebernehmen(i)">✓</button>
|
||||
<button class="bv-btn" title="Verwerfen" @click="blockVerwerfen(i)">✗</button>
|
||||
<button class="bv-btn" :class="{ aktiv: vorschlaege[i].editOffen }" title="Hinweis ergänzen" @click="blockEdit(i)">✏️</button>
|
||||
</div>
|
||||
<div v-if="vorschlaege[i].editOffen" class="bv-edit">
|
||||
<input v-model="vorschlaege[i].hinweis" class="bv-input" placeholder="Zusatz-Info → erneut prüfen" @keyup.enter="blockEditSenden(i)" />
|
||||
<button class="bv-btn ja" title="Erneut prüfen" @click="blockEditSenden(i)">↻</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="menu.show" class="menu-overlay" @click="menueZu" @contextmenu.prevent="menueZu">
|
||||
<div class="block-menu" :style="{ top: menu.y + 'px', left: menu.x + 'px' }" @click.stop>
|
||||
<button class="bm-item" @click="blockPruefen(menu.index)">Prüfen</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="rightEl" class="fokus-col right">
|
||||
<BausteinPanel
|
||||
@@ -217,4 +296,68 @@ const stufe = computed(() => {
|
||||
.fokus-col { overflow-y: visible; }
|
||||
.fokus-col.right { width: auto; }
|
||||
}
|
||||
|
||||
/* Block-Prüfen: Rechtsklick-Menü + Vorschlag unter dem Abschnitt */
|
||||
.md-block { border-radius: 6px; transition: background 0.15s; }
|
||||
.md-block:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
|
||||
|
||||
.menu-overlay { position: fixed; inset: 0; z-index: 60; }
|
||||
.block-menu {
|
||||
position: fixed;
|
||||
min-width: 8rem;
|
||||
padding: 0.25rem;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
.bm-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.4rem 0.7rem;
|
||||
text-align: left;
|
||||
font-size: 0.9rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.bm-item:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
|
||||
|
||||
.block-vorschlag {
|
||||
margin: 0.5rem 0 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid var(--accent);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--accent) 6%, var(--panel));
|
||||
}
|
||||
.bv-status { font-size: 0.9rem; color: var(--text-soft, #888); }
|
||||
.bv-fehler { color: var(--danger, #dc2626); font-size: 0.88rem; margin: 0 0 0.5rem; }
|
||||
.bv-neu > :first-child { margin-top: 0; }
|
||||
.bv-neu > :last-child { margin-bottom: 0; }
|
||||
.bv-aktionen { display: flex; gap: 0.4rem; margin-top: 0.6rem; }
|
||||
.bv-btn {
|
||||
min-width: 2rem; height: 2rem; padding: 0 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 6px;
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.bv-btn:hover { border-color: var(--accent); }
|
||||
.bv-btn.aktiv { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--panel)); }
|
||||
.bv-btn.ja { border-color: var(--success-border); color: var(--success); }
|
||||
.bv-edit { display: flex; gap: 0.4rem; margin-top: 0.5rem; }
|
||||
.bv-input {
|
||||
flex: 1;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user