From eb0386884eb7ba4df2349b6c5d050de44507651d Mon Sep 17 00:00:00 2001 From: matthieu42morin Date: Thu, 2 Nov 2023 01:15:04 +0100 Subject: [PATCH] Add new blog post layout and content, update project and blog content, and add filter category component --- src/content/blog.ts | 33 +++++++ src/content/projects.ts | 39 ++++++++ src/{lib/contents => content}/skills.ts | 0 .../blog/blog-content-layout.svelte | 18 ++++ .../components/blog/filter-category.svelte | 50 ++++++++++ .../blog/post-content-layout.svelte | 58 +++++++++++ src/lib/components/blog/post-preview.svelte | 82 ++++++++++++++++ .../projects/projects-content-layout.svelte | 4 + src/lib/contents/types.d.ts | 16 ---- src/routes/blog/+page.server.ts | 7 ++ src/routes/blog/+page.svelte | 96 +++++++++++++++++++ src/routes/blog/[slug]/+layout.server.ts | 27 ++++++ src/routes/blog/[slug]/+layout.svelte | 1 + src/routes/blog/[slug]/+page.ts | 14 +++ src/routes/sitemap.xml/+server.ts | 66 +++++++++++++ 15 files changed, 495 insertions(+), 16 deletions(-) create mode 100644 src/content/blog.ts create mode 100644 src/content/projects.ts rename src/{lib/contents => content}/skills.ts (100%) create mode 100644 src/lib/components/blog/blog-content-layout.svelte create mode 100644 src/lib/components/blog/filter-category.svelte create mode 100644 src/lib/components/blog/post-content-layout.svelte create mode 100644 src/lib/components/blog/post-preview.svelte create mode 100644 src/lib/components/projects/projects-content-layout.svelte delete mode 100644 src/lib/contents/types.d.ts create mode 100644 src/routes/blog/+page.server.ts create mode 100644 src/routes/blog/+page.svelte create mode 100644 src/routes/blog/[slug]/+layout.server.ts create mode 100644 src/routes/blog/[slug]/+layout.svelte create mode 100644 src/routes/blog/[slug]/+page.ts create mode 100644 src/routes/sitemap.xml/+server.ts diff --git a/src/content/blog.ts b/src/content/blog.ts new file mode 100644 index 0000000..15e01d6 --- /dev/null +++ b/src/content/blog.ts @@ -0,0 +1,33 @@ +import type { BlogPost } from '$lib/types/blog'; +import type { MarkdownMetadata } from '$lib/contents/types'; +import type { MdsvexImport } from './types'; +import { parseReadContent } from '../../content/utils'; +import { error } from '@sveltejs/kit'; + + +export function listBlogPosts() { + const posts = import.meta.glob('./blog/*.md', { + eager: true, + import: 'metadata' + }); + + return parseReadContent(posts); +} + +export async function getBlogPostMetadata(slug: string) { + const { post } = await getBlogPost(slug); + return post; +} + +export async function getBlogPost(slug: string) { + try { + const data: MdsvexImport = await import(`./blog/${slug}.md`); + + return { + post: { ...data.metadata, slug }, + Component: data.default + }; + } catch { + throw error(404, `Unable to find blog post "${slug}"`); + } +} diff --git a/src/content/projects.ts b/src/content/projects.ts new file mode 100644 index 0000000..c635221 --- /dev/null +++ b/src/content/projects.ts @@ -0,0 +1,39 @@ +import type { MarkdownMetadata, MdsvexImport } from './types'; +import { parseReadContent } from './utils'; +import { error } from '@sveltejs/kit'; + +export interface Project extends MarkdownMetadata { + title: string; + excerpt: string; + slug: string; + image: string; + date: string; + pageTitle: string; + pageDescription: string; + keywords: string; +} + +/** + * Gets all the projects metadata + */ +export function listProjects() { + const projects = import.meta.glob('./projects/*.md', { + eager: true, + import: 'metadata' + }); + + return parseReadContent(projects); +} + +export async function getProject(slug: string) { + try { + const data: MdsvexImport = await import(`./projects/${slug}.md`); + + return { + post: { ...data.metadata, slug }, + Component: data.default + }; + } catch { + throw error(404, `Unable to find project "${slug}"`); + } +} diff --git a/src/lib/contents/skills.ts b/src/content/skills.ts similarity index 100% rename from src/lib/contents/skills.ts rename to src/content/skills.ts diff --git a/src/lib/components/blog/blog-content-layout.svelte b/src/lib/components/blog/blog-content-layout.svelte new file mode 100644 index 0000000..91dbd5d --- /dev/null +++ b/src/lib/components/blog/blog-content-layout.svelte @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/lib/components/blog/filter-category.svelte b/src/lib/components/blog/filter-category.svelte new file mode 100644 index 0000000..5132663 --- /dev/null +++ b/src/lib/components/blog/filter-category.svelte @@ -0,0 +1,50 @@ + + +
+

+ Sort by category +

