Skip to content

Commit 79d09b0

Browse files
authored
feat(blog): enhance blog page (#10)
* feat(blog): enhance blog page * add changeset
1 parent dac27a8 commit 79d09b0

21 files changed

+1786
-25
lines changed

.changeset/honest-spoons-invite.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'vespera-site': minor
3+
---
4+
5+
feat: enhance blog page

.vscode/settings.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@
2121
["cva(([^)]*))", "[\"'`]([^\"'`]*).*?[\"'`]"],
2222
["cx(([^)]*))", "(?:'|\"|`)([^']*)(?:'|\"|`)"],
2323
["cn(([^)]*))", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
24-
]
24+
],
25+
"[mdc]": {
26+
"editor.tabSize": 2,
27+
"editor.insertSpaces": true,
28+
"editor.detectIndentation": false,
29+
"editor.formatOnPaste": true
30+
}
2531
}

eslint.config.mjs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// @ts-check
22
import withNuxt from './.nuxt/eslint.config.mjs'
33
import unusedImports from 'eslint-plugin-unused-imports'
4+
import pluginVueA11y from 'eslint-plugin-vuejs-accessibility'
45

56
export default withNuxt(
67
// Your custom configs here
8+
...pluginVueA11y.configs['flat/recommended'],
79
{
810
ignores: ['**/*.ts', '**/.nuxt/**', '**/*.config.ts', '**/*.d.ts', '*/*.json']
911
},
@@ -25,7 +27,8 @@ export default withNuxt(
2527
],
2628
'@typescript-eslint/no-unused-vars': 'off',
2729
'vue/multi-word-component-names': 'off',
28-
'vue/html-self-closing': 'off'
30+
'vue/html-self-closing': 'off',
31+
'vuejs-accessibility/form-control-has-label': 'off'
2932
}
3033
}
3134
)

layouts/default/index.vue

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import LucideMenu from '~icons/lucide/menu'
1616
<ContainerInner class="flex h-full items-center justify-between gap-x-4">
1717
<div class="flex items-center gap-x-4">
1818
<!-- Button to open the drawer menu on small screens -->
19+
<!-- eslint-disable-next-line vuejs-accessibility/label-has-for -->
1920
<label for="drawer" class="btn-square btn-sm btn lg:hidden">
2021
<LucideMenu />
2122
</label>
2223
<!-- Link to the home page -->
2324
<RouterLink :to="{ name: 'index' }" class="text-2xl font-bold"> Vespera </RouterLink>
24-
<nav class="hidden lg:block">
25+
<nav class="hidden gap-x-2 lg:flex">
2526
<NavItem :to="{ name: 'playground' }" title="Playground" />
27+
<NavItem :to="{ name: 'blog' }" title="Blog" />
2628
</nav>
2729
</div>
2830
<div class="flex items-center gap-x-4">
@@ -40,6 +42,7 @@ import LucideMenu from '~icons/lucide/menu'
4042
style="scroll-behavior: smooth; scroll-padding-top: 5rem"
4143
>
4244
<!-- Overlay to close the drawer menu -->
45+
<!-- eslint-disable-next-line vuejs-accessibility/label-has-for -->
4346
<label for="drawer" class="drawer-overlay" aria-label="Close menu" />
4447
<aside class="bg-base-100 min-h-dvh w-80">
4548
<!-- Sticky header inside the drawer menu -->
@@ -49,9 +52,8 @@ import LucideMenu from '~icons/lucide/menu'
4952
<div class="h4"></div>
5053
<!-- Navigation inside the drawer menu -->
5154
<nav class="px-6 *:py-4">
52-
<div>
53-
<NavItem :to="{ name: 'index' }" title="Playground" />
54-
</div>
55+
<NavItem :to="{ name: 'playground' }" title="Playground" />
56+
<NavItem :to="{ name: 'blog' }" title="Blog" />
5557
</nav>
5658
</aside>
5759
</div>

mdc.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module "*.mdc" {
2+
const content: string;
3+
export default content;
4+
}

nuxt.config.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import tailwindcss from "@tailwindcss/vite";
22
import Icons from "unplugin-icons/vite";
3-
import { DEFAULT_LANGUAGE } from "./shared/lib/utils/hooks/page-seo/constants";
43

