Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikUden committed Jul 20, 2024
2 parents 8aa50ad + 2583f16 commit fc361d9
Show file tree
Hide file tree
Showing 14 changed files with 409 additions and 79 deletions.
Binary file modified bun.lockb
Binary file not shown.
70 changes: 35 additions & 35 deletions components/blog/Presentation.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<script setup lang="ts">
const { locale } = useI18n();
import type { Post } from "~/types/posts";
const posts = await queryContent("/blog/").locale(locale.value).find();
const { data } = await useFetch<Post[]>("/api/articles");
const posts = ref(data.value ?? []);
const formatDate = (date?: string) => {
return new Intl.DateTimeFormat(undefined, {
year: "numeric",
month: "long",
day: "numeric",
}).format(Date.parse(date ?? new Date().toISOString()));
};
</script>

<template>
Expand All @@ -15,62 +25,52 @@ const posts = await queryContent("/blog/").locale(locale.value).find();
<div
class="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-3">
<ClientOnly>
<article v-for="post in posts" :key="post._id">
<article
v-for="post in posts"
:key="post.title"
class="hover:drop-shadow-xl duration-200 hover:ring-2 p-4 hover:bg-dark-400 ring-orange-500 rounded">
<NuxtLink
class="flex flex-col items-start justify-between"
:to="`/${locale}${post._path}`">
<div class="relative w-full">
<img
:src="post.image ?? ''"
class="flex flex-col items-start h-full justify-between"
:href="`/blog${post.path}`">
<div class="relative w-full" v-if="post.image">
<nuxt-img
format="webp"
width="500"
:src="post.image"
alt=""
class="aspect-[16/9] w-full rounded-2xl bg-dark-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]" />
class="aspect-[16/9] w-full rounded bg-dark-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]" />
<div
class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-gray-100/10" />
class="absolute inset-0 rounded-2xl ring-1 ring- inset ring-gray-100/10" />
</div>
<div class="max-w-xl">
<div class="max-w-xl h-full flex flex-col">
<div class="mt-8 flex items-center gap-x-4 text-xs">
<time
:datetime="post.created_at ?? 0"
:datetime="post.created_at"
class="text-gray-500"
>{{
Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}).format(
new Date(post.created_at ?? 0)
)
}}</time
>{{ formatDate(post.created_at) }}</time
>
</div>
<div class="group relative">
<h3
class="mt-3 text-lg font-semibold leading-6 text-gray-50 group-hover:text-gray-300">
<a :href="post.href">
<span class="absolute inset-0" />
{{ post.title ?? "" }}
</a>
{{ post.title }}
</h3>
<p
class="mt-5 line-clamp-3 text-sm leading-6 text-gray-300">
{{ post.description ?? "" }}
{{ post.description }}
</p>
</div>
<div
class="relative mt-8 flex items-center gap-x-4">
<div class="mt-auto pt-8 flex items-center gap-x-4">
<img
:src="post.author_image ?? ''"
:src="post.author.image"
alt=""
class="h-10 w-10 rounded-full bg-dark-100" />
class="h-10 w-10 rounded bg-dark-100" />
<div class="text-sm leading-6">
<p class="font-semibold text-gray-50">
<a :href="post.author ?? '#'">
<span class="absolute inset-0" />
{{ post.author ?? "" }}
</a>
{{ post.author.name }}
</p>
<p class="text-gray-300">
{{ post.author_handle ?? "" }}
{{ post.author.handle }}
</p>
</div>
</div>
Expand Down
91 changes: 91 additions & 0 deletions composables/server/Post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { access, opendir, readFile } from "node:fs/promises";
import { join } from "node:path";
import type { FrontMatter, Post } from "~/types/posts";
import {
getMarkdownRenderer,
parseFrontMatter,
stripFrontMatter,
} from "~/utils/markdown";

