fix(new-freedium): bug fixes, settings restructure, minor UI fixes, overlay loader implement

This commit is contained in:
ZhymabekRoman 2024-12-15 21:42:05 +05:00
parent 286691ea87
commit 39b539a31c
17 changed files with 392 additions and 206 deletions

View file

@ -1,4 +1,12 @@
from freedium_library.__init__ import __VERSION__
from freedium_library.api.container import APIContainer
__NAME__ = "Freedium Library API"
api_container = APIContainer()
api_container.wire(
modules=["freedium_library.api.settings", "freedium_library.api.main"]
)
__all__ = ["__NAME__", "__VERSION__"]

View file

@ -10,17 +10,23 @@ from freedium_library.api.settings import ApplicationSettings
def create_application() -> FastAPI:
container = APIContainer()
container.wire(modules=["freedium_library.api.settings"])
api_container = APIContainer()
settings = ApplicationSettings(container=container)
config = container.config()
settings = ApplicationSettings(container=api_container)
config = api_container.config()
if config.DISABLED_DOCS:
logger.warning(f"Documentation is disabled: {config.DISABLED_DOCS}")
settings.disable_docs()
app = FastAPI(**settings.to_dict(), lifespan=lifespan) # type: ignore
app = FastAPI(
title=settings.title,
version=settings.version,
openapi_url=settings.openapi_url,
docs_url=settings.docs_url,
redoc_url=settings.redoc_url,
lifespan=lifespan,
)
register_router(app, settings.prefix_path)
register_error_handler(app)

View file

@ -1,10 +1,25 @@
# freedium-library/src/freedium_library/api/config.py
from typing import Optional
from pydantic import Field
from freedium_library.utils.meta.pydantic import BaseConfig, BaseSettingsConfigDict
class ServerConfig(BaseConfig):
model_config = BaseSettingsConfigDict(env_prefix="SERVER_")
host: str = Field(default="0.0.0.0")
port: int = Field(default=7080)
reload: bool = Field(default=False)
workers: Optional[int] = Field(default=None)
class APIConfig(BaseConfig):
model_config = BaseSettingsConfigDict(env_prefix="API_")
DISABLED_DOCS: bool = Field(default=False)
PREFIX_PATH: str = Field(default="/api")
HOST: str = Field(default="0.0.0.0")
PORT: int = Field(default=7080)
MAX_WORKERS: int = Field(default=10)

View file

@ -1,7 +1,8 @@
from dependency_injector import containers, providers
from freedium_library.api.config import APIConfig
from freedium_library.api.config import APIConfig, ServerConfig
class APIContainer(containers.DeclarativeContainer):
config = providers.Singleton(APIConfig)
server_config = providers.Singleton(ServerConfig)

View file

@ -2,23 +2,52 @@ import os
from typing import Optional
import uvicorn
from dependency_injector.wiring import Provide, inject
from loguru import logger
from freedium_library.api.config import APIConfig, ServerConfig
from freedium_library.api.container import APIContainer
def calculate_workers(
requested_workers: Optional[int] = None, max_workers: int = 10
) -> int:
if requested_workers is not None:
return requested_workers
cpu_count = os.cpu_count() or 1
workers = min(cpu_count + 1, max_workers)
if workers == 1:
logger.warning("Only one worker, this is not recommended for production.")
elif workers == max_workers:
logger.warning(
f"Using hardcoded maximum workers ({max_workers}), consider passing a lower number."
)
else:
logger.info(f"Using {workers} workers based on CPU count.")
return workers
@inject
def start_server(
host: str = "0.0.0.0",
port: int = 7080,
reload: bool = False,
workers: Optional[int] = None,
server_config: Optional[ServerConfig] = Provide[APIContainer.server_config],
config: APIConfig = Provide[APIContainer.config],
) -> None:
if workers is None:
workers = (os.cpu_count() or 1) + 1
logger.warning(f"No workers specified, using CPU count {workers}")
if server_config is None:
logger.warning("No server config provided, using defaults.")
server_config = ServerConfig(
host=config.HOST,
port=config.PORT,
)
workers = calculate_workers(server_config.workers, config.MAX_WORKERS)
uvicorn.run(
"freedium_library.api.app:app",
host=host,
port=port,
reload=reload,
host=server_config.host,
port=server_config.port,
reload=server_config.reload,
workers=workers,
)