54
// https://nuxt.com/docs/api/configuration/nuxt-config
65
export default defineNuxtConfig({
@@ -39,7 +38,12 @@ export default defineNuxtConfig({
3938
},
4039

4140
plugins: [],
42-
modules: ["@nuxt/eslint", "unplugin-icons/nuxt", "@vueuse/nuxt"],
41+
modules: [
42+
"@nuxt/eslint",
43+
"unplugin-icons/nuxt",
44+
"@vueuse/nuxt",
45+
"@nuxtjs/mdc",
46+
],
4347
dir: {
4448
pages: "routes",
4549
},

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@
5050
"@codemirror/view": "^6.36.3",
5151
"@iconify-json/logos": "^1.2.4",
5252
"@iconify-json/lucide": "^1.2.26",
53+
"@nuxtjs/mdc": "^0.15.0",
5354
"@tailwindcss/vite": "^4.0.8",
5455
"@uiw/codemirror-theme-github": "^4.23.9",
5556
"@vueuse/core": "^12.8.2",
5657
"@vueuse/nuxt": "^12.8.2",
5758
"cva": "npm:class-variance-authority@^0.7.1",
5859
"czg": "^1.11.0",
5960
"daisyui": "5.0.0-beta.8",
61+
"date-fns": "^4.1.0",
6062
"eslint-plugin-unused-imports": "^4.1.4",
63+
"eslint-plugin-vuejs-accessibility": "^2.4.1",
6164
"husky": "^9.1.7",
6265
"lint-staged": "^15.4.3",
6366
"nanoid": "^5.1.3",

pages/blogs/configs/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { BlogContent } from "~/shared/blog-contents/types";
2+
3+
export interface BlogContentDetails
4+
extends Omit<BlogContent, "lastUpdate" | "content"> {
5+
id: string;
6+
lastUpdate: string;
7+
lastUpdateRaw: string;
8+
}

pages/blogs/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as BlogsPage } from "./ui/blogs.vue";

pages/blogs/ui/blog-details.vue

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup lang="ts">
2+
import { Link } from '~/shared/ui/navigation/link'
3+
import type { BlogContentDetails } from '../configs/types'
4+
5+
const props = defineProps<BlogContentDetails>()
6+
</script>
7+
8+
<template>
9+
<div class="pb-2">
10+
<Link
11+
:to="{ name: 'blog-id', params: { id: props.id } }"
12+
class="link hover:text-primary link-hover text-2xl font-semibold"
13+
:title="`Read the article » ${props.title}`"
14+
>
15+
{{ props.title }}
16+
</Link>
17+
</div>
18+
<p class="line-clamp-3">
19+
{{ props.description }}
20+
</p>
21+
<p class="text-base-content/50 text-xs" :title="`Published ${props.lastUpdateRaw}`">
22+
Published {{ props.lastUpdate }}
23+
</p>
24+
<p></p>
25+
</template>

pages/blogs/ui/blog-list-title.vue

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<template>
2+
<h1 class="text-4xl font-semibold">Blog</h1>
3+
<p class="text-2xl">What's happening in the Vespera world?</p>
4+
</template>

pages/blogs/ui/blog-list.vue

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script setup lang="ts">
2+
import BlogDetails from './blog-details.vue'
3+
import type { BlogContentDetails } from '../configs/types'
4+
import { format } from 'date-fns'
5+
import { blogContents } from '~/shared/blog-contents'
6+
7+
/**
8+
* Transforms the blog contents into an array of BlogContentDetails.
9+
* Maps each blog entry with formatted dates and required properties.
10+
*/
11+
const blogs = Object.entries(blogContents).map(([id, content]): BlogContentDetails => {
12+
return {
13+
// Unique identifier for the blog post
14+
id,
15+
// Format the last update date in a readable format (e.g., "15 Mar, 2024")
16+
lastUpdate: format(content.lastUpdate, 'dd MMM, yyyy'),
17+
// Format the last update date in a sortable format (e.g., "03/15/2024")
18+
lastUpdateRaw: format(content.lastUpdate, 'MM/dd/yyyy'),
19+
// Blog post title from content
20+
title: content.title,
21+
// Blog post description from content
22+
description: content.description,
23+
// Optional author name
24+
author: content.author,
25+
// Optional author URL
26+
authorUrl: content.authorUrl
27+
}
28+
})
29+
</script>
30+
31+
<template>
32+
<ul class="space-y-8">
33+
<li v-for="blog in blogs" :key="blog.id">
34+
<BlogDetails v-bind="blog" />
35+
</li>
36+
</ul>
37+
</template>

pages/blogs/ui/blogs.vue

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
import { Container, ContainerInner } from '~/shared/ui/layout/container'
3+
import BlogList from './blog-list.vue'
4+
import BlogListTitle from './blog-list-title.vue'
5+
</script>
6+
7+
<template>
8+
<Container>
9+
<ContainerInner>
10+
<div class="py-16">
11+
<BlogListTitle />
12+
</div>
13+
<BlogList />
14+
</ContainerInner>
15+
</Container>
16+
</template>

pages/playground/ui/playground.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const {
3131
@change-example="onChangeExample"
3232
@create-new-example="onCreateNewExample"
3333
/>
34+
<!-- eslint-disable-next-line vuejs-accessibility/label-has-for -->
3435
<label class="flex flex-row items-center justify-center gap-x-2 md:hidden">
3536
<span>Input</span>
3637
<input v-model="isMobileScreenOuput" type="checkbox" class="toggle toggle-sm" />
@@ -42,7 +43,7 @@ const {
4243
:doc="examples[selectedExample].value"
4344
@change="onChangeContent"
4445
/>
45-
<Output v-if="isMobileScreenOuput" />
46+
<Output v-if="isMobileScreenOuput" />s
4647
</div>
4748
<div class="border-base-content/20 hidden h-full min-h-0 border-t md:block">
4849
<SplitterGroup direction="horizontal">

0 commit comments

Comments
 (0)