From 767fb8cfc68e244717a97956f6bf83e7bdf1d9ec Mon Sep 17 00:00:00 2001 From: klemek Date: Sat, 2 May 2026 00:08:13 +0200 Subject: [PATCH] ci: add Makefile and stricter eslint config --- .editorconfig | 22 ++++-- .prettierrc.json | 6 -- Makefile | 61 ++++++++++++++++ bun.lock | 14 +++- eslint.config.ts | 42 ++++++----- package.json | 10 +-- post-build.ts | 154 ++++++++++++++++++++++------------------- src/lib/articles.ts | 138 ++++++++++++++++++++---------------- src/lib/config.ts | 20 +++--- src/lib/dates.ts | 12 ++-- src/views/HomeView.vue | 2 +- vite.config.ts | 10 +-- 12 files changed, 303 insertions(+), 188 deletions(-) delete mode 100644 .prettierrc.json create mode 100644 Makefile diff --git a/.editorconfig b/.editorconfig index 3b510aa..8340565 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,20 @@ -[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] -charset = utf-8 -indent_size = 2 +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true +indent_size = 4 end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab +indent_size = 2 + +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +indent_size = 2 max_line_length = 100 diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 29a2402..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/prettierrc", - "semi": false, - "singleQuote": true, - "printWidth": 100 -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..51cc1c8 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +# ENV + +ifeq (,$(shell which bun)) + NPM ?= npm +endif + +NPM ?= bun +GIT ?= git + +.PHONY: help +help: ## show this message + @echo "Usage: $(MAKE) [target1] [target2] ..." + @echo "" + @echo "Commands/Targets:" + @cat $(MAKEFILE_LIST) | grep -E '(^[a-zA-Z0-9_%-]+:.*?##.*$$)|(^##)' | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-20s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' + @echo "" + @echo "Environment:" + @cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z0-9_-]+\s*\??=.*$$' | grep -Eo '^[a-zA-Z0-9_-]+' | xargs -I {} $(MAKE) -s print-{} 2> /dev/null + +.PHONY: print-% +print-%: + @echo -e '\033[32m$*\033[0m = $($*)' + +# FILES + +node_modules: bun.lock + @$(MAKE) -s npm-install + +# ACTIONS + +.PHONY: install +install: npm-install ## install project + +.PHONY: install +install: npm-update ## update project + +.PHONY: build +build: npm-run-build ## build static site in "dist" + +.PHONY: dev +dev: npm-run-dev ## run dev server + +.PHONY: lint +lint: npm-run-lint npm-run-type-check ## lint code + +.PHONY: format +format: npm-run-lint-fix ## fix and reformat code + +# TOOLS + +.PHONY: npm-install +npm-install: ## npm install + $(NPM) install + +.PHONY: npm-update +npm-update: ## npm update + $(NPM) update + +.PHONY: npm-run-% +npm-run-%: node_modules ## npm run (script) + $(NPM) run $* diff --git a/bun.lock b/bun.lock index 314b409..15ddccf 100644 --- a/bun.lock +++ b/bun.lock @@ -16,10 +16,10 @@ "@tsconfig/node24": "^24.0.4", "@types/node": "^24.12.2", "@vitejs/plugin-vue": "^6.0.6", + "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.7.0", "@vue/tsconfig": "^0.9.1", "eslint": "^10.3.0", - "eslint-config-prettier": "^10.1.8", "eslint-plugin-oxlint": "~1.60.0", "eslint-plugin-vue": "~10.8.0", "feed": "^5.2.1", @@ -290,6 +290,8 @@ "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="], + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="], @@ -456,6 +458,8 @@ "@vue/devtools-shared": ["@vue/devtools-shared@8.1.1", "", {}, "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ=="], + "@vue/eslint-config-prettier": ["@vue/eslint-config-prettier@10.2.0", "", { "dependencies": { "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2" }, "peerDependencies": { "eslint": ">= 8.21.0", "prettier": ">= 3.0.0" } }, "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw=="], + "@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.7.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.56.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.56.0", "vue-eslint-parser": "^10.4.0" }, "peerDependencies": { "eslint": "^9.10.0 || ^10.0.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg=="], "@vue/language-core": ["@vue/language-core@3.2.7", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.1.2", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-Gn4q/tRxbpVGLEuARQ43p3YELlNAFgRUVCgW9U5Cr+5q4vfD2bWDWpl3ABbJMXUt5xlE1dF8dkigg2aUq7JYYw=="], @@ -646,6 +650,8 @@ "eslint-plugin-oxlint": ["eslint-plugin-oxlint@1.60.0", "", { "dependencies": { "jsonc-parser": "^3.3.1" }, "peerDependencies": { "oxlint": "~1.60.0" } }, "sha512-9RUD23k7ablez1qg7JWnyPYPOlbucDDqaDr+qNUi0TbIQCPqIPCLzfllgqKF9lOxlg+l17H8hISErmarvm2J1w=="], + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.5", "", { "dependencies": { "prettier-linter-helpers": "^1.0.1", "synckit": "^0.11.12" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw=="], + "eslint-plugin-vue": ["eslint-plugin-vue@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.0.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], @@ -670,6 +676,8 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], @@ -894,6 +902,8 @@ "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.1", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], @@ -990,6 +1000,8 @@ "sync-message-port": ["sync-message-port@1.2.0", "", {}, "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg=="], + "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], + "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], diff --git a/eslint.config.ts b/eslint.config.ts index 89fdc89..640a967 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,26 +1,32 @@ -import { globalIgnores } from 'eslint/config' -import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' -import pluginVue from 'eslint-plugin-vue' -import pluginOxlint from 'eslint-plugin-oxlint' -import skipFormatting from 'eslint-config-prettier/flat' +import { globalIgnores } from "eslint/config"; +import { + defineConfigWithVueTs, + vueTsConfigs, +} from "@vue/eslint-config-typescript"; +import pluginVue from "eslint-plugin-vue"; +import skipFormatting from "@vue/eslint-config-prettier/skip-formatting"; +import { configureVueProject } from "@vue/eslint-config-typescript"; +import pluginOxlint from "eslint-plugin-oxlint"; -// To allow more languages other than `ts` in `.vue` files, uncomment the following lines: -// import { configureVueProject } from '@vue/eslint-config-typescript' -// configureVueProject({ scriptLangs: ['ts', 'tsx'] }) -// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup +configureVueProject({ scriptLangs: ["ts", "tsx"] }); export default defineConfigWithVueTs( { - name: 'app/files-to-lint', - files: ['**/*.{vue,ts,mts,tsx}'], + name: "app/files-to-lint", + files: ["**/*.{ts,mts,tsx,vue}"], }, - globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), - - ...pluginVue.configs['flat/essential'], - vueTsConfigs.recommended, - - ...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'), + globalIgnores(["**/dist/**"]), + pluginVue.configs["flat/recommended"], + vueTsConfigs.strictTypeChecked, + vueTsConfigs.stylisticTypeChecked, + ...pluginOxlint.buildFromOxlintConfigFile(".oxlintrc.json"), skipFormatting, -) + + { + rules: { + "vue/no-v-html": "off", + }, + }, +); diff --git a/package.json b/package.json index 72db678..63a38bc 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,11 @@ "build-only": "vite build", "type-check": "vue-tsc --build", "lint": "run-s lint:*", - "lint:oxlint": "oxlint . --fix", - "lint:eslint": "eslint . --fix --cache", - "format": "prettier --write src/ *.ts *.json" + "lint-fix": "run-s lint-fix:*", + "lint:eslint": "eslint . --cache", + "lint:oxlint": "oxlint .", + "lint-fix:eslint": "eslint . --fix --cache", + "lint-fix:oxlint": "oxlint . --fix" }, "dependencies": { "@keithclark/shaderview": "https://github.com/keithclark/shaderview/archive/refs/tags/1.2.0.tar.gz", @@ -29,10 +31,10 @@ "@tsconfig/node24": "^24.0.4", "@types/node": "^24.12.2", "@vitejs/plugin-vue": "^6.0.6", + "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.7.0", "@vue/tsconfig": "^0.9.1", "eslint": "^10.3.0", - "eslint-config-prettier": "^10.1.8", "eslint-plugin-oxlint": "~1.60.0", "eslint-plugin-vue": "~10.8.0", "feed": "^5.2.1", diff --git a/post-build.ts b/post-build.ts index 04de414..49ce95b 100644 --- a/post-build.ts +++ b/post-build.ts @@ -1,129 +1,141 @@ -import fs from 'fs' -import process from 'process' -import { Feed } from 'feed' -import articlesConfig from './articles/config.json' +import fs from "fs"; +import process from "process"; +import { Feed } from "feed"; +import articlesConfig from "./articles/config.json"; function getFiles(dir: string): string[] { return fs.readdirSync(dir).flatMap((name) => { - const path = `${dir}/${name}` + const path = `${dir}/${name}`; if (fs.statSync(path).isDirectory()) { - return getFiles(path) + return getFiles(path); } else { - return [path] + return [path]; } - }) + }); } -const METADATA_BLOCK_REGEX = /^---\n([\s\S]*?)---\n/m -const METADATA_REGEX = /^(\w+):(.*)$/gm +const METADATA_BLOCK_REGEX = /^---\n([\s\S]*?)---\n/m; +const METADATA_REGEX = /^(\w+):(.*)$/gm; function readArticleMetadata(path: string): Record | null { - const content = fs.readFileSync(path, { encoding: 'utf8' }) - const match: RegExpExecArray | null = METADATA_BLOCK_REGEX.exec(content) - if (!match || !match[1]) { - console.warn(`No metadata for: ${path}`) - return null + const content = fs.readFileSync(path, { encoding: "utf8" }); + const match: RegExpExecArray | null = METADATA_BLOCK_REGEX.exec(content); + if (!match?.[1]) { + console.warn(`No metadata for: ${path}`); + return null; } - let subMatch: RegExpExecArray | null = null + let subMatch: RegExpExecArray | null = null; const metadata: Record = { - path: path.replaceAll('/index.md', ''), - } + path: path.replaceAll("/index.md", ""), + }; do { - subMatch = METADATA_REGEX.exec(match[1]) - if (subMatch && subMatch[1] && subMatch[2]) { - metadata[subMatch[1]] = subMatch[2].trim() + subMatch = METADATA_REGEX.exec(match[1]); + if (subMatch?.[1] && subMatch[2]) { + metadata[subMatch[1]] = subMatch[2].trim(); } - } while (subMatch) - return metadata + } while (subMatch); + return metadata; } -function formatArticlePage(metadata: Record, baseHtml: string): string { - let outHtml = baseHtml - outHtml = outHtml.replace(/<.*?property="og:url".*?>/gm, '') +function formatArticlePage( + metadata: Record, + baseHtml: string, +): string { + let outHtml = baseHtml; + outHtml = outHtml.replace(/<.*?property="og:url".*?>/gm, ""); outHtml = outHtml.replace( /<\/head>/gm, - `\n`, - ) - const blog_title = articlesConfig['title']?.replace(/(<([^>]+)>)/gi, '').trim() + `\n`, + ); + const blog_title = articlesConfig.title.replace(/(<([^>]+)>)/gi, "").trim(); if (metadata.title) { - const title = metadata.title.replace(/(<([^>]+)>)/gi, '').trim() - outHtml = outHtml.replace(/.*?<\/title>/gm, `<title>${blog_title} — ${title}`) - outHtml = outHtml.replace(/<.*?property="og:title".*?>/gm, '') + const title = metadata.title.replace(/(<([^>]+)>)/gi, "").trim(); + outHtml = outHtml.replace( + /.*?<\/title>/gm, + `<title>${blog_title} — ${title}`, + ); + outHtml = outHtml.replace(/<.*?property="og:title".*?>/gm, ""); outHtml = outHtml.replace( /<\/head>/gm, `\n`, - ) - outHtml = outHtml.replace(/<.*?property="og:description".*?>/gm, '') + ); + outHtml = outHtml.replace(/<.*?property="og:description".*?>/gm, ""); outHtml = outHtml.replace( /<\/head>/gm, `\n`, - ) + ); } if (metadata.thumbnail) { - outHtml = outHtml.replace(/<.*?property="og:image".*?>/gm, '') + outHtml = outHtml.replace(/<.*?property="og:image".*?>/gm, ""); outHtml = outHtml.replace( /<\/head>/gm, - `\n`, - ) + `\n`, + ); } - return outHtml + return outHtml; } function addFeedArticle(metadata: Record, feed: Feed) { - if (metadata.draft !== 'true') { + if (metadata.draft !== "true") { feed.addItem({ - title: metadata.title.replace(/(<([^>]+)>)/gi, '').trim(), + title: metadata.title.replace(/(<([^>]+)>)/gi, "").trim(), id: metadata.path, - link: `${articlesConfig['base_url']}${metadata.path}/`, + link: `${articlesConfig.base_url}${metadata.path}/`, date: new Date(Date.parse(metadata.date)), - image: metadata.thumbnail.replace('./', articlesConfig['base_url'] + metadata.path + '/'), - }) + image: metadata.thumbnail.replace( + "./", + articlesConfig.base_url + metadata.path + "/", + ), + }); } } -const indexContent = fs.readFileSync('dist/index.html', { encoding: 'utf8' }) +const indexContent = fs.readFileSync("dist/index.html", { encoding: "utf8" }); if (!indexContent) { - console.error('Could not read dist/index.html') - process.exit(1) + console.error("Could not read dist/index.html"); + process.exit(1); } -const metadatas = getFiles('articles') - .filter((path) => path.match(/\/index.md$/)) +const metadatas = getFiles("articles") + .filter((path) => /\/index.md$/.exec(path)) .map((path) => readArticleMetadata(path)) - .filter((metadata) => !!metadata) + .filter((metadata) => !!metadata); const feed = new Feed({ - title: articlesConfig['title']?.replace(/(<([^>]+)>)/gi, '').trim() ?? '', - id: articlesConfig['base_url'], - link: articlesConfig['base_url'], - language: articlesConfig['lang'], - favicon: articlesConfig['base_url'] + 'articles/favicon.ico', - generator: 'md-blog', + title: articlesConfig.title.replace(/(<([^>]+)>)/gi, "").trim(), + id: articlesConfig.base_url, + link: articlesConfig.base_url, + language: articlesConfig.lang, + favicon: articlesConfig.base_url + "articles/favicon.ico", + generator: "md-blog", feedLinks: { - json: articlesConfig['base_url'] + 'feed.json', - atom: articlesConfig['base_url'] + 'atom.xml', - rss: articlesConfig['base_url'] + 'rss', + json: articlesConfig.base_url + "feed.json", + atom: articlesConfig.base_url + "atom.xml", + rss: articlesConfig.base_url + "rss", }, updated: new Date( Math.max( 0, ...metadatas - .filter((metadata) => metadata.draft !== 'true') + .filter((metadata) => metadata.draft !== "true") .map((metadata) => Date.parse(metadata.date)), ), ), -}) +}); metadatas.forEach((metadata) => { - fs.writeFileSync(`dist/${metadata.path}/index.html`, formatArticlePage(metadata, indexContent)) - console.info(`Wrote dist/${metadata.path}/index.html`) - addFeedArticle(metadata, feed) -}) + fs.writeFileSync( + `dist/${metadata.path}/index.html`, + formatArticlePage(metadata, indexContent), + ); + console.info(`Wrote dist/${metadata.path}/index.html`); + addFeedArticle(metadata, feed); +}); -fs.writeFileSync('dist/feed.json', feed.json1()) -console.info(`Wrote dist/feed.json`) -fs.writeFileSync('dist/atom.xml', feed.atom1()) -console.info(`Wrote dist/atom.xml`) -fs.writeFileSync('dist/rss.xml', feed.rss2()) -console.info(`Wrote dist/rss.xml`) +fs.writeFileSync("dist/feed.json", feed.json1()); +console.info(`Wrote dist/feed.json`); +fs.writeFileSync("dist/atom.xml", feed.atom1()); +console.info(`Wrote dist/atom.xml`); +fs.writeFileSync("dist/rss.xml", feed.rss2()); +console.info(`Wrote dist/rss.xml`); diff --git a/src/lib/articles.ts b/src/lib/articles.ts index 93d0f9c..8a96b1c 100644 --- a/src/lib/articles.ts +++ b/src/lib/articles.ts @@ -1,130 +1,146 @@ -import type { MarkdownData, Article, ArticleMetadata } from '@interfaces' -import katex from 'katex' -import { nextTick } from 'vue' -import hljs from 'highlight.js' -import mermaid from 'mermaid' -import { createIcons, icons } from 'lucide' +import type { MarkdownData, Article, ArticleMetadata } from "@interfaces"; +import katex from "katex"; +import { nextTick } from "vue"; +import hljs from "highlight.js"; +import mermaid from "mermaid"; +import { createIcons, icons } from "lucide"; export async function updateDynamicContent() { - await nextTick() - hljs.highlightAll() + await nextTick(); + hljs.highlightAll(); createIcons({ icons, - nameAttr: 'icon', + nameAttr: "icon", attrs: { - width: '1.1em', - height: '1.1em', + width: "1.1em", + height: "1.1em", }, - }) - mermaid.run() + }); + await mermaid.run(); } function parseMetadata( srcAttributes: Record, pathPrefix: string, ): ArticleMetadata { - const draft = !!srcAttributes.draft + const draft = !!srcAttributes.draft; return { path: pathPrefix, title: - (draft ? '[DRAFT] ' : '') + decodeURIComponent((srcAttributes.title as string) ?? 'Untitled'), + (draft ? "[DRAFT] " : "") + + decodeURIComponent( + (srcAttributes.title as string | undefined) ?? "Untitled", + ), date: (srcAttributes.date as string | undefined) ? new Date(Date.parse(srcAttributes.date as string)) : new Date(), updated: (srcAttributes.updated as string | undefined) ? new Date(Date.parse(srcAttributes.updated as string)) : undefined, - author: decodeURIComponent((srcAttributes.author as string) ?? ''), - thumbnail: (srcAttributes.thumbnail as string) ?? '', + author: decodeURIComponent( + (srcAttributes.author as string | undefined) ?? "", + ), + thumbnail: (srcAttributes.thumbnail as string | undefined) ?? "", draft: draft, - } + }; } -const LATEX_REGEX = /\$\$((?:(?!\$\$)[\s\S])*)\$\$/m -const MERMAID_REGEX = /
\s*([\s\S]*)<\/code>\s*<\/pre>/m
+const LATEX_REGEX = /\$\$((?:(?!\$\$)[\s\S])*)\$\$/m;
+const MERMAID_REGEX =
+  /
\s*([\s\S]*)<\/code>\s*<\/pre>/m;
 
 function transformLatexBlocks(srcHtml: string): string {
-  let outHtml = srcHtml
-  let match: RegExpExecArray | null = null
+  let outHtml = srcHtml;
+  let match: RegExpExecArray | null = null;
   do {
-    match = LATEX_REGEX.exec(outHtml)
-    if (match && match[1]) {
+    match = LATEX_REGEX.exec(outHtml);
+    if (match?.[1]) {
       try {
-        outHtml = outHtml.replace(match[0], katex.renderToString(match[1]))
+        outHtml = outHtml.replace(match[0], katex.renderToString(match[1]));
       } catch (ex) {
-        outHtml = outHtml.replace(match[0], `katex error: ${ex}`)
+        outHtml = outHtml.replace(
+          match[0],
+          `katex error: ${ex as Error}`,
+        );
       }
     }
-  } while (match && match[1])
-  return outHtml
+  } while (match?.[1]);
+  return outHtml;
 }
 
 function transformMermaidBlocks(srcHtml: string): string {
-  let outHtml = srcHtml
-  let match: RegExpExecArray | null = null
+  let outHtml = srcHtml;
+  let match: RegExpExecArray | null = null;
   do {
-    match = MERMAID_REGEX.exec(outHtml)
-    if (match && match[1]) {
-      outHtml = outHtml.replace(match[0], `
\n${match[1]}\n
`) + match = MERMAID_REGEX.exec(outHtml); + if (match?.[1]) { + outHtml = outHtml.replace( + match[0], + `
\n${match[1]}\n
`, + ); } - } while (match && match[1]) - return outHtml + } while (match?.[1]); + return outHtml; } function transformHtml(srcHtml: string): string { - let outHtml: string = srcHtml - outHtml = transformLatexBlocks(outHtml) - outHtml = transformMermaidBlocks(outHtml) - return outHtml + let outHtml: string = srcHtml; + outHtml = transformLatexBlocks(outHtml); + outHtml = transformMermaidBlocks(outHtml); + return outHtml; } /** * @deprecated */ export async function loadArticleOld(date: Date): Promise
{ - const year = date.getFullYear().toString() - const month = (date.getMonth() + 1).toString().padStart(2, '0') - const day = date.getDate().toString().padStart(2, '0') - const path = `./articles/${year}/${month}/${day}/` + const year = date.getFullYear().toString(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + const path = `./articles/${year}/${month}/${day}/`; try { - const data = (await import(`@articles/${year}/${month}/${day}/index.md`)) as MarkdownData + const data = (await import( + `@articles/${year}/${month}/${day}/index.md` + )) as MarkdownData; return { metadata: parseMetadata(data.attributes, path), html: transformHtml(data.html), - } + }; } catch (ex) { - console.error(ex) - return null + console.error(ex); + return null; } } export async function loadArticle(path: string): Promise
{ - const raw_articles = import.meta.glob('@articles/**/index.md') - const key = `/articles/${path}index.md` - if (!raw_articles[key]) return null + const raw_articles = import.meta.glob("@articles/**/index.md"); + const key = `/articles/${path}index.md`; + if (!raw_articles[key]) return null; try { - const data = (await raw_articles[key]()) as MarkdownData + const data = (await raw_articles[key]()) as MarkdownData; return { metadata: parseMetadata(data.attributes, path), html: transformHtml(data.html), - } + }; } catch (ex) { - console.error(ex) - return null + console.error(ex); + return null; } } export async function listArticles(): Promise { - const raw_articles = import.meta.glob('@articles/**/index.md') + const raw_articles = import.meta.glob("@articles/**/index.md"); const articles: ArticleMetadata[] = ( await Promise.all( Object.keys(raw_articles).map(async (key) => { - if (!raw_articles[key]) return null - const data = (await raw_articles[key]()) as MarkdownData - return parseMetadata(data.attributes, key.replace('index.md', '')) + if (!raw_articles[key]) return null; + const data = (await raw_articles[key]()) as MarkdownData; + return parseMetadata(data.attributes, key.replace("index.md", "")); }), ) - ).filter((item) => item !== null) - articles.sort((article1, article2) => article2.date.valueOf() - article1.date.valueOf()) - return articles + ).filter((item) => item !== null); + articles.sort( + (article1, article2) => article2.date.valueOf() - article1.date.valueOf(), + ); + return articles; } diff --git a/src/lib/config.ts b/src/lib/config.ts index bc628aa..a308b6b 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -4,15 +4,15 @@ import articlesConfig from '@articles/config.json' export const NAME: string = packageJson.name export const VERSION: string = packageJson.version export const REPOSITORY: string = packageJson.repository -export const TITLE: string = articlesConfig['title'] -export const SIGNATURE: string = articlesConfig['signature'] -export const COPYRIGHT: string = articlesConfig['copyright'] -export const RSS_LINK: string = articlesConfig['rss_link'] -export const BACK_LINK: string = articlesConfig['back_link'] -export const ABOUT_LINK: string = articlesConfig['about_link'] -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 TITLE: string = articlesConfig.title +export const SIGNATURE: string = articlesConfig.signature +export const COPYRIGHT: string = articlesConfig.copyright +export const RSS_LINK: string = articlesConfig.rss_link +export const BACK_LINK: string = articlesConfig.back_link +export const ABOUT_LINK: string = articlesConfig.about_link +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 BASE_URL: string = import.meta.env.BASE_URL export const PROD: boolean = import.meta.env.PROD diff --git a/src/lib/dates.ts b/src/lib/dates.ts index f71a3b4..2c00a0d 100644 --- a/src/lib/dates.ts +++ b/src/lib/dates.ts @@ -1,9 +1,9 @@ export function simpleDateFormat(date: Date): string { return ( - date.getFullYear() + - '-' + - (date.getMonth() + 1).toString().padStart(2, '0') + - '-' + - date.getDate().toString().padStart(2, '0') - ) + date.getFullYear().toString() + + "-" + + (date.getMonth() + 1).toString().padStart(2, "0") + + "-" + + date.getDate().toString().padStart(2, "0") + ); } diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 773a4e9..74cf5ab 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -24,7 +24,7 @@ onUpdated(updateDynamicContent)