Updating og image generation, updating Lucia Beta, and using Shadcn Form for the search form.

This commit is contained in:
Bradley Shellnut 2024-01-26 16:35:02 -08:00
parent 019798eb0b
commit 386d4e7e3a
22 changed files with 879 additions and 573 deletions

View file

@ -28,16 +28,16 @@
"devDependencies": { "devDependencies": {
"@melt-ui/pp": "^0.3.0", "@melt-ui/pp": "^0.3.0",
"@melt-ui/svelte": "^0.70.0", "@melt-ui/svelte": "^0.70.0",
"@playwright/test": "^1.41.0", "@playwright/test": "^1.41.1",
"@resvg/resvg-js": "^2.4.1", "@resvg/resvg-js": "^2.6.0",
"@sveltejs/adapter-auto": "^3.1.0", "@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/adapter-vercel": "^4.0.5", "@sveltejs/enhanced-img": "^0.1.8",
"@sveltejs/kit": "^2.3.5", "@sveltejs/kit": "^2.5.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/cookie": "^0.5.4", "@types/cookie": "^0.5.4",
"@types/node": "^18.19.8", "@types/node": "^18.19.10",
"@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.0", "@typescript-eslint/parser": "^6.19.1",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@ -59,14 +59,14 @@
"svelte-meta-tags": "^3.1.0", "svelte-meta-tags": "^3.1.0",
"svelte-preprocess": "^5.1.3", "svelte-preprocess": "^5.1.3",
"svelte-sequential-preprocessor": "^2.0.1", "svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.3.1", "sveltekit-flash-message": "^2.4.1",
"sveltekit-superforms": "^1.13.3", "sveltekit-superforms": "^1.13.4",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.6.1", "tslib": "^2.6.1",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.0.11", "vite": "^5.0.12",
"vitest": "^1.2.1", "vitest": "^1.2.2",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"type": "module", "type": "module",
@ -78,32 +78,33 @@
"@fontsource/fira-mono": "^4.5.10", "@fontsource/fira-mono": "^4.5.10",
"@iconify-icons/line-md": "^1.2.26", "@iconify-icons/line-md": "^1.2.26",
"@iconify-icons/mdi": "^1.2.47", "@iconify-icons/mdi": "^1.2.47",
"@lucia-auth/adapter-prisma": "4.0.0-beta.9", "@lucia-auth/adapter-prisma": "4.0.0-beta.10",
"@lukeed/uuid": "^2.0.1", "@lukeed/uuid": "^2.0.1",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"@prisma/client": "^5.8.1", "@prisma/client": "^5.8.1",
"@sentry/sveltekit": "^7.88.0", "@sentry/sveltekit": "^7.88.0",
"@sveltejs/adapter-vercel": "^5.1.0",
"@types/feather-icons": "^4.29.4", "@types/feather-icons": "^4.29.4",
"@vercel/og": "^0.5.13", "@vercel/og": "^0.5.20",
"bits-ui": "^0.11.8", "bits-ui": "^0.15.1",
"boardgamegeekclient": "^1.9.1", "boardgamegeekclient": "^1.9.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"cookie": "^0.5.0", "cookie": "^0.5.0",
"feather-icons": "^4.29.1", "feather-icons": "^4.29.1",
"formsnap": "^0.4.2", "formsnap": "^0.4.3",
"html-entities": "^2.4.0", "html-entities": "^2.4.0",
"iconify-icon": "^1.0.8", "iconify-icon": "^1.0.8",
"just-kebab-case": "^4.2.0", "just-kebab-case": "^4.2.0",
"loader": "^2.1.1", "loader": "^2.1.1",
"lucia": "3.0.0-beta.14", "lucia": "3.0.0-beta.15",
"lucide-svelte": "^0.298.0", "lucide-svelte": "^0.298.0",
"open-props": "^1.6.16", "open-props": "^1.6.17",
"oslo": "^0.27.1", "oslo": "^1.0.1",
"radix-svelte": "^0.9.0", "radix-svelte": "^0.9.0",
"svelte-french-toast": "^1.2.0", "svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0", "svelte-lazy-loader": "^1.0.0",
"tailwind-merge": "^2.2.0", "tailwind-merge": "^2.2.1",
"tailwind-variants": "^0.1.19", "tailwind-variants": "^0.1.19",
"tailwindcss-animate": "^1.0.6", "tailwindcss-animate": "^1.0.6",
"zod-to-json-schema": "^3.22.3" "zod-to-json-schema": "^3.22.3"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
<script lang="ts">
import type { SuperValidated } from 'sveltekit-superforms';
import { search_schema, type SearchSchema } from '$lib/zodValidation';
import * as Form from "$lib/components/ui/form";
export let form: SuperValidated<SearchSchema>;
</script>
<search>
<Form.Root id="search-form" action="/search" method="GET" data-sveltekit-reload {form} schema={search_schema} let:config>
<fieldset>
<Form.Item>
<Form.Field {config} name="q">
<Form.Label for="label">Search</Form.Label>
<Form.Input />
<Form.Validation />
</Form.Field>
<Form.Field {config} name="skip">
<Form.Input type="hidden" />
</Form.Field>
<Form.Field {config} name="limit">
<Form.Input type="hidden" />
</Form.Field>
</Form.Item>
</fieldset>
<fieldset>
<div class="flex items-center space-x-2">
<Form.Field {config} name="exact">
<Form.Label>Exact Search</Form.Label>
<Form.Checkbox class="mt-0" />
</Form.Field>
</div>
</fieldset>
<Form.Button>Submit</Form.Button>
</Form.Root>
</search>
<style lang="postcss">
</style>

View file

@ -1,76 +0,0 @@
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import type { SuperValidated } from 'sveltekit-superforms';
import type { SearchSchema } from '$lib/zodValidation';
import { Label } from '$lib/components/ui/label';
import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button';
export let data;
console.log("text search data", data);
export let showButton: boolean = false;
export let advancedSearch: boolean = false;
const { form, errors }: SuperValidated<SearchSchema> = superForm(data.form);
const dev = process.env.NODE_ENV !== 'production';
// TODO: Keep all Pagination Values on back and forth browser
// TODO: Add cache for certain number of pages so back and forth doesn't request data again
</script>
{#if dev}
<SuperDebug collapsible data={$form} />
{/if}
<search>
<form id="search-form" action="/search" method="GET">
<div class="search">
<fieldset class="text-search">
<Label for="label">Search</Label>
<Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="q" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} />
{#if $errors.q}
<p class="text-sm text-destructive">{$errors.q}</p>
{/if}
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
</fieldset>
</div>
{#if showButton}
<Button type="submit">Submit</Button>
{/if}
</form>
</search>
<style lang="postcss">
:global(.disclosure-button) {
display: flex;
gap: 0.25rem;
place-items: center;
}
#search-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
button {
padding: 1rem;
margin: 1.5rem 0;
}
label {
display: grid;
grid-template-columns: auto auto;
gap: 1rem;
place-content: start;
place-items: center;
@media (max-width: 850px) {
display: flex;
flex-wrap: wrap;
}
}
</style>

View file

@ -7,12 +7,10 @@ const buttonVariants = tv({
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground", "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline" link: "text-primary underline-offset-4 hover:underline"
}, },

View file

@ -8,9 +8,6 @@
export { className as class }; export { className as class };
</script> </script>
<FormPrimitive.Description <FormPrimitive.Description class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
>
<slot /> <slot />
</FormPrimitive.Description> </FormPrimitive.Description>

View file

@ -12,10 +12,6 @@
const { errors, ids } = getFormField(); const { errors, ids } = getFormField();
</script> </script>
<Label <Label for={$ids.input} class={cn($errors && "text-destructive", className)} {...$$restProps}>
for={$ids.input}
class={cn($errors && "text-destructive", className)}
{...$$restProps}
>
<slot /> <slot />
</Label> </Label>

View file

@ -7,17 +7,12 @@
placeholder?: string; placeholder?: string;
}; };
type $$Events = SelectPrimitive.TriggerEvents; type $$Events = SelectPrimitive.TriggerEvents;
const { attrStore } = getFormField(); const { attrStore, value } = getFormField();
export let placeholder = ""; export let placeholder = "";
</script> </script>
<Select.Trigger <Select.Trigger {...$$restProps} {...$attrStore} on:click on:keydown type="button">
{...$$restProps} <slot value={$value}>
{...$attrStore} <Select.Value {placeholder} />
on:click </slot>
on:keydown
type="button"
>
<Select.Value {placeholder} />
<slot />
</Select.Trigger> </Select.Trigger>

View file

@ -2,10 +2,7 @@
import { getFormField } from "formsnap"; import { getFormField } from "formsnap";
import type { HTMLTextareaAttributes } from "svelte/elements"; import type { HTMLTextareaAttributes } from "svelte/elements";
import type { TextareaGetFormField } from "."; import type { TextareaGetFormField } from ".";
import { import { Textarea, type TextareaEvents } from "$lib/components/ui/textarea";
Textarea,
type TextareaEvents
} from "$lib/components/ui/textarea";
type $$Props = HTMLTextareaAttributes; type $$Props = HTMLTextareaAttributes;
type $$Events = TextareaEvents; type $$Events = TextareaEvents;

View file

@ -27,10 +27,7 @@ const SelectGroup = SelectComp.Group;
const SelectItem = SelectComp.Item; const SelectItem = SelectComp.Item;
const SelectSeparator = SelectComp.Separator; const SelectSeparator = SelectComp.Separator;
export type TextareaGetFormField = Omit< export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, "value"> & {
ReturnType<typeof getFormField>,
"value"
> & {
value: Writable<string>; value: Writable<string>;
}; };

View file

@ -8,6 +8,8 @@ export type InputEvents = {
change: FormInputEvent<Event>; change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>; click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>; focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>; keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>; keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>; keyup: FormInputEvent<KeyboardEvent>;

View file

@ -21,6 +21,8 @@
on:change on:change
on:click on:click
on:focus on:focus
on:focusin
on:focusout
on:keydown on:keydown
on:keypress on:keypress
on:keyup on:keyup

View file

@ -9,10 +9,6 @@
export { className as class }; export { className as class };
</script> </script>
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root bind:value class={cn("grid gap-2", className)} {...$$restProps}>
bind:value
class={cn("grid gap-2", className)}
{...$$restProps}
>
<slot /> <slot />
</RadioGroupPrimitive.Root> </RadioGroupPrimitive.Root>

View file

@ -8,7 +8,4 @@
export { className as class }; export { className as class };
</script> </script>
<SelectPrimitive.Separator <SelectPrimitive.Separator class={cn("-mx-1 my-1 h-px bg-muted", className)} {...$$restProps} />
class={cn("-mx-1 my-1 h-px bg-muted", className)}
{...$$restProps}
/>

View file

@ -12,7 +12,7 @@
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
class={cn( class={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 line-clamp-1 truncate",
className className
)} )}
{...$$restProps} {...$$restProps}

45
src/lib/renderImage.ts Normal file
View file

@ -0,0 +1,45 @@
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import { html as toReactNode } from 'satori-html';
import { dev } from '$app/environment';
import { read } from '$app/server';
// we use a Vite plugin to turn this import into the result of fs.readFileSync during build
import firaSansSemiBold from '$lib/fonts/FiraSans-SemiBold.ttf';
const fontData = read(firaSansSemiBold).arrayBuffer();
export async function componentToPng(component,
props: Record<string, string | undefined>,
height: number, width: number) {
const result = component.render(props);
const markup = toReactNode(`${result.html}<style lang="css">${result.css.code}</style>`);
const svg = await satori(markup, {
fonts: [
{
name: 'Fira Sans',
data: await fontData,
style: 'normal'
}
],
height: +height,
width: +width
});
const resvg = new Resvg(svg, {
fitTo: {
mode: 'width',
value: +width
}
});
const image = resvg.render();
return new Response(image.asPng(), {
headers: {
'content-type': 'image/png',
'cache-control': dev ? 'no-cache, no-store' : 'public, immutable, no-transform, max-age=86400'
}
});
}

View file

@ -2,7 +2,6 @@ import { superValidate } from 'sveltekit-superforms/server';
import { search_schema } from '$lib/zodValidation'; import { search_schema } from '$lib/zodValidation';
import type { MetaTagsProps } from 'svelte-meta-tags'; import type { MetaTagsProps } from 'svelte-meta-tags';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import prisma from '$lib/prisma';
import type { Game } from '@prisma/client'; import type { Game } from '@prisma/client';
export const load: PageServerLoad = async ({ fetch, url }) => { export const load: PageServerLoad = async ({ fetch, url }) => {

View file

@ -95,7 +95,6 @@ async function searchForGames(
}; };
} catch (e) { } catch (e) {
console.log(`Error searching board games ${e}`); console.log(`Error searching board games ${e}`);
// throw error(500, { message: 'Something went wrong' });
} }
return { return {
totalCount: 0, totalCount: 0,
@ -103,21 +102,27 @@ async function searchForGames(
}; };
} }
const defaults = {
limit: 10,
skip: 0,
order: 'name',
sort: 'asc',
q: '',
exact: false,
};
export const load: PageServerLoad = async ({ locals, fetch, url }) => { export const load: PageServerLoad = async ({ locals, fetch, url }) => {
const defaults = {
limit: 10,
skip: 0,
order: 'asc',
sort: 'name'
};
const searchParams = Object.fromEntries(url?.searchParams); const searchParams = Object.fromEntries(url?.searchParams);
console.log('searchParams', searchParams); console.log('searchParams', searchParams);
searchParams.limit = searchParams.limit || `${defaults.limit}`;
searchParams.skip = searchParams.skip || `${defaults.skip}`;
searchParams.order = searchParams.order || defaults.order; searchParams.order = searchParams.order || defaults.order;
searchParams.sort = searchParams.sort || defaults.sort; searchParams.sort = searchParams.sort || defaults.sort;
const form = await superValidate(searchParams, search_schema); searchParams.q = searchParams.q || defaults.q;
// const modifyListForm = await superValidate(listGameSchema); const form = await superValidate({
...searchParams,
skip: Number(searchParams.skip || defaults.skip),
limit: Number(searchParams.limit || defaults.limit),
exact: searchParams.exact ? searchParams.exact === 'true' : defaults.exact
}, search_schema);
const queryParams: SearchQuery = { const queryParams: SearchQuery = {
limit: form.data?.limit, limit: form.data?.limit,
@ -125,7 +130,6 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
q: form.data?.q q: form.data?.q
}; };
// fields: ('id,name,min_age,min_players,max_players,thumb_url,min_playtime,max_playtime,min_age,description');
try { try {
if (form.data?.q === '') { if (form.data?.q === '') {
return { return {
@ -169,6 +173,8 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
const urlQueryParams = new URLSearchParams(newQueryParams); const urlQueryParams = new URLSearchParams(newQueryParams);
const searchData = await searchForGames(locals, fetch, urlQueryParams); const searchData = await searchForGames(locals, fetch, urlQueryParams);
console.log('search data', JSON.stringify(searchData, null, 2));
return { return {
form, form,
// modifyListForm, // modifyListForm,
@ -178,6 +184,8 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
} catch (e) { } catch (e) {
console.log(`Error searching board games ${e}`); console.log(`Error searching board games ${e}`);
} }
console.log('returning default no data')
return { return {
form, form,
searchData: { searchData: {

View file

@ -4,21 +4,26 @@
import { superForm } from 'sveltekit-superforms/client'; import { superForm } from 'sveltekit-superforms/client';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'; import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import { createPagination, createToolbar, melt } from '@melt-ui/svelte'; import { createPagination, createToolbar, melt } from '@melt-ui/svelte';
import { ChevronLeft, ChevronRight, LayoutList, LayoutGrid } from 'lucide-svelte'; import { ChevronLeft, ChevronRight, LayoutList, LayoutGrid, Check } from 'lucide-svelte';
import type { SearchSchema } from '$lib/zodValidation'; import { search_schema, type SearchSchema } from '$lib/zodValidation';
import Game from '$components/Game.svelte'; import Game from '$components/Game.svelte';
import { Label } from '$lib/components/ui/label'; import { Label } from '$lib/components/ui/label';
import { Input } from '$lib/components/ui/input'; import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import { Checkbox } from "$lib/components/ui/checkbox"; import { Checkbox } from '$lib/components/ui/checkbox';
import * as Form from "$lib/components/ui/form";
import GameSearchForm from '$components/search/GameSearchForm.svelte';
export let data; export let data;
const { form, errors }: SuperValidated<SearchSchema> = superForm(data.form);
const { games, totalCount } = data?.searchData; const { games, totalCount } = data?.searchData;
console.log('data found', data);
console.log('found games', games);
console.log('found totalCount', totalCount);
let submitButton: HTMLElement; let submitButton: HTMLElement;
let pageSize = +form?.limit || 10; let pageSize: number = data.form.limit || 10;
$: showPagination = totalCount > pageSize; $: showPagination = totalCount > pageSize;
@ -48,33 +53,10 @@
<div class="game-search"> <div class="game-search">
{#if dev} {#if dev}
<SuperDebug collapsible data={$form} /> <SuperDebug collapsible data={data.form} />
{/if} {/if}
<search> <GameSearchForm form={data.form} />
<form id="search-form" action="/search" method="GET">
<fieldset>
<Label for="label">Search</Label>
<Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="q" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} />
{#if $errors.q}
<p class="text-sm text-destructive">{$errors.q}</p>
{/if}
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
</fieldset>
<fieldset class="flex items-center space-x-2">
<Checkbox id="exact" bind:checked={$form.exact} aria-labelledby="exact-label" />
<Label
id="exact-label"
for="exact"
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Exact Search
</Label>
</fieldset>
<Button type="submit">Submit</Button>
</form>
</search>
<section class="games"> <section class="games">
<div> <div>
@ -139,15 +121,11 @@
button { button {
display: grid; display: grid;
place-items: center; place-items: center;
border-radius: 2px;
background-color: rgb(var(--color-white) / 1); background-color: rgb(var(--color-white) / 1);
color: rgb(var(--color-magnum-700) / 1); color: rgb(var(--color-magnum-700) / 1);
box-shadow: 0px 1px 2px 0px rgb(var(--color-black) / 0.05); box-shadow: 0px 1px 2px 0px rgb(var(--color-black) / 0.05);
font-size: 14px; font-size: 14px;
padding-inline: 0.75rem;
height: 2rem;
} }
button:hover { button:hover {

View file

@ -1,56 +1,27 @@
import type { RequestHandler } from '@sveltejs/kit';
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import { html as toReactNode } from 'satori-html';
import NotoSans from '$lib/fonts/NotoSans-Regular.ttf';
import SocialImageCard from '$components/socialImageCard.svelte'; import SocialImageCard from '$components/socialImageCard.svelte';
import { componentToPng } from '$lib/renderImage.js';
const height = 630; const height = 630;
const width = 1200; const width = 1200;
export const GET: RequestHandler = async ({ url }) => { /** @type {import('./$types').RequestHandler} */
export async function GET({ url }) {
try { try {
const ogImage = `${new URL(url.origin).href}images/bored-game.png`; const faviconImageLocation = 'images/bored-game.png';
const image = `${new URL(url.origin).href}${faviconImageLocation}`;
const header = url.searchParams.get('header') ?? undefined; const header = url.searchParams.get('header') ?? undefined;
const page = url.searchParams.get('page') ?? undefined; const page = url.searchParams.get('page') ?? undefined;
const content = url.searchParams.get('content') ?? ''; const content = url.searchParams.get('content') ?? '';
const result = SocialImageCard.render({
return componentToPng(SocialImageCard, {
header, header,
page, page,
content, content,
image: ogImage, image,
width, width: `${width}`,
height, height: `${height}`,
url: new URL(url.origin).href url: new URL(url.origin).href
}); }, height, width);
console.log('result', result);
const element = toReactNode(`${result.html}<style>${result.css.code}</style>`);
const svg = await satori(element, {
fonts: [
{
name: 'Noto Sans',
data: Buffer.from(NotoSans),
style: 'normal'
}
],
height,
width
});
const resvg = new Resvg(svg, {
fitTo: {
mode: 'width',
value: width
}
});
const image = resvg.render();
return new Response(image.asPng(), {
headers: {
'content-type': 'image/png'
}
});
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View file

@ -132,7 +132,7 @@
--toast-error-background: var(--tomatoOrange); --toast-error-background: var(--tomatoOrange);
/* Media Queryies - Not yet supported in CSS */ /* Media Queryies - Not yet supported in CSS */
/* /*
--xsmall: 340px; --xsmall: 340px;
--small: 500px; --small: 500px;
--large: 960px; --large: 960px;

View file

@ -1,7 +1,6 @@
import { sentrySvelteKit } from "@sentry/sveltekit"; import { sentrySvelteKit } from "@sentry/sveltekit";
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
// import fs from 'fs';
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
@ -19,6 +18,9 @@ export default defineConfig({
test: { test: {
include: ['src/**/*.{test,spec}.{js,ts}'] include: ['src/**/*.{test,spec}.{js,ts}']
}, },
define: {
SUPERFORMS_LEGACY: true
},
css: { css: {
devSourcemap: true, devSourcemap: true,
preprocessorOptions: { preprocessorOptions: {
@ -43,15 +45,3 @@ export default defineConfig({
} }
} }
}); });
// function rawFonts(ext) {
// return {
// name: 'vite-plugin-raw-fonts',
// transform(code, id) {
// if (ext.some((e) => id.endsWith(e))) {
// const buffer = fs.readFileSync(id);
// return { code: `export default ${JSON.stringify(buffer)}`, map: null };
// }
// }
// };
// }