View file

@ -9,15 +9,13 @@ from freedium_library.api.container import APIContainer
class ApplicationSettings(BaseModel):
title: str = Field(default=f"{__NAME__} API Service")
version: str = Field(default=__VERSION__)
title: str = Field(default=f"{__NAME__} API Service", frozen=True)
version: str = Field(default=__VERSION__, frozen=True)
prefix_path: str = Field(default="/api")
openapi_url: Optional[str] = None
docs_url: Optional[str] = None
redoc_url: Optional[str] = None
model_config = {"arbitrary_types_allowed": True}
@inject
def __init__(
self,
@ -41,6 +39,3 @@ class ApplicationSettings(BaseModel):
self.openapi_url = None
self.docs_url = None
self.redoc_url = None
def to_dict(self) -> dict[str, str | None]:
return self.model_dump()

View file

@ -1,5 +1,8 @@
import click
from freedium_library.api.config import ServerConfig
from freedium_library.api.main import start_server
@click.command()
@click.option("--host", default="0.0.0.0", help="Host address to bind to")
@ -11,6 +14,9 @@ import click
default=False,
)
def cli(host: str, port: int, hot_reload: bool):
from freedium_library.api.main import start_server
start_server(host=host, port=port, reload=hot_reload)
server_config = ServerConfig(
host=host,
port=port,
reload=hot_reload,
)
start_server(server_config=server_config)

Binary file not shown.

View file

@ -2,6 +2,7 @@
@tailwind components;
@tailwind utilities;
body,
html {
scroll-behavior: smooth;
}

View file

@ -1,11 +1,11 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import DrawerOverlay from "./drawer-overlay.svelte";
import { cn } from "$lib/utils.js";
import { Drawer as DrawerPrimitive } from 'vaul-svelte';
import DrawerOverlay from './drawer-overlay.svelte';
import { cn } from '$lib/utils.js';
type $$Props = DrawerPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
@ -13,12 +13,12 @@
<DrawerOverlay />
<DrawerPrimitive.Content
class={cn(
"bg-background fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border",
'bg-background fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border',
className
)}
{...$$restProps}
>
<div class="bg-muted mx-auto mt-4 h-2 w-[100px] rounded-full"></div>
<div class="bg-primary mx-auto mt-4 h-2 w-[100px] rounded-full"></div>
<slot />
</DrawerPrimitive.Content>
</DrawerPrimitive.Portal>

View file

@ -1,17 +1,17 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { cn } from "$lib/utils.js";
import { Drawer as DrawerPrimitive } from 'vaul-svelte';
import { cn } from '$lib/utils.js';
type $$Props = DrawerPrimitive.OverlayProps;
export let el: $$Props["el"] = undefined;
let className: $$Props["class"] = undefined;
export let el: $$Props['el'] = undefined;
let className: $$Props['class'] = undefined;
export { className as class };
</script>
<DrawerPrimitive.Overlay
bind:el
class={cn("fixed inset-0 z-50 bg-black/80", className)}
class={cn('fixed inset-0 z-50 bg-black/80', className)}
{...$$restProps}
>
<slot />

View file