+
    + {#each options as option} +
  • + +
  • + {/each} +
+
diff --git a/src/lib/components/blog/post-content-layout.svelte b/src/lib/components/blog/post-content-layout.svelte new file mode 100644 index 0000000..2b09092 --- /dev/null +++ b/src/lib/components/blog/post-content-layout.svelte @@ -0,0 +1,58 @@ + + +
+
+
+ {`${title}`} +
+

+ {formatDate(date)} +

+ {#if tags && tags.length > 0} +
+ {#each tags as tag} + + {tag} + + {/each} +
+ {/if} + +
+
+
+
+ + diff --git a/src/lib/components/blog/post-preview.svelte b/src/lib/components/blog/post-preview.svelte new file mode 100644 index 0000000..44bf5cd --- /dev/null +++ b/src/lib/components/blog/post-preview.svelte @@ -0,0 +1,82 @@ + + + + + diff --git a/src/lib/components/projects/projects-content-layout.svelte b/src/lib/components/projects/projects-content-layout.svelte new file mode 100644 index 0000000..67ae89f --- /dev/null +++ b/src/lib/components/projects/projects-content-layout.svelte @@ -0,0 +1,4 @@ + + diff --git a/src/lib/contents/types.d.ts b/src/lib/contents/types.d.ts deleted file mode 100644 index 9655b9f..0000000 --- a/src/lib/contents/types.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface MarkdownHeading { - title: string; - slug: string; - level: number; - children: MarkdownHeading[]; -} - -export interface MarkdownMetadata { - headings: MarkdownHeading[]; -} - -export interface MdsvexImport { - // Technically not correct but needed to make language-tools happy - default: ConstructorOfATypedSvelteComponent; - metadata: T; -} diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts new file mode 100644 index 0000000..23ac302 --- /dev/null +++ b/src/routes/blog/+page.server.ts @@ -0,0 +1,7 @@ +import { listBlogPosts } from '$content/blog'; + +export const load = async () => { + return { + posts: listBlogPosts(), + }; +}; diff --git a/src/routes/blog/+page.svelte b/src/routes/blog/+page.svelte new file mode 100644 index 0000000..b59e4a5 --- /dev/null +++ b/src/routes/blog/+page.svelte @@ -0,0 +1,96 @@ + + + + +
+
+
+ {#each posts.slice(0, displayAmount) as post} +
+ +
+ {/each} +
+
+ + {#if posts.slice(displayAmount).length > 0} +
+

Previous posts

+ +
+ {/if} +
diff --git a/src/routes/blog/[slug]/+layout.server.ts b/src/routes/blog/[slug]/+layout.server.ts new file mode 100644 index 0000000..e78820f --- /dev/null +++ b/src/routes/blog/[slug]/+layout.server.ts @@ -0,0 +1,27 @@ +import { listBlogPosts } from '$content/blog'; +import { error } from '@sveltejs/kit'; + +function shuffle(array: T[]) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +} + +export async function load({ params }) { + const posts = listBlogPosts(); + const currentPost = posts.find((post) => post.slug == params.slug); + + if (!currentPost) { + throw error(404, `Unable to find blog post "${params.slug}"`); + } + + shuffle(posts); + + return { + featuredPosts: posts + .filter((post) => post.slug != params.slug) + .filter((p) => p.tags?.some((t) => currentPost.tags?.includes(t))) + .slice(0, 3), + }; +} diff --git a/src/routes/blog/[slug]/+layout.svelte b/src/routes/blog/[slug]/+layout.svelte new file mode 100644 index 0000000..4fa864c --- /dev/null +++ b/src/routes/blog/[slug]/+layout.svelte @@ -0,0 +1 @@ + diff --git a/src/routes/blog/[slug]/+page.ts b/src/routes/blog/[slug]/+page.ts new file mode 100644 index 0000000..3a88203 --- /dev/null +++ b/src/routes/blog/[slug]/+page.ts @@ -0,0 +1,14 @@ +import { getBlogPost, listBlogPosts } from '$content/blog.js'; +import type { PageLoad } from './$types'; + +export const entries = async () => { + const posts = await listBlogPosts(); + return posts + .filter((post) => post.slug !== undefined) + .map((post) => ({ slug: post.slug as string })); +}; + +export const load: PageLoad = async ({ params, parent }) => { + await parent(); + return await getBlogPost(params.slug); +}; diff --git a/src/routes/sitemap.xml/+server.ts b/src/routes/sitemap.xml/+server.ts new file mode 100644 index 0000000..da7898d --- /dev/null +++ b/src/routes/sitemap.xml/+server.ts @@ -0,0 +1,66 @@ +import { removeTrailingSlash } from '$lib/utils/helpers'; +import type { RequestHandler } from './$types'; +import { listBlogPosts } from '$content/blog'; + +// prettier-ignore +const sitemap = (pages: string[]) => ` + + ${pages.map((page) => `${removeTrailingSlash(page)}`).join('')} + +`; + +export const GET: RequestHandler = async () => { + const staticPages = Object.keys( + // For other static pages. Except content pages - changelogs, guides, blog posts, guides etc. + import.meta.glob('/src/routes/**/!(_)*.{svelte,md}'), + ) + .filter((page) => { + const filters = [ + '/src/routes/index.svelte', + '_', + '404', + 'slug]', + 'title]', + 'src/routes/docs/introduction/getting-started', + 'extension-activation', + 'unsubscribe', + 'subscribe', + 'stay-connected', + 'extension-uninstall', + '+error', + '+layout', + ]; + return !filters.find((filter) => page.includes(filter)); + }) + .map((page) => { + return page + .replace('/src/routes', 'https://www.gitpod.io') + .replace('/index.md', '/') + .replace('.md', '/') + .replace('/index.svelte', '/') + .replace('.svelte', '/') + .replace('/+page', ''); + }); + + const blogPosts = listBlogPosts().map( + (post) => `https://www.gitpod.io/blog/${post.slug}`, + ); + const renderedSitemap = sitemap([ + ...staticPages, + ...blogPosts, + ]); + + return new Response(renderedSitemap, { + headers: { + 'Cache-Control': 'max-age=0, s-maxage=3600', + 'Content-Type': 'application/xml', + }, + }); +};