Files
creator/frontend/src/components/ElementsOverview.vue
2026-06-07 15:17:50 +02:00

206 lines
4.2 KiB
Vue

<script setup>
import { ref, watch } from 'vue'
import { fetchElements } from '../api.js'
import { renderMarkdown } from '../markdown.js'
const props = defineProps({
topic: { type: String, required: true },
version: { type: Number, default: 0 }, // Erhöhung = Elemente neu laden
})
const emit = defineEmits(['open'])
const elements = ref([])
watch([() => props.topic, () => props.version], load, { immediate: true })
async function load() {
try {
elements.value = await fetchElements(props.topic)
} catch (e) {
console.error('Fehler beim Laden der Elemente:', e)
}
}
function plain(text) {
return (text || '').replace(/```[a-z]*\n?/g, '').replace(/[`*_#]/g, '')
}
</script>
<template>
<div class="elements-overview">
<div class="overview-scroll">
<div class="overview-content">
<header class="overview-head">
<h1>{{ topic }}</h1>
<span class="overview-format">Elemente</span>
</header>
<p v-if="!elements.length" class="overview-empty">
Noch keine Elemente. Rechts in der Sidebar Stichwort eingeben und + klicken.
</p>
<div class="element-grid">
<article
v-for="el in elements"
:key="el.id"
class="element-card"
@click="emit('open', el)"
>
<h3>{{ plain(el.title) }}</h3>
<div class="markdown" v-html="renderMarkdown(el.description)"></div>
<div v-for="(ex, i) in el.examples" :key="i" class="markdown el-example" v-html="renderMarkdown(ex)"></div>
<div v-if="el.hints.length" class="el-hints-block">
<h4>Hinweise</h4>
<ul>
<li v-for="(h, i) in el.hints" :key="i" class="markdown" v-html="renderMarkdown(h)"></li>
</ul>
</div>
</article>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.elements-overview {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
background: var(--bg-preview);
}
.overview-scroll {
flex: 1;
overflow-y: auto;
}
.overview-content {
max-width: 1000px;
margin: 0 auto;
padding: 2.5rem 2rem 4rem;
}
.overview-head {
display: flex;
align-items: baseline;
gap: 0.8rem;
margin-bottom: 1.5rem;
}
.overview-head h1 {
margin: 0;
font-size: 2.2rem;
color: var(--text);
}
.overview-format {
font-size: 1rem;
font-weight: 600;
color: var(--text-faint);
}
.overview-empty {
color: var(--text-muted);
}
.element-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 1rem;
align-items: start;
}
.element-card {
background: var(--panel);
border: 1px solid var(--border);
border-top: 3px solid var(--accent);
border-radius: 10px;
padding: 1rem 1.1rem;
cursor: pointer;
transition: box-shadow 0.15s, transform 0.15s;
}
.element-card:hover {
box-shadow: 0 4px 16px var(--shadow);
transform: translateY(-1px);
}
.element-card h3 {
margin: 0 0 0.5rem;
font-size: 1.05rem;
color: var(--text);
}
.el-example {
margin-top: 0.5rem;
}
.el-hints-block {
margin-top: 0.7rem;
}
.el-hints-block h4 {
margin: 0 0 0.3rem;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-faint);
}
.el-hints-block ul {
margin: 0;
padding-left: 1.1rem;
}
.el-hints-block li {
font-size: 0.85rem;
line-height: 1.5;
color: var(--text);
margin-bottom: 0.2rem;
}
/* Markdown im Guide-Stil */
.markdown {
font-size: 0.9rem;
line-height: 1.55;
color: var(--text);
}
.markdown :deep(p) {
margin: 0 0 0.5em;
}
.markdown :deep(p:last-child) {
margin-bottom: 0;
}
.markdown :deep(code) {
background: var(--border);
padding: 1px 4px;
border-radius: 4px;
font-family: "SF Mono", Consolas, monospace;
font-size: 0.85em;
overflow-wrap: anywhere;
}
.markdown :deep(pre) {
background: var(--code-bg, #1e2330);
color: var(--code-fg, #e6e8ee);
padding: 10px 12px;
border-radius: 8px;
/* Umbrechen statt horizontal scrollen — Scrollbar verdeckt sonst die Code-Zeile */
white-space: pre-wrap;
overflow-wrap: anywhere;
margin: 0.4em 0;
}
.markdown :deep(pre code) {
background: none;
padding: 0;
color: inherit;
font-size: 0.85em;
}
</style>