export const usePost = async (path: string): Promise<Post | null> => {
if (import.meta.client) {
return null;
}

const filePath = join(process.cwd(), "content", `${path}.md`);

if (
!(await access(filePath)
.then(() => true)
.catch(() => false))
) {
return null;
}

const contents = await readFile(filePath, "utf8");

const header = parseFrontMatter<FrontMatter>(contents);

if (!header) {
return null;
}

const renderedBody = await getMarkdownRenderer().then((renderer) =>
renderer.render(stripFrontMatter(contents)),
);

return {
author: {
handle: header.author_handle,
image: header.author_image,
name: header.author,
},
private: header.private === "true",
content: renderedBody,
created_at: new Date(Number(header.created_at || 0)).toISOString(),
description: header.description,
image: header.image,
title: header.title,
path,
};
};

export const usePostList = async (): Promise<Post[] | null> => {
if (import.meta.client) {
return null;
}

const directoryPath = join(process.cwd(), "content");

const dir = await opendir(directoryPath, {
recursive: true,
});

const files = [];

for await (const dirent of dir) {
if (dirent.isFile() && dirent.name.endsWith(".md")) {
files.push(
// Remove process.cwd() and .md from the path
join(dirent.path, dirent.name.replace(".md", "")).replace(
join(process.cwd(), "content"),
"",
),
);
}
}

const results: Post[] = [];

for (const file of files) {
const post = await usePost(file.replace(".md", ""));
if (post) {
results.push(post);
}
}

return results.sort(
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
);
};
3 changes: 2 additions & 1 deletion content/de/blog/2024/07/die-rettung-vom-troet-cafe.md
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,8 @@ Wir überlegten trotzdem ob wir langfristig einen
cronjob Einrichten der preview_cards und dessen URLs rauswirft, denn eine Tabelle mit so vielen Einträgen, vor allem bei so irrelevanten Daten, ist nicht akzeptabel.

