commit e7941231a5d2d22de81fe6f003b44fe46c31f2ad Author: Jesse Wierzbinski Date: Sat Oct 19 22:46:11 2024 +0200 feat: :sparkles: Initialize new repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a7f73a --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/README.md b/README.md new file mode 100644 index 0000000..25b5821 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Nuxt Minimal Starter + +Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. + +## Setup + +Make sure to install dependencies: + +```bash +# npm +npm install + +# pnpm +pnpm install + +# yarn +yarn install + +# bun +bun install +``` + +## Development Server + +Start the development server on `http://localhost:3000`: + +```bash +# npm +npm run dev + +# pnpm +pnpm dev + +# yarn +yarn dev + +# bun +bun run dev +``` + +## Production + +Build the application for production: + +```bash +# npm +npm run build + +# pnpm +pnpm build + +# yarn +yarn build + +# bun +bun run build +``` + +Locally preview production build: + +```bash +# npm +npm run preview + +# pnpm +pnpm preview + +# yarn +yarn preview + +# bun +bun run preview +``` + +Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/app.vue b/app.vue new file mode 100644 index 0000000..a6d24fb --- /dev/null +++ b/app.vue @@ -0,0 +1,5 @@ + diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..4e1d87a --- /dev/null +++ b/biome.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "all": true, + "correctness": { + "noNodejsModules": "off", + "useHookAtTopLevel": "off", + "noUnusedVariables": "off", + "noUnusedImports": "off" + }, + "performance": { + "useTopLevelRegex": "off" + }, + "complexity": { + "noExcessiveCognitiveComplexity": "off" + }, + "suspicious": { + "noMisplacedAssertion": "off", + "noConsole": "off" + }, + "style": { + "noDefaultExport": "off", + "noParameterProperties": "off", + "noNamespaceImport": "off", + "useFilenamingConvention": "off", + "useDefaultSwitchClause": "off", + "useNamingConvention": { + "level": "warn", + "options": { + "requireAscii": false, + "strictCase": false, + "conventions": [ + { + "selector": { + "kind": "typeProperty" + }, + "formats": [ + "camelCase", + "CONSTANT_CASE", + "PascalCase", + "snake_case" + ] + }, + { + "selector": { + "kind": "objectLiteralProperty", + "scope": "any" + }, + "formats": [ + "camelCase", + "CONSTANT_CASE", + "PascalCase", + "snake_case" + ] + } + ] + } + } + }, + "nursery": { + "noDuplicateElseIf": "warn" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 4 + }, + "javascript": { + "globals": ["Bun", "HTMLRewriter", "BufferEncoding"] + }, + "files": { + "ignore": ["node_modules", "dist", ".nuxt"] + } +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..04e7a67 Binary files /dev/null and b/bun.lockb differ diff --git a/components/article/content.vue b/components/article/content.vue new file mode 100644 index 0000000..911c7c6 --- /dev/null +++ b/components/article/content.vue @@ -0,0 +1,62 @@ + + + + + \ No newline at end of file diff --git a/components/article/image.vue b/components/article/image.vue new file mode 100644 index 0000000..f5f9edc --- /dev/null +++ b/components/article/image.vue @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/components/article/title.vue b/components/article/title.vue new file mode 100644 index 0000000..7d40edb --- /dev/null +++ b/components/article/title.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/composables/server/Post.ts b/composables/server/Post.ts new file mode 100644 index 0000000..f9b3dba --- /dev/null +++ b/composables/server/Post.ts @@ -0,0 +1,83 @@ +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 getPost = async (path: string): Promise => { + 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(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 getPostList = async (): Promise => { + const directoryPath = join(process.cwd(), "content"); + + const dir = await opendir(directoryPath, { + recursive: true, + }); + + const files: string[] = []; + + 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.parentPath, dirent.name.replace(".md", "")).replace( + join(process.cwd(), "content"), + "", + ), + ); + } + } + + const results: Post[] = []; + + for (const file of files) { + const post = await getPost(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(), + ); +}; diff --git a/content/test/test-file.md b/content/test/test-file.md new file mode 100644 index 0000000..7feea7b --- /dev/null +++ b/content/test/test-file.md @@ -0,0 +1,30 @@ +--- +title: A test post for the Versia Blog +created_at: 1700020246000 +description: This is a test post for the Versia Blog. I write this post to test the blog system. +image: https://images.pexels.com/photos/2646237/pexels-photo-2646237.jpeg +author: CPlusPatch +author_image: https://mk-cdn.cpluspatch.com/uploads/5cd850d3-6b6b-4543-97ca-9854b9dbf9f3.webp +author_handle: @jessewh +--- + +Lorem ipsum odor amet, consectetuer adipiscing elit. Habitasse augue eu phasellus volutpat aliquam venenatis dui. Fusce vitae vivamus per lectus, semper tristique. Lacus semper nam natoque cras gravida facilisis accumsan vivamus. Cras euismod non taciti ligula aptent cras. Mauris adipiscing curae mauris aliquet mi venenatis tempor. Quis congue sollicitudin ullamcorper purus non netus nascetur. Ipsum congue scelerisque tristique lobortis amet. + +# I love headers + +![Image](https://images.pexels.com/photos/2646237/pexels-photo-2646237.jpeg) + +Faucibus habitant non tortor maximus bibendum suscipit accumsan scelerisque felis. Mattis aliquet sociosqu montes pretium fusce cras nibh. Diam morbi habitant vulputate morbi; risus suscipit. Vulputate facilisis quam primis penatibus vel elementum dolor tempor. Posuere dictumst est tempus purus rutrum risus faucibus faucibus. Sem per donec nisl sociosqu inceptos eu velit. Non aenean sagittis lacus vivamus donec ac. Ipsum morbi luctus parturient, dignissim justo massa ipsum. Metus augue nascetur ornare mattis sagittis nec blandit vitae. Semper vivamus accumsan suspendisse risus senectus molestie. `moe lester`. + +```python +def hello(): + print("Hello, World!") +``` + +## Me too + +Eleifend platea conubia turpis enim iaculis nisi. Habitant congue proin elementum sed ultrices turpis aptent. Sem arcu magnis sollicitudin convallis ullamcorper vitae. Aporta dignissim praesent vitae efficitur habitant. Gravida tincidunt quam facilisis, pulvinar ante et conubia. Sapien quisque ex tortor bibendum ut feugiat felis faucibus ornare. Magna auctor platea vel non massa laoreet venenatis dis auctor. Iaculis mus tempor hendrerit ullamcorper fringilla odio donec ex. Amet rutrum magna efficitur a ad per accumsan. + +Vulputate est bibendum lobortis cubilia quisque habitasse. Ante suspendisse libero consequat quis suspendisse aenean. Eget pharetra turpis arcu varius sapien? Cras vel tempus fermentum volutpat ad fusce mauris. Dignissim ad ligula lacinia cursus sodales. Condimentum erat mattis arcu mus velit vitae fames. Mollis faucibus consectetur varius in finibus duis sollicitudin aliquam torquent. Venenatis imperdiet mollis velit maximus duis enim habitant. + +Faucibus tempus massa senectus malesuada vestibulum tristique. Platea nibh erat euismod libero, felis luctus egestas. Dis iaculis nascetur platea dis urna varius tempor condimentum lacinia. Ullamcorper tempus ad et proin tortor. Odio odio lobortis ac posuere maecenas nibh tempus dis. Ipsum pretium senectus pretium eu vulputate. Taciti viverra rhoncus ipsum egestas natoque praesent. Congue interdum scelerisque consequat, ornare penatibus tincidunt litora. \ No newline at end of file diff --git a/layouts/app.vue b/layouts/app.vue new file mode 100644 index 0000000..fdc742a --- /dev/null +++ b/layouts/app.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts new file mode 100644 index 0000000..80fa140 --- /dev/null +++ b/nuxt.config.ts @@ -0,0 +1,76 @@ +import { defineNuxtConfig } from "nuxt/config"; + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + modules: [ + "@nuxt/image", + "nuxt-security", + "@nuxt/fonts", + "@nuxtjs/tailwindcss", + "@vueuse/nuxt", + "@nuxt/icon", + "@nuxtjs/seo", + ], + compatibilityDate: "2024-04-03", + devtools: { enabled: true }, + // Disable automatic component imports (for readability) + components: false, + robots: { + blockNonSeoBots: true, + disallow: [ + "AI2Bot", + "Ai2Bot-Dolma", + "Amazonbot", + "anthropic-ai", + "Applebot", + "Applebot-Extended", + "Bytespider", + "CCBot", + "ChatGPT-User", + "Claude-Web", + "ClaudeBot", + "cohere-ai", + "Diffbot", + "FacebookBot", + "facebookexternalhit", + "FriendlyCrawler", + "Google-Extended", + "GoogleOther", + "GoogleOther-Image", + "GoogleOther-Video", + "GPTBot", + "iaskspider/2.0", + "ICC-Crawler", + "ImagesiftBot", + "img2dataset", + "ISSCyberRiskCrawler", + "Kangaroo Bot", + "Meta-ExternalAgent", + "Meta-ExternalFetcher", + "OAI-SearchBot", + "omgili", + "omgilibot", + "PerplexityBot", + "PetalBot", + "Scrapy", + "Sidetrade indexer bot", + "Timpibot", + "VelenPublicWebCrawler", + "Webzio-Extended", + "YouBot", + ], + }, + future: { + compatibilityVersion: 4, + }, + image: { + domains: ["images.pexels.com"], + }, + nitro: { + preset: "bun", + minify: true, + prerender: { + failOnError: true, + }, + }, +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..3f514f6 --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "@versia/blog", + "private": true, + "type": "module", + "description": "The source code for Versia's blog.", + "author": { + "email": "contact@cpluspatch.com", + "name": "Jesse Wierzbinski", + "url": "https://cpluspatch.com" + }, + "bugs": { + "url": "https://github.com/versia-pub/blog/issues" + }, + "license": "CC0-1.0", + "maintainers": [ + { + "email": "contact@cpluspatch.com", + "name": "Jesse Wierzbinski", + "url": "https://cpluspatch.com" + } + ], + "repository": { + "type": "git", + "url": "git+https://github.com/versia-pub/blog.git" + }, + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "postinstall": "nuxt prepare", + "lint": "bunx @biomejs/biome check .", + "typecheck": "bunx tsc -p ." + }, + "dependencies": { + "@hackmd/markdown-it-task-lists": "^2.1.4", + "@nuxt/fonts": "^0.10.0", + "@nuxt/icon": "^1.5.6", + "@nuxt/image": "^1.8.1", + "@nuxtjs/seo": "^2.0.0-rc.23", + "@nuxtjs/tailwindcss": "^6.12.2", + "@shikijs/markdown-it": "^1.22.0", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@vueuse/nuxt": "^11.1.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^9.2.0", + "markdown-it-container": "^4.0.0", + "markdown-it-toc-done-right": "^4.2.0", + "nuxt": "^3.13.2", + "nuxt-security": "^2.0.0", + "shiki": "^1.22.0", + "vue": "latest", + "vue-router": "latest" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/bun": "^1.1.11", + "@types/markdown-it-container": "^2.0.10", + "tailwindcss": "^3.4.14" + }, + "trustedDependencies": ["@biomejs/biome", "esbuild", "sharp", "vue-demi"] +} diff --git a/pages/articles/[...path].vue b/pages/articles/[...path].vue new file mode 100644 index 0000000..0a75704 --- /dev/null +++ b/pages/articles/[...path].vue @@ -0,0 +1,75 @@ +