mirror of
https://github.com/OHTU-German-learning-website/OHTU-German-learning-website.git
synced 2026-10-12 19:13:51 +03:00
@@ -9,13 +9,51 @@
|
|||||||
margin: 0 0 var(--u-sm);
|
margin: 0 0 var(--u-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glossary-index-item {
|
.glossary-index-letter {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
padding: 0.35rem 0.75rem;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.45rem 0.75rem;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
background-color: var(--bg3);
|
background-color: var(--bg3);
|
||||||
color: var(--fg1);
|
color: var(--fg1);
|
||||||
font-size: 0.95rem;
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: border-color 0.2s, background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-index-letter:hover,
|
||||||
|
.glossary-index-letter:focus {
|
||||||
|
background-color: var(--bg4);
|
||||||
|
border-color: var(--bg5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-index-group {
|
||||||
|
padding: var(--u-sm);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background-color: var(--bg3);
|
||||||
|
display: grid;
|
||||||
|
gap: var(--u-2xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-index-group-title {
|
||||||
|
margin: 0 0 var(--u-xs);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-index-entry {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--fg1);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-index-entry:hover,
|
||||||
|
.glossary-index-entry:focus {
|
||||||
|
background-color: var(--bg4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glossary-card {
|
.glossary-card {
|
||||||
@@ -25,18 +63,60 @@
|
|||||||
border: 1px solid var(--bg3);
|
border: 1px solid var(--bg3);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
transition: box-shadow 0.2s;
|
transition: box-shadow 0.2s;
|
||||||
|
scroll-margin-top: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glossary-card:hover {
|
.glossary-card:hover {
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.glossary-letter-group {
|
||||||
|
border-top: 1px solid var(--bg3);
|
||||||
|
padding-top: var(--u-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-letter-title {
|
||||||
|
margin: 0 0 var(--u-sm);
|
||||||
|
font-size: 1.35rem;
|
||||||
|
scroll-margin-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-back-to-top {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--fg2);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-top: var(--u-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-back-to-top:hover,
|
||||||
|
.glossary-back-to-top:focus {
|
||||||
|
color: var(--fg1);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.glossary-word {
|
.glossary-word {
|
||||||
color: var(--fg1);
|
color: var(--fg1);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.glossary-word-link {
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: background-color 0.2s, padding 0.2s;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
margin: -0.25rem -0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-word-link:hover,
|
||||||
|
.glossary-word-link:focus {
|
||||||
|
background-color: var(--bg4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.glossary-definition {
|
.glossary-definition {
|
||||||
color: var(--fg2);
|
color: var(--fg2);
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
|
|||||||
@@ -57,27 +57,66 @@ export default function GlossaryList() {
|
|||||||
a.word.localeCompare(b.word)
|
a.word.localeCompare(b.word)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const entriesByLetter = sortedEntries.reduce((map, entry) => {
|
||||||
|
const letter = (entry.word?.[0] || "").toUpperCase();
|
||||||
|
if (!map[letter]) {
|
||||||
|
map[letter] = [];
|
||||||
|
}
|
||||||
|
map[letter].push(entry);
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const letters = Object.keys(entriesByLetter).sort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap="md" width="100%">
|
<Column gap="md" width="100%">
|
||||||
<Row justify="space-between" gap="xl" width="100%" align="center">
|
<Row justify="space-between" gap="xl" width="100%" align="center">
|
||||||
<h2>Glossareinträge</h2>
|
<h2 id="glossary-top">Glossareinträge</h2>
|
||||||
</Row>
|
</Row>
|
||||||
<LinkButton href="/admin/glossary/create">
|
<LinkButton href="/admin/glossary/create">
|
||||||
Neuen Eintrag erstellen
|
Neuen Eintrag erstellen
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|
||||||
{sortedEntries.length > 0 && (
|
{letters.length > 0 && (
|
||||||
<Container className="glossary-index" mb="md">
|
<Container className="glossary-index" mb="md">
|
||||||
<h3>Register</h3>
|
<h3>Register</h3>
|
||||||
|
<Container display="flex" flexWrap="wrap" gap="xs" mb="sm">
|
||||||
|
{letters.map((letter) => (
|
||||||
|
<a
|
||||||
|
key={letter}
|
||||||
|
className="glossary-index-letter"
|
||||||
|
href={`#letter-${letter}-section`}
|
||||||
|
>
|
||||||
|
{letter}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
display="grid"
|
display="grid"
|
||||||
templateColumns="repeat(auto-fit, minmax(160px, 1fr))"
|
templateColumns="repeat(auto-fit, minmax(160px, 1fr))"
|
||||||
gap="sm"
|
gap="sm"
|
||||||
>
|
>
|
||||||
{sortedEntries.map((entry) => (
|
{letters.map((letter) => (
|
||||||
<span key={entry.id} className="glossary-index-item">
|
<Container key={letter} className="glossary-index-group">
|
||||||
{entry.word}
|
<h4
|
||||||
</span>
|
id={`letter-${letter}`}
|
||||||
|
className="glossary-index-group-title"
|
||||||
|
>
|
||||||
|
{letter}
|
||||||
|
</h4>
|
||||||
|
<Column gap="xs">
|
||||||
|
{entriesByLetter[letter].map((entry) => (
|
||||||
|
<a
|
||||||
|
key={entry.id}
|
||||||
|
href={`#entry-${entry.id}`}
|
||||||
|
className="glossary-index-entry"
|
||||||
|
>
|
||||||
|
{entry.word}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</Column>
|
||||||
|
</Container>
|
||||||
))}
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -89,46 +128,69 @@ export default function GlossaryList() {
|
|||||||
beginnen.
|
beginnen.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<Container
|
<Container display="grid" gap="md">
|
||||||
display="grid"
|
{letters.map((letter) => (
|
||||||
templateColumns="repeat(auto-fill, minmax(300px, 1fr))"
|
<Container key={letter} className="glossary-letter-group">
|
||||||
gap="md"
|
<h3
|
||||||
>
|
id={`letter-${letter}-section`}
|
||||||
{sortedEntries.map((entry) => (
|
className="glossary-letter-title"
|
||||||
<Container key={entry.id} className="glossary-card">
|
>
|
||||||
<Column gap="sm">
|
{letter}
|
||||||
<h4 className="glossary-word">{entry.word}</h4>
|
</h3>
|
||||||
<RenderHTML data={entry.word_definition} disableGlossary />
|
<Container
|
||||||
<Row justify="space-between" className="glossary-meta">
|
display="grid"
|
||||||
<span>
|
templateColumns="repeat(auto-fill, minmax(300px, 1fr))"
|
||||||
Erstellt: {new Date(entry.created_at).toLocaleDateString()}
|
gap="md"
|
||||||
</span>
|
>
|
||||||
<Row gap="sm">
|
{entriesByLetter[letter].map((entry) => (
|
||||||
<LinkButton
|
<Container
|
||||||
href={`/admin/glossary/create?id=${entry.id}`}
|
key={entry.id}
|
||||||
variant="outline"
|
id={`entry-${entry.id}`}
|
||||||
size="sm"
|
className="glossary-card"
|
||||||
>
|
>
|
||||||
Bearbeiten
|
<Column gap="sm">
|
||||||
</LinkButton>
|
<h4 className="glossary-word">{entry.word}</h4>
|
||||||
<Button
|
<RenderHTML
|
||||||
variant={
|
data={entry.word_definition}
|
||||||
confirmDelete === entry.id ? "danger" : "outline"
|
disableGlossary
|
||||||
}
|
/>
|
||||||
size="sm"
|
<Row justify="space-between" className="glossary-meta">
|
||||||
width="fit"
|
<span>
|
||||||
onClick={() => handleDelete(entry.id)}
|
Erstellt:{" "}
|
||||||
disabled={deleting === entry.id}
|
{new Date(entry.created_at).toLocaleDateString()}
|
||||||
>
|
</span>
|
||||||
{deleting === entry.id
|
<Row gap="sm">
|
||||||
? "Löschen..."
|
<LinkButton
|
||||||
: confirmDelete === entry.id
|
href={`/admin/glossary/create?id=${entry.id}`}
|
||||||
? "Bestätigen"
|
variant="outline"
|
||||||
: "Löschen"}
|
size="sm"
|
||||||
</Button>
|
>
|
||||||
</Row>
|
Bearbeiten
|
||||||
</Row>
|
</LinkButton>
|
||||||
</Column>
|
<Button
|
||||||
|
variant={
|
||||||
|
confirmDelete === entry.id ? "danger" : "outline"
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
width="fit"
|
||||||
|
onClick={() => handleDelete(entry.id)}
|
||||||
|
disabled={deleting === entry.id}
|
||||||
|
>
|
||||||
|
{deleting === entry.id
|
||||||
|
? "Löschen..."
|
||||||
|
: confirmDelete === entry.id
|
||||||
|
? "Bestätigen"
|
||||||
|
: "Löschen"}
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
</Container>
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
<a href="#glossary-top" className="glossary-back-to-top">
|
||||||
|
Zurück nach oben
|
||||||
|
</a>
|
||||||
</Container>
|
</Container>
|
||||||
))}
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -78,6 +78,22 @@
|
|||||||
.glossary-letter-title {
|
.glossary-letter-title {
|
||||||
margin: 0 0 var(--u-sm);
|
margin: 0 0 var(--u-sm);
|
||||||
font-size: 1.35rem;
|
font-size: 1.35rem;
|
||||||
|
scroll-margin-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-back-to-top {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--fg2);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-top: var(--u-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-back-to-top:hover,
|
||||||
|
.glossary-back-to-top:focus {
|
||||||
|
color: var(--fg1);
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glossary-word {
|
.glossary-word {
|
||||||
@@ -86,6 +102,21 @@
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.glossary-word-link {
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: background-color 0.2s, padding 0.2s;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
margin: -0.25rem -0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glossary-word-link:hover,
|
||||||
|
.glossary-word-link:focus {
|
||||||
|
background-color: var(--bg4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.glossary-definition {
|
.glossary-definition {
|
||||||
color: var(--fg2);
|
color: var(--fg2);
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function GlossaryPage() {
|
|||||||
return (
|
return (
|
||||||
<Column gap="md" width="100%">
|
<Column gap="md" width="100%">
|
||||||
<Row justify="space-between" gap="xl" width="100%" align="center">
|
<Row justify="space-between" gap="xl" width="100%" align="center">
|
||||||
<h2>Glossar</h2>
|
<h2 id="glossary-top">Glossar</h2>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{letters.length > 0 && (
|
{letters.length > 0 && (
|
||||||
@@ -114,6 +114,9 @@ export default function GlossaryPage() {
|
|||||||
</Container>
|
</Container>
|
||||||
))}
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
|
<a href="#glossary-top" className="glossary-back-to-top">
|
||||||
|
Zurück nach oben
|
||||||
|
</a>
|
||||||
</Container>
|
</Container>
|
||||||
))}
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ const getValue = (cssProp, value) => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Container = memo(({ children, className, ...props }) => {
|
export const Container = memo(({ children, className, id, ...props }) => {
|
||||||
const mediaQueries = {};
|
const mediaQueries = {};
|
||||||
|
|
||||||
const validLayoutPropsEntries = useMemo(
|
const validLayoutPropsEntries = useMemo(
|
||||||
@@ -159,7 +159,7 @@ export const Container = memo(({ children, className, ...props }) => {
|
|||||||
};
|
};
|
||||||
}, [validLayoutPropsEntries]);
|
}, [validLayoutPropsEntries]);
|
||||||
return (
|
return (
|
||||||
<div style={style} className={className}>
|
<div id={id} style={style} className={className}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user