Set favicon and logo on output

This commit is contained in:
thiloho
2024-08-25 16:31:12 +02:00
parent e7acc9749f
commit 926218fa34
7 changed files with 121 additions and 8 deletions

View File

@@ -0,0 +1,74 @@
-- migrate:up
CREATE OR REPLACE FUNCTION api.upload_file (BYTEA, OUT file_id UUID)
AS $$
DECLARE
_headers JSON := CURRENT_SETTING('request.headers', TRUE)::JSON;
_website_id UUID := (_headers ->> 'x-website-id')::UUID;
_mimetype TEXT := _headers ->> 'x-mimetype';
_original_filename TEXT := _headers ->> 'x-original-filename';
_allowed_mimetypes TEXT[] := ARRAY['image/png', 'image/jpeg', 'image/webp'];
_max_file_size INT := 5 * 1024 * 1024;
BEGIN
IF OCTET_LENGTH($1) = 0 THEN
RAISE invalid_parameter_value
USING message = 'No file data was provided';
END IF;
IF _mimetype IS NULL OR _mimetype NOT IN (
SELECT
UNNEST(_allowed_mimetypes)) THEN
RAISE invalid_parameter_value
USING message = 'Invalid MIME type. Allowed types are: png, jpg, webp';
END IF;
IF OCTET_LENGTH($1) > _max_file_size THEN
RAISE program_limit_exceeded
USING message = FORMAT('File size exceeds the maximum limit of %s MB', _max_file_size / (1024 * 1024));
END IF;
INSERT INTO internal.media (website_id, blob, mimetype, original_name)
VALUES (_website_id, $1, _mimetype, _original_filename)
RETURNING
id INTO file_id;
END;
$$
LANGUAGE plpgsql
SECURITY DEFINER;
GRANT EXECUTE ON FUNCTION api.upload_file (BYTEA) TO authenticated_user;
-- migrate:down
DROP FUNCTION api.upload_file(BYTEA);
CREATE FUNCTION api.upload_file (BYTEA, OUT file_id UUID)
AS $$
DECLARE
_headers JSON := CURRENT_SETTING('request.headers', TRUE)::JSON;
_website_id UUID := (_headers ->> 'x-website-id')::UUID;
_mimetype TEXT := _headers ->> 'x-mimetype';
_original_filename TEXT := _headers ->> 'x-original-filename';
_allowed_mimetypes TEXT[] := ARRAY['image/png', 'image/jpeg', 'image/webp'];
_max_file_size INT := 5 * 1024 * 1024;
BEGIN
IF OCTET_LENGTH($1) = 0 THEN
RAISE invalid_parameter_value
USING message = 'No file data was provided';
END IF;
IF _mimetype IS NULL OR _mimetype NOT IN (
SELECT
UNNEST(_allowed_mimetypes)) THEN
RAISE invalid_parameter_value
USING message = 'Invalid MIME type. Allowed types are: png, svg, jpg, webp';
END IF;
IF OCTET_LENGTH($1) > _max_file_size THEN
RAISE program_limit_exceeded
USING message = FORMAT('File size exceeds the maximum limit of %s MB', _max_file_size / (1024 * 1024));
END IF;
INSERT INTO internal.media (website_id, blob, mimetype, original_name)
VALUES (_website_id, $1, _mimetype, _original_filename)
RETURNING
id INTO file_id;
END;
$$
LANGUAGE plpgsql
SECURITY DEFINER;
GRANT EXECUTE ON FUNCTION api.upload_file (BYTEA) TO authenticated_user;

View File

@@ -4,6 +4,7 @@
import BlogFooter from "./common/BlogFooter.svelte"; import BlogFooter from "./common/BlogFooter.svelte";
const { const {
favicon,
title, title,
logoType, logoType,
logo, logo,
@@ -12,6 +13,7 @@
publicationDate, publicationDate,
footerAdditionalText footerAdditionalText
}: { }: {
favicon: string;
title: string; title: string;
logoType: "text" | "image"; logoType: "text" | "image";
logo: string; logo: string;
@@ -22,7 +24,7 @@
} = $props(); } = $props();
</script> </script>
<BlogHead {title} nestingLevel={1} /> <BlogHead {title} {favicon} nestingLevel={1} />
<BlogNav {logoType} {logo} /> <BlogNav {logoType} {logo} />

View File

