BLOG-45 Post content page #67

Merged
squid merged 9 commits from BLOG-45_post_content_page into main 2025-07-24 22:20:58 +08:00
6 changed files with 85 additions and 15 deletions
Showing only changes of commit f07460d95c - Show all commits

View File

@ -23,10 +23,12 @@
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"@types/markdown-it": "^14.1.2",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0", "eslint-plugin-svelte": "^3.0.0",
"globals": "^16.0.0", "globals": "^16.0.0",
"markdown-it": "^14.1.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",

View File

@ -35,6 +35,9 @@ importers:
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.1.11(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1)) version: 4.1.11(vite@7.0.5(jiti@2.4.2)(lightningcss@1.30.1))
'@types/markdown-it':
specifier: ^14.1.2
version: 14.1.2
eslint: eslint:
specifier: ^9.18.0 specifier: ^9.18.0
version: 9.31.0(jiti@2.4.2) version: 9.31.0(jiti@2.4.2)
@ -47,6 +50,9 @@ importers:
globals: globals:
specifier: ^16.0.0 specifier: ^16.0.0
version: 16.3.0 version: 16.3.0
markdown-it:
specifier: ^14.1.0
version: 14.1.0
prettier: prettier:
specifier: ^3.4.2 specifier: ^3.4.2
version: 3.6.2 version: 3.6.2
@ -622,6 +628,15 @@ packages:
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/markdown-it@14.1.2':
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
'@types/resolve@1.20.2': '@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@ -798,6 +813,10 @@ packages:
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
esbuild@0.25.8: esbuild@0.25.8:
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -1098,6 +1117,9 @@ packages:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
locate-character@3.0.0: locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
@ -1117,6 +1139,13 @@ packages:
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
merge2@1.4.1: merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -1314,6 +1343,10 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
punycode@2.3.1: punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1452,6 +1485,9 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
uri-js@4.4.1: uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@ -1950,6 +1986,15 @@ snapshots:
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/linkify-it@5.0.0': {}
'@types/markdown-it@14.1.2':
dependencies:
'@types/linkify-it': 5.0.0
'@types/mdurl': 2.0.0
'@types/mdurl@2.0.0': {}
'@types/resolve@1.20.2': {} '@types/resolve@1.20.2': {}
'@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)': '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)':
@ -2135,6 +2180,8 @@ snapshots:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
tapable: 2.2.2 tapable: 2.2.2
entities@4.5.0: {}
esbuild@0.25.8: esbuild@0.25.8:
optionalDependencies: optionalDependencies:
'@esbuild/aix-ppc64': 0.25.8 '@esbuild/aix-ppc64': 0.25.8
@ -2441,6 +2488,10 @@ snapshots:
lilconfig@2.1.0: {} lilconfig@2.1.0: {}
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
locate-character@3.0.0: {} locate-character@3.0.0: {}
locate-path@6.0.0: locate-path@6.0.0:
@ -2457,6 +2508,17 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.4 '@jridgewell/sourcemap-codec': 1.5.4
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.0
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
mdurl@2.0.0: {}
merge2@1.4.1: {} merge2@1.4.1: {}
micromatch@4.0.8: micromatch@4.0.8:
@ -2569,6 +2631,8 @@ snapshots:
prettier@3.6.2: {} prettier@3.6.2: {}
punycode.js@2.3.1: {}
punycode@2.3.1: {} punycode@2.3.1: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
@ -2730,6 +2794,8 @@ snapshots:
typescript@5.8.3: {} typescript@5.8.3: {}
uc.micro@2.1.0: {}
uri-js@4.4.1: uri-js@4.4.1:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1

View File

