mirror of
https://codeberg.org/Freedium-cfd/web.git
synced 2026-03-11 09:04:37 +00:00
feat(render): enhance article rendering error handling & implement smooth theme transition
This commit is contained in:
parent
39b539a31c
commit
5ff68c22e6
9 changed files with 165 additions and 51 deletions
|
|
@ -148,7 +148,35 @@ console.log('Hello, world!');
|
|||
|
||||
|
||||
async def render_page(service_name: str):
|
||||
return JSONResponse(content={"text": TEXT, "service_name": service_name})
|
||||
return JSONResponse(
|
||||
content={
|
||||
"text": TEXT,
|
||||
"service_name": service_name,
|
||||
"article": {
|
||||
"title": "UploadThing is 5x Faster",
|
||||
"date": "2024-09-13T12:00:00Z",
|
||||
"author": {
|
||||
"name": "Theo Browne",
|
||||
"role": "CEO @ Ping Labs",
|
||||
"avatar": "https://picsum.photos/seed/post1/400/300",
|
||||
},
|
||||
"postImage": "https://picsum.photos/seed/postimage/1200/600",
|
||||
"tableOfContents": [
|
||||
{"id": "v7-is-here", "title": "V7 Is Here!"},
|
||||
{"id": "benchmarks", "title": "Benchmarks"},
|
||||
{"id": "the-road-to-v7", "title": "The Road To V7"},
|
||||
{
|
||||
"id": "uploadthing-has-served",
|
||||
"title": "UploadThing Has Served...",
|
||||
},
|
||||
{
|
||||
"id": "and-were-just-getting-started",
|
||||
"title": "...and we're just getting started",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def register_render_router(router: APIRouter) -> None:
|
||||
|
|
|
|||
|
|
@ -2,11 +2,6 @@
|
|||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body,
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0, 0%, 97%;
|
||||
|
|
@ -71,6 +66,41 @@ html {
|
|||
|
||||
--ring: 240 4.9% 83.9%;
|
||||
}
|
||||
body,
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
html {
|
||||
@apply transition-colors duration-200;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
@apply text-gray-100 bg-gray-900;
|
||||
}
|
||||
|
||||
html:not(.dark) {
|
||||
@apply text-gray-900 bg-white;
|
||||
}
|
||||
|
||||
* {
|
||||
@apply transition-colors duration-200 border-border;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
:root {
|
||||
@apply transition-all duration-200;
|
||||
}
|
||||
|
||||
/* Update existing html dark/light modes to include transition */
|
||||
html.dark {
|
||||
@apply text-gray-100 transition-all duration-200 bg-gray-900;
|
||||
}
|
||||
|
||||
html:not(.dark) {
|
||||
@apply text-gray-900 transition-all duration-200 bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="w-full bg-yellow-400 text-center py-1 px-4">
|
||||
<div class="w-full bg-yellow-400 text-center py-1 px-4 relative">
|
||||
<p class="text-yellow-900">
|
||||
Advertise here and support our project! Reach out to us at admin@freedium.cfd
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,37 @@
|
|||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
|
||||
let isNavOpen = false;
|
||||
let isSearchOpen = false;
|
||||
let isHeaderVisible = true;
|
||||
let lastScrollY = 0;
|
||||
|
||||
// Handle scroll events
|
||||
function handleScroll() {
|
||||
const currentScrollY = window.scrollY;
|
||||
const documentHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const scrollPercentage = (currentScrollY / documentHeight) * 100;
|
||||
|
||||
if (scrollPercentage > 15) {
|
||||
isHeaderVisible = lastScrollY > currentScrollY;
|
||||
} else {
|
||||
isHeaderVisible = true;
|
||||
}
|
||||
|
||||
lastScrollY = currentScrollY;
|
||||
}
|
||||
|
||||
// Bind scroll event when component mounts
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
});
|
||||
|
||||
const toggleNav = () => {
|
||||
isNavOpen = !isNavOpen;
|
||||
};
|
||||
|
||||
let isSearchOpen = false;
|
||||
const toggleSearch = () => {
|
||||
isSearchOpen = !isSearchOpen;
|
||||
};
|
||||
|
|
@ -27,7 +52,8 @@
|
|||
|
||||
<nav
|
||||
id="header"
|
||||
class="sticky top-0 z-20 w-full border-b shadow-sm bg-white/95 backdrop-blur-sm dark:bg-zinc-900/95 border-zinc-200 dark:border-zinc-800"
|
||||
class="sticky top-0 z-20 w-full transition-transform duration-300 border-b shadow-sm bg-white/95 backdrop-blur-sm dark:bg-zinc-900/95 border-zinc-200 dark:border-zinc-800"
|
||||
style="transform: translateY({isHeaderVisible ? '0' : '-100%'})"
|
||||
>
|
||||
<ProgressLine />
|
||||
|
||||
|
|
@ -119,7 +145,12 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<Advertise />
|
||||
<div
|
||||
class="transition-transform duration-300"
|
||||
style="transform: translateY({isHeaderVisible ? '0' : '-100%'})"
|
||||
>
|
||||
<Advertise />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<SearchDialog bind:open={isSearchOpen} />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { fade } from 'svelte/transition';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const MIN_DURATION = 400;
|
||||
const MIN_DURATION = 200;
|
||||
const isVisible = writable(false);
|
||||
|
||||
$: {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
import ProgressOverlay from '$lib/elements/ProgressOverlay.svelte';
|
||||
</script>
|
||||
|
||||
<ProgressOverlay />
|
||||
<Toaster position="top-right" expand={true} />
|
||||
<slot />
|
||||
<div class="transition-all duration-200 ease-in-out">
|
||||
<ProgressOverlay />
|
||||
<Toaster position="top-right" expand={true} />
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -137,6 +137,19 @@ export async function load({ params }) {
|
|||
transformed = await render("medium");
|
||||
} catch (err) {
|
||||
console.error("Failed to render article:", err);
|
||||
return {
|
||||
slug: params.slug,
|
||||
loading: false,
|
||||
content: null,
|
||||
article: null,
|
||||
error: {
|
||||
status: 500,
|
||||
message: "Failed to render article",
|
||||
code: ErrorCodes.RENDER_ERROR,
|
||||
details:
|
||||
process.env.NODE_ENV === "development" ? err.message : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!transformed) {
|
||||
|
|
@ -164,7 +177,7 @@ export async function load({ params }) {
|
|||
slug: params.slug,
|
||||
loading: false,
|
||||
content: code,
|
||||
article: MOCK_ARTICLE,
|
||||
article: transformed.article,
|
||||
error: null,
|
||||
};
|
||||
} catch (compileError) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import apiFetch from "@/api";
|
|||
|
||||
async function render(serviceName) {
|
||||
const response = await apiFetch(`/services/${serviceName}/render`);
|
||||
return response;
|
||||
if (!response) {
|
||||
throw new Error("Failed to fetch article");
|
||||
}
|
||||
return {
|
||||
text: response.text,
|
||||
article: response.article,
|
||||
};
|
||||
}
|
||||
|
||||
export { render };
|
||||
|
|
|
|||
|
|
@ -1,66 +1,70 @@
|
|||
import { fontFamily } from 'tailwindcss/defaultTheme';
|
||||
import { addDynamicIconSelectors } from '@iconify/tailwind';
|
||||
import { fontFamily } from "tailwindcss/defaultTheme";
|
||||
import { addDynamicIconSelectors } from "@iconify/tailwind";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const config = {
|
||||
darkMode: ['class'],
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
safelist: ['dark'],
|
||||
darkMode: ["class"],
|
||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
safelist: ["dark"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
'2xl': '1400px'
|
||||
}
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: 'hsla(var(--border))',
|
||||
input: 'hsla(var(--input))',
|
||||
ring: 'hsla(var(--ring))',
|
||||
background: 'hsla(var(--background))',
|
||||
foreground: 'hsla(var(--foreground))',
|
||||
transitionProperty: {
|
||||
colors:
|
||||
"color, background-color, border-color, text-decoration-color, fill, stroke",
|
||||
},
|
||||
border: "hsla(var(--border))",
|
||||
input: "hsla(var(--input))",
|
||||
ring: "hsla(var(--ring))",
|
||||
background: "hsla(var(--background))",
|
||||
foreground: "hsla(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: 'hsla(var(--primary))',
|
||||
foreground: 'hsla(var(--primary-foreground))'
|
||||
DEFAULT: "hsla(var(--primary))",
|
||||
foreground: "hsla(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsla(var(--secondary))',
|
||||
foreground: 'hsla(var(--secondary-foreground))'
|
||||
DEFAULT: "hsla(var(--secondary))",
|
||||
foreground: "hsla(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsla(var(--destructive))',
|
||||
foreground: 'hsla(var(--destructive-foreground))'
|
||||
DEFAULT: "hsla(var(--destructive))",
|
||||
foreground: "hsla(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsla(var(--muted))',
|
||||
foreground: 'hsla(var(--muted-foreground))'
|
||||
DEFAULT: "hsla(var(--muted))",
|
||||
foreground: "hsla(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsla(var(--accent))',
|
||||
foreground: 'hsla(var(--accent-foreground))'
|
||||
DEFAULT: "hsla(var(--accent))",
|
||||
foreground: "hsla(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsla(var(--popover))',
|
||||
foreground: 'hsla(var(--popover-foreground))'
|
||||
DEFAULT: "hsla(var(--popover))",
|
||||
foreground: "hsla(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsla(var(--card))',
|
||||
foreground: 'hsla(var(--card-foreground))'
|
||||
}
|
||||
DEFAULT: "hsla(var(--card))",
|
||||
foreground: "hsla(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [...fontFamily.sans]
|
||||
}
|
||||
}
|
||||
sans: [...fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [addDynamicIconSelectors()]
|
||||
plugins: [addDynamicIconSelectors()],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
Loading…
Reference in a new issue