From cbab7c74e591a834bde577c44ae9c5f9ea42569f Mon Sep 17 00:00:00 2001 From: Klemek Date: Fri, 24 Apr 2026 18:25:27 +0200 Subject: [PATCH] feat: working SPA --- package.json | 2 +- public/articles | 1 + public/favicon.ico | Bin 4286 -> 0 bytes src/App.vue | 10 +-------- src/interfaces.ts | 11 ++++++++++ src/lib/articles.ts | 44 +++++++++++++++++++++++++++++++++++++ src/router/index.ts | 13 ++++++++--- src/views/ArticleView.vue | 39 ++++++++++++++++++++++++++++++++ src/views/HomeView.vue | 22 +++++++++++++++++++ src/views/NotFoundView.vue | 7 ++++++ tsconfig.app.json | 2 +- vite.config.ts | 4 ++++ vite.d.ts | 25 +++++++++++++++++++++ 13 files changed, 166 insertions(+), 14 deletions(-) create mode 120000 public/articles delete mode 100644 public/favicon.ico create mode 100644 src/interfaces.ts create mode 100644 src/lib/articles.ts create mode 100644 src/views/ArticleView.vue create mode 100644 src/views/HomeView.vue create mode 100644 src/views/NotFoundView.vue create mode 100644 vite.d.ts diff --git a/package.json b/package.json index 0a0b157..f9456ee 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint": "run-s lint:*", "lint:oxlint": "oxlint . --fix", "lint:eslint": "eslint . --fix --cache", - "format": "prettier --write --experimental-cli src/" + "format": "prettier --write src/" }, "dependencies": { "vue": "^3.5.32", diff --git a/public/articles b/public/articles new file mode 120000 index 0000000..75a0fa5 --- /dev/null +++ b/public/articles @@ -0,0 +1 @@ +../articles \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S diff --git a/src/App.vue b/src/App.vue index abfd315..7c2aa3f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,11 +1,3 @@ - - - - diff --git a/src/interfaces.ts b/src/interfaces.ts new file mode 100644 index 0000000..1add3a9 --- /dev/null +++ b/src/interfaces.ts @@ -0,0 +1,11 @@ +export interface MarkdownAttributes { + title?: string + draft?: string + thumbnail?: string + path?: string +} + +export interface MarkdownData { + attributes: MarkdownAttributes + html: string +} diff --git a/src/lib/articles.ts b/src/lib/articles.ts new file mode 100644 index 0000000..9753ce5 --- /dev/null +++ b/src/lib/articles.ts @@ -0,0 +1,44 @@ +import type { ArticleList, MarkdownAttributes } from '@/interfaces' +import type { MarkdownData } from '@interfaces' + +function completeAttributes( + srcAttributes: MarkdownAttributes, + pathPrefix: string, +): MarkdownAttributes { + return { + draft: srcAttributes.draft ?? 'false', + thumbnail: srcAttributes.thumbnail + ? pathPrefix + srcAttributes.thumbnail + : pathPrefix + '/thumbnail.jpg', + title: srcAttributes.title ?? 'Untitled', + path: pathPrefix, + } +} + +export async function loadArticle(year: number, month: number, day: number): MarkdownData | null { + const path = `${year}/${month.toString().padStart(2, '0')}/${day.toString().padStart(2, '0')}` + console.log(path) + try { + const data = (await import( + `@articles/${year}/${month.toString().padStart(2, '0')}/${day.toString().padStart(2, '0')}/index.md` + )) as MarkdownData + return { + attributes: completeAttributes(data.attributes, `./articles/${path}`), + html: data.html.replaceAll('./', `./articles/${path}/`), + } + } catch { + return null + } +} + +export async function listArticles(): Promise { + const raw_articles = import.meta.glob('@articles/**/index.md') + const articles: MarkdownAttributes[] = await Promise.all( + Object.keys(raw_articles).map(async (key) => { + const data = (await raw_articles[key]()) as MarkdownData + return completeAttributes(data.attributes, key.replace('/index.md', '')) + }), + ) + articles.sort((article1, article2) => article1.path?.localeCompare(article2.path ?? '') ?? 0) + return articles +} diff --git a/src/router/index.ts b/src/router/index.ts index e1eab52..7bba770 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,8 +1,15 @@ -import { createRouter, createWebHistory } from 'vue-router' +import ArticleView from '@views/ArticleView.vue' +import HomeView from '@views/HomeView.vue' +import NotFoundView from '@views/NotFoundView.vue' +import { createRouter, createWebHashHistory } from 'vue-router' const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: [], + history: createWebHashHistory(), + routes: [ + { path: '/', component: HomeView }, + { path: '/articles/:year(\\d{4})/:month(\\d{2})/:day(\\d{2})', component: ArticleView }, + { path: '/:pathMatch(.*)', component: NotFoundView }, + ], }) export default router diff --git a/src/views/ArticleView.vue b/src/views/ArticleView.vue new file mode 100644 index 0000000..c4b5054 --- /dev/null +++ b/src/views/ArticleView.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue new file mode 100644 index 0000000..0226f8c --- /dev/null +++ b/src/views/HomeView.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/views/NotFoundView.vue b/src/views/NotFoundView.vue new file mode 100644 index 0000000..23f7f88 --- /dev/null +++ b/src/views/NotFoundView.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/tsconfig.app.json b/tsconfig.app.json index c0f2d86..313a8cd 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,6 +1,6 @@ { "extends": "@vue/tsconfig/tsconfig.dom.json", - "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "include": ["env.d.ts", "vite.d.ts", "src/**/*", "src/**/*.vue"], "exclude": ["src/**/__tests__/*"], "compilerOptions": { // Extra safety for array and object lookups, but may have false positives. diff --git a/vite.config.ts b/vite.config.ts index b87b405..e6b5bc9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,7 +11,11 @@ export default defineConfig({ resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), + '@views': fileURLToPath(new URL('./src/views', import.meta.url)), + '@lib': fileURLToPath(new URL('./src/lib', import.meta.url)), '@articles': fileURLToPath(new URL('./articles', import.meta.url)), + '@interfaces': fileURLToPath(new URL('./src/interfaces.ts', import.meta.url)), }, }, + assetsInclude: [/\.\/articles\/.*(?!\.md)'/], }) diff --git a/vite.d.ts b/vite.d.ts new file mode 100644 index 0000000..f05b246 --- /dev/null +++ b/vite.d.ts @@ -0,0 +1,25 @@ +declare module '*.md' { + // "unknown" would be more detailed depends on how you structure frontmatter + const attributes: Record + + // When "Mode.TOC" is requested + const toc: { level: string; content: string }[] + + // When "Mode.HTML" is requested + const html: string + + // When "Mode.RAW" is requested + const raw: string + + // When "Mode.React" is requested. VFC could take a generic like React.VFC<{ MyComponent: TypeOfMyComponent }> + import React from 'react' + const ReactComponent: React.VFC + + // When "Mode.Vue" is requested + import { ComponentOptions, Component } from 'vue' + const VueComponent: ComponentOptions + const VueComponentWith: (components: Record) => ComponentOptions + + // Modify below per your usage + export { attributes, toc, html, ReactComponent, VueComponent, VueComponentWith } +}