feat: Adressbuch-Block mit Suche, Org- und Buchstabenfilter
- AdressbuchBlock: listet alle Kontakte alphabetisch mit Suche, Org-Filter-Chips und A–Z-Buchstabenfilter; /adressbuch-Seite - ContactCard: neues Design passend zu OrganisationsCard (Badges, Links) - ContactsBlock: Karussell mit snap-scroll, volle Breite - block-types, blog-utils, cms: AdressbuchBlockData, resolveAdressbuchBlocks, getContacts; BlockRenderer registriert adressbuch - Kontakt-Seite: Link auf /adressbuch statt eingebettetem Block Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -336,6 +336,7 @@ export interface ContactEntry {
|
||||
_slug?: string;
|
||||
name?: string;
|
||||
role?: string;
|
||||
organisations?: (string | { _slug?: string; name?: string })[];
|
||||
email?: string;
|
||||
phone?: string;
|
||||
note?: string;
|
||||
@@ -349,6 +350,15 @@ export interface ContactsBlockData {
|
||||
layout?: BlockLayout;
|
||||
}
|
||||
|
||||
export interface AdressbuchBlockData {
|
||||
_type?: "adressbuch";
|
||||
_slug?: string;
|
||||
excludeContacts?: (string | ContactEntry)[];
|
||||
layout?: BlockLayout;
|
||||
/** Server-side injected — not in CMS schema */
|
||||
resolvedContacts?: ContactEntry[];
|
||||
}
|
||||
|
||||
/** Live-Strommix-Widget (_type: "live_strommix"). Editorial-Konfig + Platzierungs-Instanz.
|
||||
* Fetched live data from /api/strommix (aggregator) and renders status bucket
|
||||
* derived from `(wind_onshore + wind_offshore + solar) / load * 100`. */
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getEntryBySlug,
|
||||
getCalendarBySlug,
|
||||
getCalendarItemBySlug,
|
||||
getContacts,
|
||||
getCalendarItems,
|
||||
} from "./cms";
|
||||
import type { RowContentLayout } from "./block-types";
|
||||
@@ -833,3 +834,29 @@ export async function resolveStellingnahmeGeneratorBlocks(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveAdressbuchBlocks(
|
||||
layout: RowContentLayout | null | undefined,
|
||||
): Promise<void> {
|
||||
if (!layout) return;
|
||||
const rows = [layout.row1Content ?? [], layout.row2Content ?? [], layout.row3Content ?? []];
|
||||
let contacts: unknown[] | null = null;
|
||||
for (const content of rows) {
|
||||
if (!Array.isArray(content)) continue;
|
||||
for (const item of content) {
|
||||
if (typeof item !== "object" || item === null) continue;
|
||||
if ((item as { _type?: string })._type !== "adressbuch") continue;
|
||||
if (contacts === null) contacts = await getContacts();
|
||||
const block = item as Record<string, unknown>;
|
||||
const exclude = (block.excludeContacts as unknown[]) ?? [];
|
||||
const excludeSlugs = new Set(
|
||||
exclude.map((e) =>
|
||||
typeof e === "string" ? e : (e as { _slug?: string })?._slug ?? ""
|
||||
).filter(Boolean)
|
||||
);
|
||||
block.resolvedContacts = excludeSlugs.size
|
||||
? contacts.filter((c) => !excludeSlugs.has((c as { _slug?: string })._slug ?? ""))
|
||||
: contacts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1067,3 +1067,20 @@ export async function getCommentCounts(
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/** Alle Kontakte (GET /api/content/contact), alphabetisch nach Name. */
|
||||
export async function getContacts(): Promise<Record<string, unknown>[]> {
|
||||
return cached("contact", "contact:list:all", async () => {
|
||||
const base = getBaseUrl();
|
||||
const url = new URL(`${base}/api/content/contact`);
|
||||
url.searchParams.set("_per_page", "1000");
|
||||
url.searchParams.set("_resolve", "all");
|
||||
url.searchParams.set("_depth", "2");
|
||||
url.searchParams.set("_sort", "name");
|
||||
url.searchParams.set("_order", "asc");
|
||||
const res = await cmsFetch(url.toString());
|
||||
if (!res.ok) return [];
|
||||
const data = (await res.json()) as { items?: Record<string, unknown>[] };
|
||||
return data.items ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@
|
||||
import InternalComponentBlock from "./blocks/InternalComponentBlock.svelte";
|
||||
import DeadlineBannerBlock from "./blocks/DeadlineBannerBlock.svelte";
|
||||
import WindkarteBlock from "./blocks/WindkarteBlock.svelte";
|
||||
import AdressbuchBlock from "./blocks/AdressbuchBlock.svelte";
|
||||
import type { Translations } from "$lib/translations";
|
||||
import type {
|
||||
ResolvedBlock,
|
||||
AdressbuchBlockData,
|
||||
MarkdownBlockData,
|
||||
HeadlineBlockData,
|
||||
HtmlBlockData,
|
||||
@@ -109,6 +111,8 @@
|
||||
{/await}
|
||||
{:else if type === "wind_map"}
|
||||
<WindkarteBlock block={block as WindMapBlockData} />
|
||||
{:else if type === "adressbuch"}
|
||||
<AdressbuchBlock block={block as AdressbuchBlockData} />
|
||||
{:else if type === "stellungnahme_generator"}
|
||||
{#await import("./blocks/StellingnahmeGeneratorBlock.svelte") then m}
|
||||
{@const Comp = m.default}
|
||||
|
||||
@@ -4,27 +4,54 @@
|
||||
import type { ContactEntry } from "$lib/block-types";
|
||||
|
||||
let { contact }: { contact: ContactEntry } = $props();
|
||||
|
||||
const orgNames = $derived(
|
||||
(contact.organisations ?? [])
|
||||
.filter((o): o is { _slug?: string; name?: string } => typeof o === "object" && o !== null)
|
||||
.map((o) => o.name)
|
||||
.filter(Boolean) as string[]
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-1.5 rounded-lg border border-stein-200 bg-white px-4 py-3">
|
||||
<div>
|
||||
<span class="text-sm font-semibold text-stein-900">{contact.name ?? ""}</span>
|
||||
{#if contact.role}
|
||||
<span class="ml-1.5 text-xs text-stein-500">{contact.role}</span>
|
||||
{/if}
|
||||
<div
|
||||
class="not-prose card-surface relative flex h-full min-w-0 flex-col gap-2 overflow-hidden rounded-sm p-4 transition-[transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:border-wald-300 hover:shadow-md"
|
||||
>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<p class="truncate text-sm font-medium leading-tight text-stein-900">{contact.name ?? ""}</p>
|
||||
|
||||
{#if contact.role || orgNames.length > 0}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#if contact.role}
|
||||
<span class="shrink-0 rounded-full border border-stein-200 bg-stein-100 px-1.5 py-0 text-[0.65rem] font-medium text-stein-600">
|
||||
{contact.role}
|
||||
</span>
|
||||
{/if}
|
||||
{#each orgNames as org}
|
||||
<span class="shrink-0 rounded-full border border-wald-200 bg-wald-50 px-1.5 py-0 text-[0.65rem] font-medium text-wald-700">
|
||||
{org}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if contact.phone}
|
||||
<a href="tel:{contact.phone.replace(/\s/g, '')}" class="inline-flex items-center gap-1.5 text-xs text-stein-700 no-underline hover:text-wald-700">
|
||||
<Icon icon="mdi:phone-outline" class="size-3.5 shrink-0 text-stein-400" />
|
||||
{contact.phone}
|
||||
</a>
|
||||
{/if}
|
||||
{#if contact.email}
|
||||
<a href="mailto:{contact.email}" class="inline-flex items-center gap-1.5 text-xs text-stein-700 no-underline hover:text-wald-700">
|
||||
<Icon icon="mdi:email-outline" class="size-3.5 shrink-0 text-stein-400" />
|
||||
{contact.email}
|
||||
</a>
|
||||
|
||||
{#if contact.phone || contact.email}
|
||||
<div class="flex flex-col gap-1">
|
||||
{#if contact.phone}
|
||||
<a href="tel:{contact.phone.replace(/\s/g, '')}" class="inline-flex items-center gap-1.5 text-xs text-stein-600 no-underline hover:text-wald-700">
|
||||
<Icon icon="mdi:phone-outline" class="size-3.5 shrink-0 text-stein-400" />
|
||||
<span class="truncate">{contact.phone}</span>
|
||||
</a>
|
||||
{/if}
|
||||
{#if contact.email}
|
||||
<a href="mailto:{contact.email}" class="inline-flex items-center gap-1.5 text-xs text-stein-600 no-underline hover:text-wald-700">
|
||||
<Icon icon="mdi:email-outline" class="size-3.5 shrink-0 text-stein-400" />
|
||||
<span class="truncate">{contact.email}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if contact.note}
|
||||
<p class="text-[11px] text-stein-400">{contact.note}</p>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import "$lib/iconify-offline";
|
||||
import { getBlockLayoutClasses } from "$lib/block-layout";
|
||||
import type { AdressbuchBlockData, ContactEntry } from "$lib/block-types";
|
||||
|
||||
let { block }: { block: AdressbuchBlockData } = $props();
|
||||
|
||||
const layoutClasses = $derived(getBlockLayoutClasses(block.layout));
|
||||
|
||||
const allContacts = $derived(
|
||||
(block.resolvedContacts ?? []) as ContactEntry[]
|
||||
);
|
||||
|
||||
// Unique orgs from all contacts (resolved objects only)
|
||||
const allOrgs = $derived(() => {
|
||||
const map = new Map<string, string>();
|
||||
for (const c of allContacts) {
|
||||
for (const o of c.organisations ?? []) {
|
||||
if (typeof o === "object" && o !== null && o._slug && o.name) {
|
||||
map.set(o._slug, o.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...map.entries()].sort((a, b) => a[1].localeCompare(b[1], "de"));
|
||||
});
|
||||
|
||||
let search = $state("");
|
||||
let activeOrg = $state<string | null>(null);
|
||||
let activeLetter = $state<string | null>(null);
|
||||
|
||||
const afterOrgFilter = $derived(() =>
|
||||
allContacts.filter((c) => {
|
||||
if (!activeOrg) return true;
|
||||
return (c.organisations ?? []).some(
|
||||
(o) => (typeof o === "object" ? o._slug : o) === activeOrg
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const availableLetters = $derived(() => {
|
||||
const set = new Set<string>();
|
||||
for (const c of afterOrgFilter()) {
|
||||
const first = (c.name ?? "").trim()[0];
|
||||
if (first) set.add(first.toUpperCase());
|
||||
}
|
||||
return set;
|
||||
});
|
||||
|
||||
const filtered = $derived(() => {
|
||||
const q = search.toLowerCase().trim();
|
||||
return afterOrgFilter().filter((c) => {
|
||||
if (activeLetter) {
|
||||
const first = (c.name ?? "").trim()[0]?.toUpperCase();
|
||||
if (first !== activeLetter) return false;
|
||||
}
|
||||
if (!q) return true;
|
||||
return (
|
||||
(c.name ?? "").toLowerCase().includes(q) ||
|
||||
(c.role ?? "").toLowerCase().includes(q) ||
|
||||
(c.email ?? "").toLowerCase().includes(q) ||
|
||||
(c.organisations ?? []).some((o) =>
|
||||
typeof o === "object" ? (o.name ?? "").toLowerCase().includes(q) : false
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="{layoutClasses} not-prose" data-block-type="adressbuch">
|
||||
<!-- Search + Filter bar -->
|
||||
<div class="mb-4 flex flex-col gap-2">
|
||||
<div class="relative max-w-sm">
|
||||
<Icon icon="mdi:magnify" class="absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-stein-400" />
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Suchen…"
|
||||
bind:value={search}
|
||||
class="w-full rounded-sm border border-stein-200 bg-white py-1.5 pl-8 pr-3 text-sm text-stein-900 placeholder:text-stein-400 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-400"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<button
|
||||
onclick={() => (activeOrg = null)}
|
||||
class="rounded-full border px-2 py-px text-[0.65rem] font-medium transition-colors {activeOrg === null
|
||||
? 'border-wald-400 bg-wald-500 text-white'
|
||||
: 'border-stein-200 bg-stein-100 text-stein-600 hover:border-wald-300 hover:text-wald-700'}"
|
||||
>
|
||||
Alle
|
||||
</button>
|
||||
{#each allOrgs() as [slug, name]}
|
||||
<button
|
||||
onclick={() => (activeOrg = activeOrg === slug ? null : slug)}
|
||||
class="rounded-full border px-2 py-px text-[0.65rem] font-medium transition-colors {activeOrg === slug
|
||||
? 'border-wald-400 bg-wald-500 text-white'
|
||||
: 'border-stein-200 bg-stein-100 text-stein-600 hover:border-wald-300 hover:text-wald-700'}"
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- Letter filter -->
|
||||
<div class="flex flex-wrap gap-0.5">
|
||||
{#each "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("") as letter}
|
||||
{@const has = availableLetters().has(letter)}
|
||||
<button
|
||||
onclick={() => (activeLetter = activeLetter === letter ? null : letter)}
|
||||
disabled={!has}
|
||||
class="w-6 rounded py-px text-center text-[0.65rem] font-medium transition-colors {activeLetter === letter
|
||||
? 'bg-wald-500 text-white'
|
||||
: has
|
||||
? 'text-stein-600 hover:bg-stein-100 hover:text-wald-700'
|
||||
: 'cursor-default text-stein-300'}"
|
||||
>
|
||||
{letter}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact list -->
|
||||
{#if filtered().length === 0}
|
||||
<p class="text-sm text-stein-400">Keine Kontakte gefunden.</p>
|
||||
{:else}
|
||||
<div class="divide-y divide-stein-100 rounded-sm border border-stein-200 bg-white">
|
||||
{#each filtered() as contact (contact._slug)}
|
||||
<div class="flex flex-col gap-2 px-4 py-3">
|
||||
<!-- Name + badges -->
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-stein-900">{contact.name ?? ""}</p>
|
||||
{#if contact.role || (contact.organisations ?? []).some((o) => typeof o === "object" && o.name)}
|
||||
<div class="mt-0.5 flex flex-nowrap gap-1 overflow-hidden">
|
||||
{#if contact.role}
|
||||
<span class="shrink-0 whitespace-nowrap rounded-full border border-stein-200 bg-stein-100 px-1.5 py-0 text-[0.65rem] font-medium text-stein-600">
|
||||
{contact.role}
|
||||
</span>
|
||||
{/if}
|
||||
{#each (contact.organisations ?? []).filter((o): o is { _slug?: string; name?: string } => typeof o === "object" && !!o.name) as org}
|
||||
<span class="shrink-0 whitespace-nowrap rounded-full border border-wald-200 bg-wald-50 px-1.5 py-0 text-[0.65rem] font-medium text-wald-700">
|
||||
{org.name}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Contact actions -->
|
||||
{#if contact.phone || contact.email}
|
||||
<div class="flex justify-end gap-2">
|
||||
{#if contact.phone}
|
||||
<a
|
||||
href="tel:{contact.phone.replace(/\s/g, '')}"
|
||||
title={contact.phone}
|
||||
class="inline-flex items-center gap-1 rounded-sm border border-stein-200 bg-stein-50 px-2 py-0.5 text-[0.6rem] text-stein-600 no-underline transition-colors hover:border-wald-300 hover:bg-wald-50 hover:text-wald-700"
|
||||
>
|
||||
<Icon icon="mdi:phone-outline" class="size-3.5 shrink-0" />
|
||||
<span class="truncate">{contact.phone}</span>
|
||||
</a>
|
||||
{/if}
|
||||
{#if contact.email}
|
||||
<a
|
||||
href="mailto:{contact.email}"
|
||||
title={contact.email}
|
||||
class="inline-flex items-center gap-1 rounded-sm border border-stein-200 bg-stein-50 px-2 py-0.5 text-[0.6rem] text-stein-600 no-underline transition-colors hover:border-wald-300 hover:bg-wald-50 hover:text-wald-700"
|
||||
>
|
||||
<Icon icon="mdi:email-outline" class="size-3.5 shrink-0" />
|
||||
<span class="truncate">{contact.email}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="mt-2 text-right text-xs text-stein-400">{filtered().length} Kontakt{filtered().length !== 1 ? "e" : ""}</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,22 +1,27 @@
|
||||
<script lang="ts">
|
||||
import ContactCard from "$lib/components/ContactCard.svelte";
|
||||
import { getBlockLayoutClasses } from "$lib/block-layout";
|
||||
import type { ContactsBlockData, ContactEntry } from "$lib/block-types";
|
||||
|
||||
let { block }: { block: ContactsBlockData } = $props();
|
||||
|
||||
const layoutClasses = $derived(getBlockLayoutClasses(block.layout));
|
||||
|
||||
const contacts = $derived(
|
||||
(block.contacts ?? []).filter((c): c is ContactEntry => typeof c === "object" && c !== null)
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if contacts.length > 0}
|
||||
<div>
|
||||
<div class="{layoutClasses}" data-block="ContactsBlock" data-block-type="contacts_block">
|
||||
{#if block.headline}
|
||||
<h3 class="mb-3 text-sm font-semibold text-stein-700">{block.headline}</h3>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:flex-wrap">
|
||||
<div
|
||||
class="flex snap-x snap-mandatory gap-3 overflow-x-auto px-px pt-1 pb-2 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
{#each contacts as contact}
|
||||
<div class="sm:min-w-[220px] sm:max-w-xs">
|
||||
<div class="w-[calc(100%-2rem)] min-w-[220px] max-w-[320px] shrink-0 snap-start sm:w-60 lg:w-72">
|
||||
<ContactCard {contact} />
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
"shield-check-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M21 11c0 5.55-3.84 10.74-9 12c-5.16-1.26-9-6.45-9-12V5l9-4l9 4zm-9 10c3.75-1 7-5.46 7-9.78V6.3l-7-3.12L5 6.3v4.92C5 15.54 8.25 20 12 21m-2-4l-4-4l1.41-1.41L10 14.17l6.59-6.59L18 9\"/>"
|
||||
},
|
||||
"phone-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M20 15.5c-1.2 0-2.5-.2-3.6-.6h-.3c-.3 0-.5.1-.7.3l-2.2 2.2c-2.8-1.5-5.2-3.8-6.6-6.6l2.2-2.2c.3-.3.4-.7.2-1c-.3-1.1-.5-2.4-.5-3.6c0-.5-.5-1-1-1H4c-.5 0-1 .5-1 1c0 9.4 7.6 17 17 17c.5 0 1-.5 1-1v-3.5c0-.5-.5-1-1-1M5 5h1.5c.1.9.3 1.8.5 2.6L5.8 8.8C5.4 7.6 5.1 6.3 5 5m14 14c-1.3-.1-2.6-.4-3.8-.8l1.2-1.2c.8.2 1.7.4 2.6.4z\"/>"
|
||||
},
|
||||
"email-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2zm-2 0l-8 5l-8-5zm0 12H4V8l8 5l8-5z\"/>"
|
||||
},
|
||||
"map-marker": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 11.5A2.5 2.5 0 0 1 9.5 9A2.5 2.5 0 0 1 12 6.5A2.5 2.5 0 0 1 14.5 9a2.5 2.5 0 0 1-2.5 2.5M12 2a7 7 0 0 0-7 7c0 5.25 7 13 7 13s7-7.75 7-13a7 7 0 0 0-7-7\"/>"
|
||||
},
|
||||
@@ -144,6 +150,9 @@
|
||||
"flag-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12.36 6l.4 2H18v6h-3.36l-.4-2H7V6zM14 4H5v17h2v-7h5.6l.4 2h7V6h-5.6\"/>"
|
||||
},
|
||||
"drag-vertical": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M9 3h2v2H9zm4 0h2v2h-2zM9 7h2v2H9zm4 0h2v2h-2zm-4 4h2v2H9zm4 0h2v2h-2zm-4 4h2v2H9zm4 0h2v2h-2zm-4 4h2v2H9zm4 0h2v2h-2z\"/>"
|
||||
},
|
||||
"transmission-tower": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m8.28 5.45l-1.78-.9L7.76 2h8.47l1.27 2.55l-1.78.89L15 4H9zM18.62 8h-4.53l-.79-3h-2.6l-.79 3H5.38L4.1 10.55l1.79.89l.73-1.44h10.76l.72 1.45l1.79-.89zm-.85 14H15.7l-.24-.9L12 15.9l-3.47 5.2l-.23.9H6.23l2.89-11h2.07l-.36 1.35L12 14.1l1.16-1.75l-.35-1.35h2.07zm-6.37-7l-.9-1.35l-1.18 4.48zm3.28 3.12l-1.18-4.48l-.9 1.36z\"/>"
|
||||
},
|
||||
@@ -204,9 +213,6 @@
|
||||
"email": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m20 8l-8 5l-8-5V6l8 5l8-5m0-2H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2\"/>"
|
||||
},
|
||||
"email-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2zm-2 0l-8 5l-8-5zm0 12H4V8l8 5l8-5z\"/>"
|
||||
},
|
||||
"phone": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M6.62 10.79c1.44 2.83 3.76 5.15 6.59 6.59l2.2-2.2c.28-.28.67-.36 1.02-.25c1.12.37 2.32.57 3.57.57a1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1c0 1.25.2 2.45.57 3.57c.11.35.03.74-.25 1.02z\"/>"
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
resolveCalendarBlocks,
|
||||
resolveDeadlineBannerBlocks,
|
||||
resolveStellingnahmeGeneratorBlocks,
|
||||
resolveAdressbuchBlocks,
|
||||
} from '$lib/blog-utils';
|
||||
import { PAGE_RESOLVE, POST_SOCIAL_IMAGE_TRANSFORM } from '$lib/constants';
|
||||
import { env } from '$env/dynamic/public';
|
||||
@@ -74,6 +75,7 @@ export const load: PageServerLoad = async ({ params, locals, fetch, url, setHead
|
||||
resolveCalendarBlocks(page),
|
||||
resolveDeadlineBannerBlocks(page),
|
||||
resolveStellingnahmeGeneratorBlocks(page),
|
||||
resolveAdressbuchBlocks(page),
|
||||
]);
|
||||
const tagsMap = await postOverviewPromise;
|
||||
await Promise.all([
|
||||
|
||||
Reference in New Issue
Block a user