update
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useConfirm } from '../composables/useConfirm.js'
|
||||
import { fetchQuelle } from '../api.js'
|
||||
|
||||
const props = defineProps({
|
||||
topics: { type: Array, required: true },
|
||||
projects: { type: Array, default: () => [] },
|
||||
selectedTopic: { type: String, default: null },
|
||||
stats: { type: Object, default: null },
|
||||
fortschritt: { type: Object, default: () => ({}) },
|
||||
@@ -20,9 +20,17 @@ const props = defineProps({
|
||||
dark: { type: Boolean, default: false },
|
||||
provider: { type: String, default: 'claude' },
|
||||
providers: { type: Array, default: () => [] },
|
||||
folders: { type: Object, default: () => ({ projekt: [], uni: [] }) },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['select', 'create', 'createThema', 'formatClick', 'bausteineClick', 'cancelBausteine', 'resetBausteine', 'deleteTopic', 'deleteProject', 'cancelGuide', 'deleteGuide', 'dismissError', 'dismissUiError', 'preview', 'openElements', 'togglePin', 'sidebarLeave', 'toggleDark', 'setProvider'])
|
||||
const emit = defineEmits(['select', 'create', 'createThema', 'updateQuelle', 'formatClick', 'bausteineClick', 'cancelBausteine', 'resetBausteine', 'deleteTopic', 'cancelGuide', 'deleteGuide', 'dismissError', 'dismissUiError', 'preview', 'openBausteineView', 'openElements', 'togglePin', 'sidebarLeave', 'toggleDark', 'setProvider'])
|
||||
|
||||
// Accordion: höchstens ein Panel offen. IDs: 'bausteine', 'fmt-<Format>', 'topic-<Name>'.
|
||||
const openPanel = ref(null)
|
||||
const isOpen = (id) => openPanel.value === id
|
||||
function togglePanel(id) {
|
||||
openPanel.value = openPanel.value === id ? null : id
|
||||
}
|
||||
|
||||
function providerAvailable(id) {
|
||||
const p = props.providers.find((x) => x.id === id)
|
||||
@@ -40,6 +48,7 @@ const trackerItems = computed(() => {
|
||||
{ label: 'Themen', value: String(props.stats.themen ?? 0), title: 'Themen inkl. Projekte' },
|
||||
{ label: 'MiniGuides', value: fmt('MiniGuide'), title: 'absolviert/erstellt' },
|
||||
{ label: 'Guides', value: fmt('Guide'), title: 'absolviert/erstellt' },
|
||||
{ label: 'ProGuides', value: fmt('ProGuide'), title: 'absolviert/erstellt' },
|
||||
{ label: 'FullGuides', value: fmt('FullGuide'), title: 'absolviert/erstellt' },
|
||||
]
|
||||
})
|
||||
@@ -48,6 +57,7 @@ const formats = [
|
||||
{ key: 'OnePager', label: 'OnePager' },
|
||||
{ key: 'MiniGuide', label: 'MiniGuide' },
|
||||
{ key: 'Guide', label: 'Guide' },
|
||||
{ key: 'ProGuide', label: 'ProGuide' },
|
||||
{ key: 'FullGuide', label: 'FullGuide' },
|
||||
]
|
||||
|
||||
@@ -82,6 +92,12 @@ function handleBausteinePlay() {
|
||||
emit('bausteineClick', { instructions: '' })
|
||||
}
|
||||
|
||||
// Name-Klick = Primäraktion: fertige Bausteine → Übersicht, sonst Panel auf/zu.
|
||||
function onBausteineName() {
|
||||
if (props.bausteine.ready && !props.bausteine.generating) emit('openBausteineView')
|
||||
else togglePanel('bausteine')
|
||||
}
|
||||
|
||||
function guideStatus(format) {
|
||||
// Laufende Generierung hat Vorrang — sonst maskiert ein älterer fertiger
|
||||
// Guide den Lauf und ▶ würde Duplikate starten.
|
||||
@@ -132,11 +148,11 @@ function abgebrochen(format) {
|
||||
return latest?.status === 'error' && (latest.error_msg || '').startsWith('Abgebrochen')
|
||||
}
|
||||
|
||||
// Name-Klick: fertiger Guide → Vorschau, sonst Aktions-Panel auf/zu.
|
||||
function handleFormatClick(format) {
|
||||
const guide = props.doneByFormat[format]
|
||||
if (guide) {
|
||||
emit('preview', guide)
|
||||
}
|
||||
if (guide) emit('preview', guide)
|
||||
else togglePanel('fmt-' + format)
|
||||
}
|
||||
|
||||
// Sperr-Gründe kommen vom Backend (GET /guides/locks) — die Regeln existieren
|
||||
@@ -179,13 +195,6 @@ function handleDelete(format) {
|
||||
// Erstellen-Bereich: inline aufklappbar (Name + weitere Infos + Quellen-Typ).
|
||||
const dlg = ref(false)
|
||||
const form = ref({ name: '', instructions: '', sourceType: 'thema', sourceOrt: '' })
|
||||
const SOURCE_HINTS = {
|
||||
thema: 'Web-Recherche zum Thema.',
|
||||
link: 'Seite wird gecrawlt (gleiche Domain, begrenzt) — Klausur-Fokus.',
|
||||
projekt: 'Ordner wird gelesen — Architektur/Features verstehen.',
|
||||
uni: 'Ordner wird gelesen — Klausur-Vorbereitung.',
|
||||
}
|
||||
const sourceHint = computed(() => SOURCE_HINTS[form.value.sourceType])
|
||||
const canCreate = computed(() => {
|
||||
if (!form.value.name.trim()) return false
|
||||
if (['link', 'projekt', 'uni'].includes(form.value.sourceType)) return !!form.value.sourceOrt.trim()
|
||||
@@ -212,8 +221,45 @@ function confirmDeleteTopic(topic) {
|
||||
armOrRun('topic-' + topic, () => emit('deleteTopic', topic))
|
||||
}
|
||||
|
||||
function confirmDeleteProject(name) {
|
||||
armOrRun('project-' + name, () => emit('deleteProject', name))
|
||||
// --- Thema-Edit: Klick auf ein Thema wählt es UND klappt die Quellen-Form auf ---
|
||||
const editTopic = ref(null)
|
||||
const editForm = ref({ type: 'thema', ort: '', spec: '' })
|
||||
const editLoading = ref(false)
|
||||
const canSave = computed(() => {
|
||||
if (['link', 'projekt', 'uni'].includes(editForm.value.type)) return !!editForm.value.ort.trim()
|
||||
return true
|
||||
})
|
||||
|
||||
async function onTopicClick(t) {
|
||||
emit('select', t)
|
||||
if (openPanel.value === 'topic-' + t) { openPanel.value = null; return }
|
||||
openPanel.value = 'topic-' + t
|
||||
editTopic.value = t
|
||||
editLoading.value = true
|
||||
try {
|
||||
const q = await fetchQuelle(t)
|
||||
editForm.value = { type: q.type || 'thema', ort: q.ort || '', spec: q.spec || '' }
|
||||
} catch {
|
||||
editForm.value = { type: 'thema', ort: '', spec: '' }
|
||||
} finally {
|
||||
editLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function setEditType(t) {
|
||||
editForm.value.type = t
|
||||
editForm.value.ort = ''
|
||||
}
|
||||
|
||||
function saveQuelle() {
|
||||
if (!canSave.value || !editTopic.value) return
|
||||
emit('updateQuelle', {
|
||||
topic: editTopic.value,
|
||||
type: editForm.value.type,
|
||||
ort: editForm.value.ort.trim(),
|
||||
spec: editForm.value.spec.trim(),
|
||||
})
|
||||
openPanel.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -242,7 +288,7 @@ function confirmDeleteProject(name) {
|
||||
<!-- Erstellen: inline aufklappbar (kein Modal) -->
|
||||
<div v-if="dlg" class="thema-panel">
|
||||
<input class="dlg-input" v-model="form.name" placeholder="Thema-Name…" @keyup.enter="createThema" autofocus />
|
||||
<textarea class="dlg-textarea" v-model="form.instructions" rows="3" placeholder="Weitere Informationen / Spezifikation (optional)…"></textarea>
|
||||
<textarea class="dlg-textarea" v-model="form.instructions" rows="2" placeholder="Weitere Infos (optional)…"></textarea>
|
||||
<div class="dlg-sources">
|
||||
<button :class="{ active: form.sourceType === 'thema' }" @click="form.sourceType = 'thema'; form.sourceOrt = ''">Thema</button>
|
||||
<button :class="{ active: form.sourceType === 'link' }" @click="form.sourceType = 'link'; form.sourceOrt = ''">Link</button>
|
||||
@@ -254,12 +300,13 @@ function confirmDeleteProject(name) {
|
||||
class="dlg-input" v-model="form.sourceOrt"
|
||||
placeholder="https://…" @keyup.enter="createThema"
|
||||
/>
|
||||
<input
|
||||
<select
|
||||
v-else-if="form.sourceType === 'projekt' || form.sourceType === 'uni'"
|
||||
class="dlg-input" v-model="form.sourceOrt"
|
||||
placeholder="Ordner ab ./ (z.B. projects/meinprojekt)" @keyup.enter="createThema"
|
||||
/>
|
||||
<p class="dlg-hint">{{ sourceHint }}</p>
|
||||
>
|
||||
<option value="" disabled>Ordner wählen…</option>
|
||||
<option v-for="fo in (folders[form.sourceType] || [])" :key="fo.ort" :value="fo.ort">{{ fo.name }}</option>
|
||||
</select>
|
||||
<div class="dlg-actions">
|
||||
<button class="dlg-cancel" @click="dlg = false">Abbrechen</button>
|
||||
<button class="dlg-create" :disabled="!canCreate" @click="createThema">Erstellen</button>
|
||||
@@ -283,66 +330,60 @@ function confirmDeleteProject(name) {
|
||||
<div class="progress-info" v-if="activeGenerations.length">
|
||||
<div v-for="(line, i) in activeGenerations" :key="i">{{ line }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="format-row bausteine-row ord-bausteine"
|
||||
:class="{ 'is-active': bausteineState === 'generating' || bausteine.partial }"
|
||||
>
|
||||
<div class="format-name bausteine-name">
|
||||
<span class="format-label">Bausteine</span>
|
||||
<span
|
||||
v-if="bausteine.partial && bausteineState !== 'generating'"
|
||||
class="resume-badge"
|
||||
title="Abgebrochen — ▶ setzt fort"
|
||||
>Pausiert</span>
|
||||
<span class="step-dots">
|
||||
<div class="ord-bausteine">
|
||||
<div
|
||||
class="format-row bausteine-row"
|
||||
:class="{ 'is-active': bausteineState === 'generating' || bausteine.partial, 'row-open': isOpen('bausteine') }"
|
||||
>
|
||||
<button class="format-name bausteine-name" @click="onBausteineName">
|
||||
<span class="format-label">Bausteine</span>
|
||||
<span
|
||||
v-for="s in (bausteine.steps || [])"
|
||||
:key="s.label"
|
||||
class="step-dot"
|
||||
:class="s.state"
|
||||
:title="s.state === 'active' ? (bausteine.progress || s.label) : s.label"
|
||||
></span>
|
||||
</span>
|
||||
<span
|
||||
v-if="bausteineState === 'generating'"
|
||||
class="format-x"
|
||||
:class="{ armed: pendingConfirm === 'bausteine' }"
|
||||
title="Aktuellen Schritt abbrechen (Fortschritt bleibt)"
|
||||
@click.stop="confirmCancelBausteine"
|
||||
>{{ pendingConfirm === 'bausteine' ? 'Sicher?' : '×' }}</span>
|
||||
<span
|
||||
v-else-if="bausteine.partial"
|
||||
class="format-x"
|
||||
:class="{ armed: pendingConfirm === 'bausteine' }"
|
||||
title="Fortschritt löschen"
|
||||
@click.stop="confirmResetBausteine"
|
||||
>{{ pendingConfirm === 'bausteine' ? 'Sicher?' : '×' }}</span>
|
||||
v-if="bausteine.partial && bausteineState !== 'generating'"
|
||||
class="resume-badge"
|
||||
title="Abgebrochen — Fortsetzen möglich"
|
||||
>Pausiert</span>
|
||||
<span class="step-dots">
|
||||
<span
|
||||
v-for="s in (bausteine.steps || [])"
|
||||
:key="s.label"
|
||||
class="step-dot"
|
||||
:class="s.state"
|
||||
:title="s.state === 'active' ? (bausteine.progress || s.label) : s.label"
|
||||
></span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="panel-toggle" :class="{ open: isOpen('bausteine') }" title="Aktionen" @click.stop="togglePanel('bausteine')">▾</button>
|
||||
</div>
|
||||
<div class="format-actions">
|
||||
<template v-if="bausteineState !== 'generating'">
|
||||
<div v-if="isOpen('bausteine')" class="action-panel">
|
||||
<template v-if="bausteineState === 'generating'">
|
||||
<button class="panel-btn danger" :class="{ armed: pendingConfirm === 'bausteine' }" @click="confirmCancelBausteine">{{ pendingConfirm === 'bausteine' ? 'Sicher?' : 'Abbrechen' }}</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="panel-btn play" @click="handleBausteinePlay">{{ bausteine.partial ? 'Fortsetzen' : bausteine.ready ? 'Neu generieren' : 'Generieren' }}</button>
|
||||
<button
|
||||
class="action-btn play"
|
||||
:title="bausteine.partial ? 'Fortsetzen' : bausteine.ready ? 'Bausteine neu erstellen' : 'Bausteine erstellen'"
|
||||
@click="handleBausteinePlay"
|
||||
>▶</button>
|
||||
v-if="bausteine.ready || bausteine.partial"
|
||||
class="panel-btn danger"
|
||||
:class="{ armed: pendingConfirm === 'bausteine' }"
|
||||
@click="confirmResetBausteine"
|
||||
>{{ pendingConfirm === 'bausteine' ? 'Sicher?' : 'Entfernen' }}</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="bausteineState === 'generating'" class="format-progress ord-bausteine">
|
||||
{{ bausteine.progress || 'Wartend…' }}
|
||||
</div>
|
||||
<div v-if="bausteine.error && !bausteine.error.startsWith('Abgebrochen')" class="format-error ord-bausteine">
|
||||
<span class="format-error-text">{{ bausteine.error }}</span>
|
||||
<div v-if="bausteineState === 'generating'" class="format-progress">
|
||||
{{ bausteine.progress || 'Wartend…' }}
|
||||
</div>
|
||||
<div v-if="bausteine.error && !bausteine.error.startsWith('Abgebrochen')" class="format-error">
|
||||
<span class="format-error-text">{{ bausteine.error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- OnePager (unabhängig von Bausteinen) steht per CSS-order vor der Bausteine-Zeile -->
|
||||
<div v-for="f in formats" :key="f.key" :style="{ order: f.key === 'OnePager' ? 1 : 3 }">
|
||||
<div :class="['format-row', 'fmt-' + guideStatus(f.key), { 'fmt-paused': abgebrochen(f.key) }]">
|
||||
<div :class="['format-row', 'fmt-' + guideStatus(f.key), { 'fmt-paused': abgebrochen(f.key), 'row-open': isOpen('fmt-' + f.key) }]">
|
||||
<button class="format-name" @click="handleFormatClick(f.key)">
|
||||
<span class="format-label">{{ f.label }}</span>
|
||||
<span
|
||||
v-if="abgebrochen(f.key)"
|
||||
class="resume-badge"
|
||||
title="Abgebrochen — ▶ setzt fort"
|
||||
title="Abgebrochen — Fortsetzen möglich"
|
||||
>Pausiert</span>
|
||||
<span class="step-dots" v-if="guideSteps(f.key).length">
|
||||
<span
|
||||
@@ -353,25 +394,27 @@ function confirmDeleteProject(name) {
|
||||
:title="s.state === 'active' ? (latestByFormat[f.key]?.progress || s.label) : s.label"
|
||||
></span>
|
||||
</span>
|
||||
<span
|
||||
v-if="guideStatus(f.key) !== 'none' || abgebrochen(f.key)"
|
||||
class="format-x"
|
||||
:class="{ armed: pendingConfirm === 'fmt-' + f.key }"
|
||||
@click.stop="handleDelete(f.key)"
|
||||
:title="guideStatus(f.key) === 'generating' || guideStatus(f.key) === 'queued' ? 'Abbrechen'
|
||||
: abgebrochen(f.key) ? 'Fortschritt löschen' : 'Löschen'"
|
||||
>{{ pendingConfirm === 'fmt-' + f.key ? 'Sicher?' : '×' }}</span>
|
||||
</button>
|
||||
<div class="format-actions">
|
||||
<template v-if="guideStatus(f.key) !== 'generating' && guideStatus(f.key) !== 'queued'">
|
||||
<button
|
||||
class="action-btn play"
|
||||
:title="playLock(f.key) || (abgebrochen(f.key) ? 'Fortsetzen' : 'Generieren')"
|
||||
:disabled="!!playLock(f.key)"
|
||||
@click="handlePlay(f.key)"
|
||||
>▶</button>
|
||||
</template>
|
||||
</div>
|
||||
<button class="panel-toggle" :class="{ open: isOpen('fmt-' + f.key) }" title="Aktionen" @click.stop="togglePanel('fmt-' + f.key)">▾</button>
|
||||
</div>
|
||||
<div v-if="isOpen('fmt-' + f.key)" class="action-panel">
|
||||
<template v-if="guideStatus(f.key) === 'generating' || guideStatus(f.key) === 'queued'">
|
||||
<button class="panel-btn danger" :class="{ armed: pendingConfirm === 'fmt-' + f.key }" @click="handleDelete(f.key)">{{ pendingConfirm === 'fmt-' + f.key ? 'Sicher?' : 'Abbrechen' }}</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button
|
||||
class="panel-btn play"
|
||||
:title="playLock(f.key) || (abgebrochen(f.key) ? 'Fortsetzen' : 'Generieren')"
|
||||
:disabled="!!playLock(f.key)"
|
||||
@click="handlePlay(f.key)"
|
||||
>{{ abgebrochen(f.key) ? 'Fortsetzen' : guideStatus(f.key) === 'done' ? 'Neu generieren' : 'Generieren' }}</button>
|
||||
<button
|
||||
v-if="guideStatus(f.key) !== 'none' || abgebrochen(f.key)"
|
||||
class="panel-btn danger"
|
||||
:class="{ armed: pendingConfirm === 'fmt-' + f.key }"
|
||||
@click="handleDelete(f.key)"
|
||||
>{{ pendingConfirm === 'fmt-' + f.key ? 'Sicher?' : abgebrochen(f.key) ? 'Fortschritt löschen' : 'Entfernen' }}</button>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="guideStatus(f.key) === 'generating' || guideStatus(f.key) === 'queued'"
|
||||
@@ -393,34 +436,45 @@ function confirmDeleteProject(name) {
|
||||
<li
|
||||
v-for="t in topics"
|
||||
:key="t"
|
||||
:class="{ active: t === selectedTopic }"
|
||||
@click="emit('select', t)"
|
||||
:class="{ active: t === selectedTopic, 'li-open': isOpen('topic-' + t) }"
|
||||
>
|
||||
<span>{{ t }}</span>
|
||||
<button
|
||||
class="delete-topic"
|
||||
:class="{ armed: pendingConfirm === 'topic-' + t }"
|
||||
@click.stop="confirmDeleteTopic(t)"
|
||||
title="Thema und alle Guides löschen"
|
||||
>{{ pendingConfirm === 'topic-' + t ? 'Sicher?' : '×' }}</button>
|
||||
<div class="topic-row" @click="onTopicClick(t)">
|
||||
<span class="topic-name">{{ t }}</span>
|
||||
<span class="panel-toggle" :class="{ open: isOpen('topic-' + t) }">▾</span>
|
||||
</div>
|
||||
<div v-if="isOpen('topic-' + t)" class="thema-panel edit-panel" @click.stop>
|
||||
<p v-if="editLoading" class="dlg-hint">Lade…</p>
|
||||
<template v-else>
|
||||
<textarea class="dlg-textarea" v-model="editForm.spec" rows="2" placeholder="Weitere Infos (optional)…"></textarea>
|
||||
<div class="dlg-sources">
|
||||
<button :class="{ active: editForm.type === 'thema' }" @click="setEditType('thema')">Thema</button>
|
||||
<button :class="{ active: editForm.type === 'link' }" @click="setEditType('link')">Link</button>
|
||||
<button :class="{ active: editForm.type === 'projekt' }" @click="setEditType('projekt')">Projekt</button>
|
||||
<button :class="{ active: editForm.type === 'uni' }" @click="setEditType('uni')">Uni</button>
|
||||
</div>
|
||||
<input
|
||||
v-if="editForm.type === 'link'"
|
||||
class="dlg-input" v-model="editForm.ort"
|
||||
placeholder="https://…"
|
||||
/>
|
||||
<select
|
||||
v-else-if="editForm.type === 'projekt' || editForm.type === 'uni'"
|
||||
class="dlg-input" v-model="editForm.ort"
|
||||
>
|
||||
<option value="" disabled>Ordner wählen…</option>
|
||||
<option v-for="fo in (folders[editForm.type] || [])" :key="fo.ort" :value="fo.ort">{{ fo.name }}</option>
|
||||
</select>
|
||||
<div class="dlg-actions">
|
||||
<button
|
||||
class="dlg-delete"
|
||||
:class="{ armed: pendingConfirm === 'topic-' + t }"
|
||||
@click="confirmDeleteTopic(t)"
|
||||
>{{ pendingConfirm === 'topic-' + t ? 'Sicher?' : 'Löschen' }}</button>
|
||||
<button class="dlg-create" :disabled="!canSave" @click="saveQuelle">Aktualisieren</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</li>
|
||||
<template v-if="projects.length">
|
||||
<li class="projects-divider">Projekte</li>
|
||||
<li
|
||||
v-for="p in projects"
|
||||
:key="'project-' + p"
|
||||
:class="{ active: p === selectedTopic, 'project-item': true }"
|
||||
@click="emit('select', p)"
|
||||
>
|
||||
<span>{{ p }}</span>
|
||||
<button
|
||||
class="delete-topic"
|
||||
:class="{ armed: pendingConfirm === 'project-' + p }"
|
||||
@click.stop="confirmDeleteProject(p)"
|
||||
title="Projekt entfernen (löscht ./projects-Ordner)"
|
||||
>{{ pendingConfirm === 'project-' + p ? 'Sicher?' : '×' }}</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</aside>
|
||||
</template>
|
||||
@@ -582,59 +636,35 @@ function confirmDeleteProject(name) {
|
||||
}
|
||||
|
||||
.topic-list li {
|
||||
padding: 0.6rem 1rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text);
|
||||
transition: background 0.15s;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.topic-list li:hover {
|
||||
.topic-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
padding: 0.6rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.topic-row:hover {
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
|
||||
.topic-list li.active {
|
||||
.topic-list li.active .topic-row {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent-hover);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.delete-topic {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--danger);
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
padding: 0 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.topic-list li:hover .delete-topic {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.projects-divider {
|
||||
cursor: default;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-faint);
|
||||
font-weight: 700;
|
||||
padding: 0.6rem 1rem 0.3rem;
|
||||
margin-top: 0.4rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.projects-divider:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.topic-list li.project-item span::before {
|
||||
content: '📁 ';
|
||||
.topic-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Format section */
|
||||
@@ -642,7 +672,7 @@ function confirmDeleteProject(name) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: default;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.step-dots {
|
||||
@@ -756,13 +786,14 @@ function confirmDeleteProject(name) {
|
||||
|
||||
.format-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
font-size: 0.85rem;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
cursor: pointer;
|
||||
color: var(--text-faint);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -770,6 +801,55 @@ function confirmDeleteProject(name) {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Accordion: Pfeil-Toggle + aufklappendes Aktions-Panel */
|
||||
.panel-toggle {
|
||||
flex: 0 0 auto;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-faint);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
padding: 4px 8px;
|
||||
transition: transform 0.15s, color 0.15s;
|
||||
}
|
||||
.panel-toggle.open { transform: rotate(180deg); color: var(--text); }
|
||||
.panel-toggle:hover { color: var(--accent); }
|
||||
|
||||
.format-row.row-open,
|
||||
.topic-list li.li-open .topic-row { background: var(--panel-soft); }
|
||||
|
||||
.action-panel {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.15rem 0.75rem 0.55rem calc(0.75rem + 8px);
|
||||
}
|
||||
|
||||
.panel-btn {
|
||||
flex: 1;
|
||||
padding: 0.45rem 0.6rem;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 6px;
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.panel-btn:hover { border-color: var(--accent); }
|
||||
.panel-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.panel-btn:disabled:hover { border-color: var(--border-strong); }
|
||||
.panel-btn.play {
|
||||
color: var(--success);
|
||||
background: var(--success-soft);
|
||||
border-color: var(--success-border);
|
||||
}
|
||||
.panel-btn.play:hover { background: var(--success-soft-hover); }
|
||||
.panel-btn.danger { color: var(--danger); }
|
||||
.panel-btn.danger:hover { border-color: var(--danger); }
|
||||
.panel-btn.armed { background: var(--danger); color: #fff; border-color: var(--danger); }
|
||||
|
||||
.format-x {
|
||||
display: none;
|
||||
color: var(--danger);
|
||||
@@ -915,20 +995,20 @@ function confirmDeleteProject(name) {
|
||||
.new-topic-toggle:hover { background: var(--accent-hover); }
|
||||
.new-topic-toggle.active { background: var(--accent-hover); }
|
||||
.thema-panel {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.75rem;
|
||||
margin: 0.4rem 0;
|
||||
padding: 0.5rem;
|
||||
background: var(--panel-soft);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
gap: 0.35rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.dlg-input, .dlg-textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.5rem 0.6rem;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--panel);
|
||||
@@ -936,23 +1016,23 @@ function confirmDeleteProject(name) {
|
||||
font: inherit;
|
||||
}
|
||||
.dlg-input:focus, .dlg-textarea:focus { outline: none; border-color: var(--accent); }
|
||||
.dlg-textarea { resize: vertical; min-height: 3rem; }
|
||||
.dlg-sources { display: flex; gap: 0.4rem; }
|
||||
.dlg-textarea { resize: vertical; min-height: 2rem; }
|
||||
.dlg-sources { display: flex; gap: 0.3rem; }
|
||||
.dlg-sources button {
|
||||
flex: 1;
|
||||
padding: 0.45rem 0.2rem;
|
||||
padding: 0.35rem 0.2rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--panel-soft);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-size: 0.82rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.dlg-sources button.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
|
||||
.dlg-hint { margin: 0; font-size: 0.75rem; color: var(--text-faint); }
|
||||
.dlg-actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 0.3rem; }
|
||||
.dlg-actions { display: flex; justify-content: flex-end; gap: 0.4rem; margin-top: 0.1rem; }
|
||||
.dlg-actions button {
|
||||
padding: 0.45rem 0.9rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--panel-soft);
|
||||
@@ -962,4 +1042,10 @@ function confirmDeleteProject(name) {
|
||||
.dlg-create { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
|
||||
.dlg-create:disabled { opacity: 0.45; cursor: default; }
|
||||
|
||||
/* Thema-Edit-Panel innerhalb der Themen-Liste */
|
||||
.edit-panel { margin: 0 0.75rem 0.5rem; }
|
||||
.dlg-delete { margin-right: auto; color: var(--danger); }
|
||||
.dlg-delete.armed { background: var(--danger); border-color: var(--danger); color: #fff; font-weight: 700; }
|
||||
select.dlg-input { cursor: pointer; }
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user