diff --git a/apps/frontend/src/pages/blog.astro b/apps/frontend/src/pages/blog.astro index 1f92dd0..a5c0175 100644 --- a/apps/frontend/src/pages/blog.astro +++ b/apps/frontend/src/pages/blog.astro @@ -69,53 +69,6 @@ try { locale: p.locale, })); - // 2. Try to fetch rendered HTML for each page - const idsToFetch = blogPages.slice(0, 10).map(p => p.id); - - if (idsToFetch.length > 0) { - const SINGLE_PAGES_QUERY = ` - { - ${idsToFetch.map((id: number, index: number) => ` - page_${index}: pages { - single(id: ${id}) { - id - render - } - } - `).join('\n')} - } - `; - - const contentResponse = await fetch(`${WIKIJS_BASE_URL}/graphql`, { - method: 'POST', - headers, - body: JSON.stringify({ query: SINGLE_PAGES_QUERY }), - }); - - const contentJson = await contentResponse.json(); - - if (contentJson.errors) { - if (contentJson.errors.some((e: any) => e.message.includes('authorized') || e.message.includes('permission'))) { - console.warn('WikiJS: Permission denied for pages.single. Falling back to descriptions.'); - permissionWarning = true; - } else { - console.error('WikiJS Content Error:', contentJson.errors); - } - } - - // Merge rendered HTML if available - if (contentJson.data) { - Object.values(contentJson.data).forEach((item: any) => { - if (item?.single) { - const match = blogPages.find(p => p.id === item.single.id); - if (match) { - match.render = item.single.render; - } - } - }); - } - } - } catch (e: any) { console.error('Failed to fetch WikiJS data:', e); error = `Failed to fetch wiki data: ${e.message}`; @@ -135,6 +88,11 @@ function timeAgo(dateStr: string): string { if (diffDays < 30) return `${diffDays}d ago`; return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } + +function getPermalink(path: string): string { + const slug = path.startsWith('blog/') ? path.replace('blog/', '') : path; + return `/blog/${slug}`; +} --- @@ -170,16 +128,6 @@ function timeAgo(dateStr: string): string { )} - {permissionWarning && ( - - )} - {!error && blogPages.length === 0 && (
📭
@@ -192,6 +140,7 @@ function timeAgo(dateStr: string): string {
{blogPages.map((page) => { const isNew = (new Date().getTime() - new Date(page.createdAt).getTime()) < 7 * 24 * 60 * 60 * 1000; + const permalink = getPermalink(page.path); return (
@@ -211,7 +160,9 @@ function timeAgo(dateStr: string): string {

- {page.title} + + {page.title} +

{page.description && ( @@ -220,15 +171,14 @@ function timeAgo(dateStr: string): string {

)} - {page.render ? ( -
- ) : ( - !page.description && ( -
- No preview available for this post. -
- ) - )} +
); @@ -250,7 +200,7 @@ function timeAgo(dateStr: string): string { @apply absolute inset-0 opacity-30; } .blog-main { - @apply w-full px-4 md:px-8 -mt-12 relative z-10; + @apply max-w-5xl mx-auto px-4 md:px-8 -mt-12 relative z-10; } /* Banners */ @@ -261,10 +211,6 @@ function timeAgo(dateStr: string): string { .error-banner-title { @apply text-red-800 font-bold text-lg; } .error-banner-desc { @apply text-red-700 mt-1; } - .warning-banner { @apply banner-base bg-amber-50 border-amber-500; } - .warning-banner-title { @apply text-amber-800 font-bold text-lg; } - .warning-banner-desc { @apply text-amber-700 mt-1; } - /* Empty State */ .empty-state { @apply max-w-7xl mx-auto bg-white rounded-2xl shadow-xl p-12 text-center border border-gray-100; @@ -299,10 +245,11 @@ function timeAgo(dateStr: string): string { @apply text-2xl md:text-3xl font-black text-gray-900 mb-4 leading-tight; } .blog-post-description { - @apply text-lg text-gray-500 mb-8 font-medium leading-relaxed; + @apply text-lg text-gray-500 mb-2 font-medium leading-relaxed; } - .blog-post-fallback { - @apply text-gray-600 text-base leading-relaxed whitespace-pre-wrap; + + .read-more-btn { + @apply inline-flex items-center text-indigo-600 font-bold hover:text-indigo-800 transition-colors; } diff --git a/apps/frontend/src/pages/blog/[...slug].astro b/apps/frontend/src/pages/blog/[...slug].astro new file mode 100644 index 0000000..ec03220 --- /dev/null +++ b/apps/frontend/src/pages/blog/[...slug].astro @@ -0,0 +1,244 @@ +--- +import Layout from '../../layouts/Layout.astro'; + +export async function getStaticPaths() { + const WIKIJS_BASE_URL = 'https://wiki.teletype.hu'; + const WIKIJS_TOKEN = import.meta.env.WEBAPP_WIKIJS_TOKEN; + + const headers: Record = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }; + + if (WIKIJS_TOKEN) { + headers['Authorization'] = `Bearer ${WIKIJS_TOKEN}`; + } + + const LIST_QUERY = ` + { + pages { + list(orderBy: UPDATED, orderByDirection: DESC, tags: ["blog"]) { + id + path + title + description + updatedAt + createdAt + locale + } + } + } + `; + + try { + const listResponse = await fetch(`${WIKIJS_BASE_URL}/graphql`, { + method: 'POST', + headers, + body: JSON.stringify({ query: LIST_QUERY }), + }); + + const listJson = await listResponse.json(); + const pages = listJson?.data?.pages?.list ?? []; + + return pages.map((page: any) => { + // WikiJS paths usually look like "blog/my-post" + // We want to match it against /blog/[...slug] + // If path is "blog/post-1", slug should be "post-1" + const slug = page.path.startsWith('blog/') + ? page.path.replace('blog/', '') + : page.path; + + return { + params: { slug }, + props: { pageId: page.id }, + }; + }); + } catch (e) { + console.error('Error fetching paths for blog posts:', e); + return []; + } +} + +const { slug } = Astro.params; +const { pageId } = Astro.props; + +const WIKIJS_BASE_URL = 'https://wiki.teletype.hu'; +const WIKIJS_TOKEN = import.meta.env.WEBAPP_WIKIJS_TOKEN; + +let pageContent = null; +let error = null; + +try { + const headers: Record = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }; + + if (WIKIJS_TOKEN) { + headers['Authorization'] = `Bearer ${WIKIJS_TOKEN}`; + } + + const SINGLE_PAGE_QUERY = ` + { + pages { + single(id: ${pageId}) { + id + path + title + description + render + updatedAt + createdAt + } + } + } + `; + + const response = await fetch(`${WIKIJS_BASE_URL}/graphql`, { + method: 'POST', + headers, + body: JSON.stringify({ query: SINGLE_PAGE_QUERY }), + }); + + const json = await response.json(); + pageContent = json?.data?.pages?.single; + + if (!pageContent) { + error = "Post not found"; + } +} catch (e: any) { + error = e.message; +} + +function timeAgo(dateStr: string): string { + const now = new Date(); + const date = new Date(dateStr); + const diffMs = now.getTime() - date.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + + if (diffMins < 1) return 'just now'; + if (diffMins < 60) return `${diffMins}m ago`; + if (diffHours < 24) return `${diffHours}h ago`; + if (diffDays < 30) return `${diffDays}d ago`; + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); +} +--- + + +
+
+
+
+
+
+ +
+ + ← Back to Blog + + {pageContent && ( + <> +

+ {pageContent.title} +

+

+ {pageContent.description} +

+ + + )} +
+
+ +
+ {error && ( + + )} + + {pageContent && ( +
+
+ {pageContent.render ? ( +
+ ) : ( +
+ No content available for this post. +
+ )} +
+
+ )} +
+
+
+ + + +