@ -1,4 +1,5 @@
@import 'tailwindcss'; @import 'tailwindcss';
@plugin '@tailwindcss/typography';
@font-face { @font-face {
font-family: 'HackNerdMono'; font-family: 'HackNerdMono';
@ -25,14 +26,7 @@
} }
body { body {
@apply bg-white font-sans text-base font-normal text-gray-800; @apply bg-white font-sans text-base font-normal text-gray-700;
}
pre,
code,
kbd,
samp {
@apply font-mono;
} }
.container { .container {

View File

@ -5,13 +5,13 @@
const { postInfo }: { postInfo: PostInfoViewModel } = $props(); const { postInfo }: { postInfo: PostInfoViewModel } = $props();
</script> </script>
<div class="flex flex-col gap-y-4 py-9"> <div class="flex flex-col gap-y-4 pt-9 md:pt-20">
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
{#each postInfo.labels as label (label.id)} {#each postInfo.labels as label (label.id)}
<Label {label} /> <Label {label} />
{/each} {/each}
</div> </div>
<h1 class="text-3xl font-bold">{postInfo.title}</h1> <h1 class="text-3xl font-bold text-gray-800 sm:text-4xl md:text-5xl">{postInfo.title}</h1>
<span>{postInfo.description}</span> <p class="max-w-3xl">{postInfo.description}</p>
<span class="text-gray-500">{postInfo.formattedPublishedTime}</span> <span class="text-gray-500">{postInfo.formattedPublishedTime}</span>
</div> </div>

View File

@ -2,17 +2,25 @@
import { PostBloc, PostEventType } from '$lib/post/adapter/presenter/postBloc'; import { PostBloc, PostEventType } from '$lib/post/adapter/presenter/postBloc';
import PostContentHeader from '$lib/post/framework/ui/PostContentHeader.svelte'; import PostContentHeader from '$lib/post/framework/ui/PostContentHeader.svelte';
import { getContext, onMount } from 'svelte'; import { getContext, onMount } from 'svelte';
import markdownit from 'markdown-it';
const { id }: { id: number } = $props(); const { id }: { id: number } = $props();
const postBloc = getContext<PostBloc>(PostBloc.name); const postBloc = getContext<PostBloc>(PostBloc.name);
const state = $derived($postBloc); const state = $derived($postBloc);
const md = markdownit();
const parsedContent = $derived(state.data?.content ? md.render(state.data.content) : '');
onMount(() => postBloc.dispatch({ event: PostEventType.PostLoadedEvent, id: id })); onMount(() => postBloc.dispatch({ event: PostEventType.PostLoadedEvent, id: id }));
</script> </script>
<div class="container pb-10"> <article class="prose container pb-10">
{#if state.data} {#if state.data}
<PostContentHeader postInfo={state.data.info} /> <PostContentHeader postInfo={state.data.info} />
<div class="max-w-3xl">
<hr />
{@html parsedContent}
</div>
{/if} {/if}
</div> </article>

View File

@ -17,7 +17,7 @@
} }
</script> </script>
<a class="flex cursor-pointer flex-col gap-y-4" href="/post/{postInfo.id}"> <a class="flex cursor-pointer flex-col gap-y-6" href="/post/{postInfo.id}">
<div class="relative aspect-video overflow-hidden rounded-2xl bg-gray-200"> <div class="relative aspect-video overflow-hidden rounded-2xl bg-gray-200">
<img <img
class="rounded-2xl object-cover transition-opacity duration-300 class="rounded-2xl object-cover transition-opacity duration-300
@ -32,7 +32,7 @@
<div class="absolute inset-0 flex items-center justify-center bg-gray-200"></div> <div class="absolute inset-0 flex items-center justify-center bg-gray-200"></div>
{/if} {/if}
</div> </div>
<div class="flex flex-col gap-y-1.5"> <div class="flex flex-col gap-y-2.5">
<PostPreviewLabels labels={postInfo.labels} /> <PostPreviewLabels labels={postInfo.labels} />
<span class="line-clamp-1 font-bold text-lg">{postInfo.title}</span> <span class="line-clamp-1 font-bold text-lg">{postInfo.title}</span>
<span class="line-clamp-3 text-justify text-sm">{postInfo.description}</span> <span class="line-clamp-3 text-justify text-sm">{postInfo.description}</span>