@@ -4,6 +4,7 @@
import BlogFooter from "./common/BlogFooter.svelte"; import BlogFooter from "./common/BlogFooter.svelte";
const { const {
favicon,
title, title,
logoType, logoType,
logo, logo,
@@ -11,6 +12,7 @@
articles, articles,
footerAdditionalText footerAdditionalText
}: { }: {
favicon: string;
title: string; title: string;
logoType: "text" | "image"; logoType: "text" | "image";
logo: string; logo: string;
@@ -20,7 +22,7 @@
} = $props(); } = $props();
</script> </script>
<BlogHead {title} /> <BlogHead {title} {favicon} />
<BlogNav {logoType} {logo} /> <BlogNav {logoType} {logo} />

View File

@@ -1,5 +1,9 @@
<script lang="ts"> <script lang="ts">
const { title, nestingLevel = 0 }: { title: string; nestingLevel?: number } = $props(); const {
title,
favicon,
nestingLevel = 0
}: { title: string; favicon: string; nestingLevel?: number } = $props();
</script> </script>
<svelte:head> <svelte:head>
@@ -8,5 +12,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title> <title>{title}</title>
<link rel="stylesheet" href={`${"../".repeat(nestingLevel)}styles.css`} /> <link rel="stylesheet" href={`${"../".repeat(nestingLevel)}styles.css`} />
{#if favicon}
<link rel="icon" href={favicon} />
{/if}
</head> </head>
</svelte:head> </svelte:head>

View File

@@ -10,7 +10,7 @@
<strong>{logo}</strong> <strong>{logo}</strong>
</p> </p>
{:else} {:else}
<img src={logo} alt="" /> <img src={logo} width="24" height="24" alt="" />
{/if} {/if}
</a> </a>
</div> </div>

View File

@@ -57,9 +57,15 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
{ {
({ head, body } = render(BlogIndex, { ({ head, body } = render(BlogIndex, {
props: { props: {
favicon: websiteData.favicon_image
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.favicon_image}`
: "",
title: websiteData.title, title: websiteData.title,
logoType: websiteData.logo_type, logoType: websiteData.logo_type,
logo: websiteData.logo_text, logo:
websiteData.logo_type === "text"
? websiteData.logo_text
: `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.logo_image}`,
mainContent: md(websiteData.main_content ?? "", false), mainContent: md(websiteData.main_content ?? "", false),
articles: websiteData.articles ?? [], articles: websiteData.articles ?? [],
footerAdditionalText: md(websiteData.additional_text ?? "") footerAdditionalText: md(websiteData.additional_text ?? "")
@@ -110,9 +116,15 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
{ {
({ head, body } = render(BlogArticle, { ({ head, body } = render(BlogArticle, {
props: { props: {
favicon: websiteData.favicon_image
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.favicon_image}`
: "",
title: article.title, title: article.title,
logoType: websiteData.logo_type, logoType: websiteData.logo_type,
logo: websiteData.logo_text, logo:
websiteData.logo_type === "text"
? websiteData.logo_text
: `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.logo_image}`,
coverImage: article.cover_image coverImage: article.cover_image
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}` ? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`
: "", : "",
@@ -160,5 +172,21 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
const specificStyles = await readFile(`${process.cwd()}/template-styles/blog-styles.css`, { const specificStyles = await readFile(`${process.cwd()}/template-styles/blog-styles.css`, {
encoding: "utf-8" encoding: "utf-8"
}); });
await writeFile(join(uploadDir, "styles.css"), commonStyles.concat(specificStyles)); await writeFile(
join(uploadDir, "styles.css"),
commonStyles
.concat(specificStyles)
.replace(
/--color-accent:\s*(.*?);/,
`--color-accent: ${websiteData.accent_color_dark_theme};`
)
.replace(
/@media\s*\(prefers-color-scheme:\s*dark\)\s*{[^}]*--color-accent:\s*(.*?);/,
(match) =>
match.replace(
/--color-accent:\s*(.*?);/,
`--color-accent: ${websiteData.accent_color_light_theme};`
)
)
);
}; };

View File

@@ -166,7 +166,7 @@ summary {
background-color: var(--bg-tertiary); background-color: var(--bg-tertiary);
} }
:is(button, input, textarea, select, a, summary):focus, :is(button, input, textarea, select, a, summary, pre):focus,
#toggle-mobile-preview:checked + label { #toggle-mobile-preview:checked + label {
outline: 0.125rem solid var(--color-accent); outline: 0.125rem solid var(--color-accent);
outline-offset: 0.25rem; outline-offset: 0.25rem;