Um 17:12 generierten wir zum ersten Mal eine Liste an allen Links die in dieser Tabelle gespeichert wurden, von Alt zu Neu. Wir zeigten lediglich die ersten 5 Einträge an, denn die gesamten 19.000.000 auszugeben wäre nicht machbar.
``` url | id | created_at
```
url | id | created_at
--------------------------------------------------------------------------------------------------------+----+----------------------------
https://youtu.be/IPSbNdBmWKE | 1 | 2018-05-01 07:10:15.725173
https://techcrunch.com/2018/04/30/jan-koum-quits-facebook/ | 2 | 2018-05-01 07:13:48.155344
Expand Down
7 changes: 1 addition & 6 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export default defineNuxtConfig({
"@vueuse/nuxt",
"@nuxtjs/seo",
"@nuxtjs/i18n",
"@nuxt/content",
"@nuxtjs/tailwindcss",
"@nuxt/image",
],

app: {
Expand All @@ -22,11 +22,6 @@ export default defineNuxtConfig({
},
},

content: {
locales: ["en", "de"],
defaultLocale: "de",
},

i18n: {
vueI18n: "./i18n.config.ts",
baseUrl: "https://join-mastodon.de",
Expand Down
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
"format": "prettier --write components/ pages/ styles layouts/ *.ts *.json"
},
"devDependencies": {
"@nuxt/content": "^2.13.1",
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"@nuxtjs/i18n": "^8.3.1",
"@nuxtjs/seo": "^2.0.0-rc.15",
"@nuxtjs/tailwindcss": "^6.12.1",
"@types/markdown-it-container": "^2.0.10",
"@types/node": "^20.14.11",
"@typescript-eslint/parser": "^7.16.1",
"@vueuse/core": "^10.11.0",
Expand All @@ -31,10 +31,17 @@
"typescript": "^5.5.3"
},
"dependencies": {
"@hackmd/markdown-it-task-lists": "^2.1.4",
"@nuxt/image": "^1.7.0",
"@nuxtjs/robots": "^4.0.1",
"@shikijs/markdown-it": "^1.10.3",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.13",
"iconify-icon": "^2.1.0"
"iconify-icon": "^2.1.0",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.0.1",
"markdown-it-container": "^4.0.0",
"markdown-it-toc-done-right": "^4.2.0",
"shiki": "^1.10.3"
}
}
105 changes: 70 additions & 35 deletions pages/blog/[...path].vue
Original file line number Diff line number Diff line change
@@ -1,64 +1,99 @@
<script setup lang="ts">
const { locale } = useI18n();
import type { Post } from "~/types/posts";
let post: {
author: string;
created_at: string;
description: string;
image: string;
title: string;
} | null;
try {
post = await queryContent(
`/blog/${(useRoute().params.path as string[]).join("/") ?? ""}`
)
.locale(locale.value)
.findOne();
} catch {
post = null;
const filePath = (useRoute().params.path as string[]).join("/");
const { data: post } = await useFetch<Post>(
`/api/article?path=${encodeURIComponent(`/${filePath}`)}`
);
if (!post.value) {
throw createError({
statusCode: 404,
message: "Post not found",
});
}
useSchemaOrg([
defineArticle({
author: post?.author,
datePublished: post?.created_at,
image: post?.title,
description: post?.description,
inLanguage: locale.value,
thumbnailUrl: post?.image,
author: post.value.author.name,
datePublished: post.value.created_at,
image: post.value.image,
description: post.value.description,
inLanguage: "en-US",
thumbnailUrl: post.value.image,
}),
]);
useServerSeoMeta({
title: post?.title,
description: post?.description,
ogImage: post?.image,
title: post.value.title,
ogTitle: post.value.title,
author: post.value.author.name,
description: post.value.description,
ogDescription: post.value.description,
ogImage: post.value.image,
twitterCard: "summary_large_image",
});
let body = post.value.content;
// Fix for optimizing images during prerendering
const img = useImage();
// Find all links of type /_ipx/ in body
const ipxLinks = body.match(/\/_ipx\/[^"]+/g) || [];
for (const ipxLink of ipxLinks) {
body = body.replace(
ipxLink,
// Replace the link with the optimized image
img(`/${ipxLink.split("/").slice(3).join("/")}` || "", {
width: 800,
format: "webp",
})
);
}
const formatDate = (date?: string) => {
return new Intl.DateTimeFormat(undefined, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
}).format(Date.parse(date ?? new Date().toISOString()));
};
</script>

<template>
<HeadersNavbar />

<div
v-if="post"
class="mx-auto max-w-7xl flex flex-col pt-20 pb-24 sm:pb-32 px-6 lg:px-8">
<div class="mx-auto max-w-2xl text-center mt-32">
<div v-if="post" class="mx-auto max-w-7xl pb-24 sm:pb-32 px-6 lg:px-8 pt-1">
<div class="mx-auto max-w-2xl text-center mt-40">
<h1
v-if="post.title"
class="text-4xl font-bold tracking-tight text-gray-50 sm:text-5xl">
{{ post.title }}
</h1>

<div class="mt-8 mx-auto">
<time
v-if="post.created_at"
:datetime="post.created_at"
class="text-gray-500"
>{{ formatDate(post.created_at) }}</time
>
</div>
</div>
<img
<nuxt-img
v-if="post.image"
:src="post.image"
width="800"
format="webp"
alt=""
class="aspect-[16/9] mt-32 w-full max-w-3xl mx-auto rounded-2xl bg-dark-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]" />
<div
class="mx-auto max-w-3xl prose prose-invert prose-gray pb-24 sm:pb-32 mt-16 [&_figcaption]:mt-4 [&_img]:mt-6 [&_img]:rounded-xl">
<ContentRenderer :value="post" />
</div>
class="aspect-[16/9] drop-shadow-2xl mt-20 w-full max-w-3xl mx-auto rounded bg-dark-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]" />
<article
class="mx-auto max-w-3xl prose prose-invert mt-10 content prose-code:before:content-none prose-code:after:content-none prose-a:text-orange-500 prose-a:underline"
v-html="body"></article>
</div>
<Errors404 v-else />
</template>
Loading

0 comments on commit fc361d9

Please sign in to comment.