diff --git a/articles.example/2026-01-01/index.md b/articles.example/2026-01-01/index.md index 191cb44..bc589a2 100644 --- a/articles.example/2026-01-01/index.md +++ b/articles.example/2026-01-01/index.md @@ -4,6 +4,7 @@ author: kleπek draft: false thumbnail: ./thumbnail.jpg date: 2026-01-01 +tags: sample --- ## Images diff --git a/articles.example/config.json b/articles.example/config.json index 2af2626..81861bd 100644 --- a/articles.example/config.json +++ b/articles.example/config.json @@ -11,6 +11,7 @@ "back_link": " Back to home", "published_on": "Published on", "updated_on": "Updated on", + "tags": "Tags:", "authored": "By", "copyright": "CC BY-NC", "footer": "", diff --git a/articles.example/style.scss b/articles.example/style.scss index c4f818d..530e55f 100644 --- a/articles.example/style.scss +++ b/articles.example/style.scss @@ -185,3 +185,12 @@ nav .nav-items { .article-info { font-style: italic; } + +.article-tag { + text-decoration: underline !important; +} + +.article-active-tag { + text-decoration: underline !important; + font-weight: bold; +} diff --git a/src/components/ArticleItem.vue b/src/components/ArticleItem.vue new file mode 100644 index 0000000..eedd8c4 --- /dev/null +++ b/src/components/ArticleItem.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/ArticleTag.vue b/src/components/ArticleTag.vue new file mode 100644 index 0000000..eb5c73e --- /dev/null +++ b/src/components/ArticleTag.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/ArticleTagList.vue b/src/components/ArticleTagList.vue new file mode 100644 index 0000000..5cf15cb --- /dev/null +++ b/src/components/ArticleTagList.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/PageFooter.vue b/src/components/PageFooter.vue index 07d80ef..b5ce405 100644 --- a/src/components/PageFooter.vue +++ b/src/components/PageFooter.vue @@ -5,8 +5,15 @@ import { stripHTML } from '@lib/strings' diff --git a/src/interfaces.ts b/src/interfaces.ts index 24145b6..fa21f69 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -11,6 +11,7 @@ export interface ArticleMetadata { author?: string thumbnail?: string draft?: boolean + tags: string[] } export interface Article { diff --git a/src/lib/articles.ts b/src/lib/articles.ts index 8a96b1c..25bc26c 100644 --- a/src/lib/articles.ts +++ b/src/lib/articles.ts @@ -42,6 +42,10 @@ function parseMetadata( ), thumbnail: (srcAttributes.thumbnail as string | undefined) ?? "", draft: draft, + tags: (srcAttributes.tags as string | undefined ?? '') + .split(',') + .map((tag) => tag.trim()) + .filter((tag) => tag !== ''), }; } diff --git a/src/lib/config.ts b/src/lib/config.ts index a136c42..913af8a 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -16,5 +16,6 @@ export const HOME_COUNT: number = articlesConfig.home_count; export const PUBLISHED_ON: string = articlesConfig.published_on; export const UPDATED_ON: string = articlesConfig.updated_on; export const AUTHORED: string = articlesConfig.authored; +export const TAGS: string = articlesConfig.tags; export const BASE_URL: string = import.meta.env.BASE_URL; export const PROD: boolean = import.meta.env.PROD; diff --git a/src/views/ArticleView.vue b/src/views/ArticleView.vue index 775c10d..fd99794 100644 --- a/src/views/ArticleView.vue +++ b/src/views/ArticleView.vue @@ -8,6 +8,7 @@ import { AUTHORED, PUBLISHED_ON, SIGNATURE, TITLE, UPDATED_ON } from '@lib/confi import { stripHTML } from '@lib/strings' import BackHomeButton from '@components/BackHomeButton.vue' import NotFoundView from '@views/NotFoundView.vue' +import ArticleTagList from '@components/ArticleTagList.vue' const article = ref
(null) const loading = ref(true) @@ -37,14 +38,26 @@ onUpdated(updateDynamicContent)

-import { ref, onBeforeMount, onUpdated, onMounted } from 'vue' -import { listArticles, updateDynamicContent } from '@lib/articles' -import type { ArticleMetadata } from '@interfaces' -import { simpleDateFormat } from '@lib/dates' -import { HOME_COUNT, PROD, PUBLISHED_ON, TITLE } from '@lib/config' -import { stripHTML } from '@lib/strings' +import { ref, onBeforeMount, onUpdated, onMounted, watch } from "vue"; +import { listArticles, updateDynamicContent } from "@lib/articles"; +import type { ArticleMetadata } from "@interfaces"; +import { HOME_COUNT, PROD, TITLE } from "@lib/config"; +import { stripHTML } from "@lib/strings"; +import ArticleItem from "@/components/ArticleItem.vue"; +import { useRoute } from 'vue-router' -const articles = ref([]) -const loading = ref(true) +const articles = ref([]); +const filteredArticles = ref([]); +const loading = ref(true); +const route = useRoute(); + +function filterArticles() { + if (route.query.tag) { + filteredArticles.value = articles.value + .filter((metadata) => metadata.tags.includes(route.query.tag as string)); + } else { + filteredArticles.value = articles.value + .slice(0, HOME_COUNT); + } +} onBeforeMount(async () => { const newArticles = (await listArticles()) .filter((metadata) => (!metadata.draft || !PROD) && metadata.path) - .slice(0, HOME_COUNT) - articles.value.splice(0, articles.value.length, ...newArticles) - window.document.title = stripHTML(TITLE) + ' — Home' - loading.value = false - await updateDynamicContent() + articles.value.splice(0, articles.value.length, ...newArticles); + filterArticles(); + window.document.title = stripHTML(TITLE) + " — Home"; + loading.value = false; + await updateDynamicContent(); +}); + +watch(() => route.query.tag, () => { + filterArticles(); }) -onMounted(updateDynamicContent) -onUpdated(updateDynamicContent) + +onMounted(updateDynamicContent); +onUpdated(updateDynamicContent);