@ -1,4 +1,5 @@
<script>
import { page } from '$app/stores';
import Header from '$lib/elements/Header.svelte';
import { formatDate } from '$lib/utils/dateFormatter';
import ImageZoom from '$lib/elements/ImageZoom.svelte';
@ -10,118 +11,174 @@
$: ({ article, content, loading } = data);
$: contentLoaded = !loading && !!content;
$: error = data.error;
$: showSkeleton = !error && !contentLoaded;
function getErrorMessage(error) {
if (!error) return '';
switch (error.code) {
case 'ARTICLE_NOT_FOUND':
return "We couldn't find the article you're looking for.";
case 'RENDER_ERROR':
return 'There was a problem preparing this article.';
case 'COMPILE_ERROR':
return 'There was a problem processing the article content.';
default:
return error.message || 'An unexpected error occurred.';
}
}
</script>
<svelte:head>
<title>{article.title} - Freedium</title>
<title>{error ? 'Freedium' : data?.article?.title || 'Freedium'} - Freedium</title>
<meta name="description" content="Read about the latest updates to UploadThing" />
</svelte:head>
<Header />
<main class="max-w-5xl px-4 py-8 mx-auto">
<nav class="flex items-center justify-between gap-2 mb-4 text-center">
<a
href="/"
class="flex items-center justify-center transition bg-white rounded-full shadow-md text-primary hover:text-primary/90 group size-8 shadow-zinc-800/5 ring-1 ring-zinc-900/5 dark:border dark:border-zinc-700/50 dark:bg-zinc-800 dark:ring-0 dark:ring-white/10 dark:hover:border-zinc-700 dark:hover:ring-white/20"
>
<span class="icon-[heroicons--arrow-left-20-solid] size-6" />
</a>
<a href="/" class="font-bold text-primary hover:text-primary/90"> Original article</a>
</nav>
<div class="lg:flex lg:space-x-8">
<article class="flex-grow overflow-hidden bg-white rounded-lg shadow-lg dark:bg-zinc-900">
{#if !contentLoaded}
<Skeleton class="w-full h-96" />
<div class="p-6 bg-gray-50 dark:bg-zinc-800">
<Skeleton class="w-32 h-4 mb-2" />
<Skeleton class="w-full h-10 mb-4" />
<div class="flex items-center">
<Skeleton class="w-12 h-12 mr-4 rounded-full" />
<div class="space-y-2">
<Skeleton class="w-40 h-4" />
<Skeleton class="w-32 h-4" />
</div>
</div>
</div>
<div class="p-6">
<div class="space-y-4">
<Skeleton class="w-full h-4" />
<Skeleton class="w-full h-4" />
<Skeleton class="w-3/4 h-4" />
</div>
</div>
{:else}
{#if article.postImage}
<ImageZoom
src={article.postImage}
alt="Post cover image"
class="object-cover w-full h-auto min-h-96"
/>
{/if}
<header class="p-6 bg-gray-50 dark:bg-zinc-800">
<p class="mb-2 text-gray-600 dark:text-gray-400">{formatDate(article.date)}</p>
<h1 class="mb-4 text-4xl font-bold text-gray-900 dark:text-white">{article.title}</h1>
<div class="flex items-center">
<img src={article.author.avatar} alt="" class="w-12 h-12 mr-4 rounded-full" />
<div>
<p class="font-semibold text-gray-900 dark:text-white">{article.author.name}</p>
<p class="text-gray-600 dark:text-gray-400">{article.author.role}</p>
</div>
</div>
</header>
<div class="p-6 {article.postImage ? '' : 'pt-0'} dark:text-gray-300">
<div class="prose max-w-none">
{#if content}
{@html content}
{:else}
<p>Error loading content</p>
{/if}
</div>
</div>
<div class="flex flex-col min-h-screen">
<main class="flex-1 w-full h-full max-w-5xl px-4 py-8">
<nav class="flex items-center justify-between gap-2 mb-4 text-center">
<a
href="/"
class="flex items-center justify-center transition bg-white rounded-full shadow-md text-primary hover:text-primary/90 group size-8 shadow-zinc-800/5 ring-1 ring-zinc-900/5 dark:border dark:border-zinc-700/50 dark:bg-zinc-800 dark:ring-0 dark:ring-white/10 dark:hover:border-zinc-700 dark:hover:ring-white/20"
>
<span class="icon-[heroicons--arrow-left-20-solid] size-6" />
</a>
{#if article?.url}
<a href={article.url} class="font-bold text-primary hover:text-primary/90">
Original article
</a>
{/if}
</article>
</nav>
<aside class="order-first mt-7 lg:mt-0 lg:min-w-80 lg:order-none">
{#if !contentLoaded}
<div class="w-full p-4 bg-white rounded-lg shadow-lg dark:bg-zinc-900">
<Skeleton class="w-32 h-6 mb-4" />
<div class="space-y-2">
<Skeleton class="w-full h-4" />
<Skeleton class="w-full h-4" />
<Skeleton class="w-3/4 h-4" />
</div>
</div>
{:else}
<nav
aria-labelledby="toc-heading"
class="w-full p-4 bg-white rounded-lg shadow-lg dark:bg-zinc-900 lg:sticky lg:top-36"
{#if error}
<div class="p-6 text-center bg-white rounded-lg shadow-lg dark:bg-zinc-900">
<h1
class="mb-4 text-2xl font-bold {error.status === 404 ? 'text-amber-600' : 'text-red-600'}"
>
<h2 id="toc-heading" class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">
Contents
</h2>
{#if article.tableOfContents && article.tableOfContents.length > 0}
<ul class="space-y-2">
{#each article.tableOfContents as item}
<li>
<a
href={`#${item.id}`}
class="transition-colors text-zinc-800 hover:text-zinc-900 dark:text-gray-300 dark:hover:text-white"
>
{item.title}
</a>
</li>
{/each}
</ul>
{:else}
<p class="dark:text-gray-300">No table of contents available</p>
{/if}
</nav>
{/if}
</aside>
</div>
</main>
{error.status === 404 ? 'Article Not Found' : 'Error Loading Article'}
</h1>
<p class="text-gray-600 dark:text-gray-400">
{getErrorMessage(error)}
</p>
{#if error.details && process.env.NODE_ENV === 'development'}
<pre class="p-4 mt-4 overflow-auto text-sm bg-gray-100 dark:bg-zinc-800">
{error.details}
</pre>
{/if}
<div class="mt-6 space-x-4">
<a
href="/"
class="inline-block px-4 py-2 text-white rounded-md bg-primary hover:bg-primary/90"
>
Return Home
</a>
<button
class="inline-block px-4 py-2 border rounded-md border-primary text-primary hover:bg-primary/10"
on:click={() => window.location.reload()}
>
Try Again
</button>
</div>
</div>
{:else if showSkeleton}
<div class="lg:flex lg:space-x-8">
<article class="flex-grow overflow-hidden bg-white rounded-lg shadow-lg dark:bg-zinc-900">
<Skeleton class="w-full h-96" />
<div class="p-6 bg-gray-50 dark:bg-zinc-800">
<Skeleton class="w-32 h-4 mb-2" />
<Skeleton class="w-full h-10 mb-4" />
<div class="flex items-center">
<Skeleton class="w-12 h-12 mr-4 rounded-full" />
<div class="space-y-2">
<Skeleton class="w-40 h-4" />
<Skeleton class="w-32 h-4" />
</div>
</div>
</div>
<div class="p-6">
<div class="space-y-4">
<Skeleton class="w-full h-4" />
<Skeleton class="w-full h-4" />
<Skeleton class="w-3/4 h-4" />
</div>
</div>
</article>
<Footer />
<aside class="order-first mt-7 lg:mt-0 lg:min-w-80 lg:order-none">
<div class="w-full p-4 bg-white rounded-lg shadow-lg dark:bg-zinc-900">
<Skeleton class="w-32 h-6 mb-4" />
<div class="space-y-2">
<Skeleton class="w-full h-4" />
<Skeleton class="w-full h-4" />
<Skeleton class="w-3/4 h-4" />
</div>
</div>
</aside>
</div>
{:else}
<div class="lg:flex lg:space-x-8">
<article class="flex-grow overflow-hidden bg-white rounded-lg shadow-lg dark:bg-zinc-900">
{#if article.postImage}
<ImageZoom
src={article.postImage}
alt="Post cover image"
class="object-cover w-full h-auto min-h-96"
/>
{/if}
<header class="p-6 bg-gray-50 dark:bg-zinc-800">
<p class="mb-2 text-gray-600 dark:text-gray-400">{formatDate(article.date)}</p>
<h1 class="mb-4 text-4xl font-bold text-gray-900 dark:text-white">{article.title}</h1>
<div class="flex items-center">
<img src={article.author.avatar} alt="" class="w-12 h-12 mr-4 rounded-full" />
<div>
<p class="font-semibold text-gray-900 dark:text-white">{article.author.name}</p>
<p class="text-gray-600 dark:text-gray-400">{article.author.role}</p>
</div>
</div>
</header>
<div class="p-6 {article.postImage ? '' : 'pt-0'} dark:text-gray-300">
<div class="prose max-w-none">
{#if content}
{@html content}
{:else}
<p>Error loading content</p>
{/if}
</div>
</div>
</article>
<aside class="order-first mt-7 lg:mt-0 lg:min-w-80 lg:order-none">
<nav
aria-labelledby="toc-heading"
class="w-full p-4 bg-white rounded-lg shadow-lg dark:bg-zinc-900 lg:sticky lg:top-36"
>
<h2 id="toc-heading" class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">
Contents
</h2>
{#if article.tableOfContents && article.tableOfContents.length > 0}
<ul class="space-y-2">
{#each article.tableOfContents as item}
<li>
<a
href={`#${item.id}`}
class="transition-colors text-zinc-800 hover:text-zinc-900 dark:text-gray-300 dark:hover:text-white"
>
{item.title}
</a>
</li>
{/each}
</ul>
{:else}
<p class="dark:text-gray-300">No table of contents available</p>
{/if}
</nav>
</aside>
</div>
{/if}
</main>
<Footer />
</div>

View file

@ -0,0 +1,24 @@
<script lang="ts">
import { navigating } from '$app/stores';
import { fade } from 'svelte/transition';
import { writable } from 'svelte/store';
const MIN_DURATION = 400;
const isVisible = writable(false);
$: {
if ($navigating) {
$isVisible = true;
} else {
setTimeout(() => {
$isVisible = false;
}, MIN_DURATION);
}
}
</script>
{#if $isVisible}
<div class="fixed inset-0 z-40 bg-black/30 dark:bg-black/40" transition:fade={{ duration: 300 }}>
<div class="fixed z-50 top-4 right-4"></div>
</div>
{/if}

View file

@ -102,62 +102,66 @@
</div>
</Drawer.Trigger>
<form on:submit|preventDefault={handleSubmit}>
<Drawer.Content>
<Drawer.Header class="text-left">
<Drawer.Title class="text-lg font-semibold tracking-tight">Report a Problem</Drawer.Title>
<Drawer.Description class="text-foreground-alt">
Please describe the problem you're experiencing. We'll look into it as soon as possible.
</Drawer.Description>
</Drawer.Header>
<div class="px-4">
<div class="grid grid-cols-1 gap-2 md:grid-cols-2 pt-7">
<div class="flex flex-col items-start gap-2">
<Label.Root class="text-sm font-medium">Problem Type</Label.Root>
<RadioGroup.Root bind:value={problemType} class="space-y-2">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="ui_problem" id="ui-problem-mobile" />
<Label.Root for="ui-problem-mobile">UI problem</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="article_not_full" id="article-not-full-mobile" />
<Label.Root for="article-not-full-mobile">Article is not full</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="suggestion" id="suggestion-mobile" />
<Label.Root for="suggestion-mobile">Suggestion</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="vulnerability" id="vulnerability-mobile" />
<Label.Root for="vulnerability-mobile">Vulnerability (XSS, etc.)</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="other" id="other-mobile" />
<Label.Root for="other-mobile">Other</Label.Root>
</div>
</RadioGroup.Root>
<Drawer.Content class="max-h-[90dvh] flex flex-col">
<div class="flex-1 mb-5 overflow-y-auto">
<Drawer.Header class="text-left">
<Drawer.Title class="text-lg font-semibold tracking-tight"
>Report a Problem</Drawer.Title
>
<Drawer.Description class="text-foreground-alt">
Please describe the problem you're experiencing. We'll look into it as soon as
possible.
</Drawer.Description>
</Drawer.Header>
<div class="px-4 space-y-4">
<div class="space-y-4">
<div class="flex flex-col items-start space-y-2">
<Label.Root class="text-sm font-medium">Problem Type</Label.Root>
<RadioGroup.Root bind:value={problemType} class="space-y-2">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="ui_problem" id="ui-problem-mobile" />
<Label.Root for="ui-problem-mobile">UI problem</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="article_not_full" id="article-not-full-mobile" />
<Label.Root for="article-not-full-mobile">Article is not full</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="suggestion" id="suggestion-mobile" />
<Label.Root for="suggestion-mobile">Suggestion</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="vulnerability" id="vulnerability-mobile" />
<Label.Root for="vulnerability-mobile">Vulnerability (XSS, etc.)</Label.Root>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="other" id="other-mobile" />
<Label.Root for="other-mobile">Other</Label.Root>
</div>
</RadioGroup.Root>
</div>
<div class="flex flex-col items-start space-y-2">
<Label.Root for="problemDescription-mobile" class="text-sm font-medium"
>Problem Description</Label.Root
>
<Textarea
id="problemDescription-mobile"
bind:value={problemDescription}
placeholder="Describe the problem you're experiencing..."
rows={12}
/>
<p class="mt-2 text-sm text-foreground-alt">
The current opened page will be automatically attached to your report.
</p>
</div>
</div>
<div class="flex flex-col items-start gap-1">
<Label.Root for="problemDescription-mobile" class="text-sm font-medium"
>Problem Description</Label.Root
>
<Textarea
id="problemDescription-mobile"
bind:value={problemDescription}
placeholder="Describe the problem you're experiencing..."
rows={12}
/>
<p class="mt-2 text-sm text-foreground-alt">
The current opened page will be automatically attached to your report.
</p>
<div class="flex justify-center gap-2">
<Drawer.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Drawer.Close>
<Button type="submit">Submit</Button>
</div>
</div>
</div>
<Drawer.Footer class="p-4">
<div class="flex justify-end gap-2">
<Drawer.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Drawer.Close>
<Button type="submit">Submit</Button>
</div>
</Drawer.Footer>
</Drawer.Content>
</form>
</Drawer.Root>

View file

@ -1,7 +1,9 @@
<script lang="ts">
import '../app.css';
import { Toaster } from '$lib/components/ui/sonner';
import ProgressOverlay from '$lib/elements/ProgressOverlay.svelte';
</script>
<ProgressOverlay />
<Toaster position="top-right" expand={true} />
<slot />

View file

@ -27,6 +27,16 @@ const CODE_ATTRIBUTES = {
"data-ms-editor": "false",
};
let highlighterInstance = null;
async function getHighlighter() {
if (!highlighterInstance) {
highlighterInstance = await createHighlighter(HIGHLIGHT_CONFIG);
await highlighterInstance.loadLanguage("javascript", "typescript");
}
return highlighterInstance;
}
const MOCK_ARTICLE = {
title: "UploadThing is 5x Faster",
date: "2024-09-13T12:00:00Z",
@ -75,8 +85,7 @@ function createCodeCopyButton(code, toggleMs = 3000) {
}
async function createHighlightedCode(code, lang = "text") {
const highlighter = await createHighlighter(HIGHLIGHT_CONFIG);
await highlighter.loadLanguage("javascript", "typescript");
const highlighter = await getHighlighter();
const lightHtml = highlighter.codeToHtml(code, {
lang,
@ -115,9 +124,36 @@ async function createHighlightedCode(code, lang = "text") {
`;
}
const ErrorCodes = {
ARTICLE_NOT_FOUND: "ARTICLE_NOT_FOUND",
RENDER_ERROR: "RENDER_ERROR",
COMPILE_ERROR: "COMPILE_ERROR",
INTERNAL_ERROR: "INTERNAL_ERROR",
};
export async function load({ params }) {
let transformed = null;
try {
transformed = await render("medium");
} catch (err) {
console.error("Failed to render article:", err);
}
if (!transformed) {
return {
slug: params.slug,
loading: false,
content: null,
article: null,
error: {
status: 404,
message: "Article not found",
code: ErrorCodes.ARTICLE_NOT_FOUND,
},
};
}
try {
const transformed = await render("medium");
const { code } = await compile(transformed.text, {
highlight: {
highlighter: createHighlightedCode,
@ -129,21 +165,23 @@ export async function load({ params }) {
loading: false,
content: code,
article: MOCK_ARTICLE,
error: null,
};
} catch (error) {
console.error("Failed to load article:", error);
} catch (compileError) {
return {
slug: params.slug,
loading: false,
content: null,
article: {
title: "Article Not Found",
date: new Date().toISOString(),
author: { name: "Unknown", role: "", avatar: "" },
postImage: null,
tableOfContents: [],
article: null,
error: {
status: 500,
message: "Failed to compile article content",
code: ErrorCodes.COMPILE_ERROR,
details:
process.env.NODE_ENV === "development"
? compileError.message
: undefined,
},
error: error.message,
};
}
}

View file

@ -23,7 +23,7 @@ url_for_test = {
"https://www.freedium.cfd/c81e00f6320d",
}
blacklist_url = {"51e23c5a2aac"}
blacklist_url = {"51e23c5a2aac", "e65e737baa29"}
def main():