ci: add Makefile and stricter eslint config
This commit is contained in:
+17
-5
@@ -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
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
||||
@@ -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 $*
|
||||
@@ -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=="],
|
||||
|
||||
+24
-18
@@ -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",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
+6
-4
@@ -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",
|
||||
|
||||
+83
-71
@@ -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<string, string> | 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<string, string> = {
|
||||
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<string, string>, baseHtml: string): string {
|
||||
let outHtml = baseHtml
|
||||
outHtml = outHtml.replace(/<.*?property="og:url".*?>/gm, '')
|
||||
function formatArticlePage(
|
||||
metadata: Record<string, string>,
|
||||
baseHtml: string,
|
||||
): string {
|
||||
let outHtml = baseHtml;
|
||||
outHtml = outHtml.replace(/<.*?property="og:url".*?>/gm, "");
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:url" content="${articlesConfig['base_url']}${metadata.path}/">\n</head>`,
|
||||
)
|
||||
const blog_title = articlesConfig['title']?.replace(/(<([^>]+)>)/gi, '').trim()
|
||||
`<meta property="og:url" content="${articlesConfig.base_url}${metadata.path}/">\n</head>`,
|
||||
);
|
||||
const blog_title = articlesConfig.title.replace(/(<([^>]+)>)/gi, "").trim();
|
||||
if (metadata.title) {
|
||||
const title = metadata.title.replace(/(<([^>]+)>)/gi, '').trim()
|
||||
outHtml = outHtml.replace(/<title>.*?<\/title>/gm, `<title>${blog_title} — ${title}</title>`)
|
||||
outHtml = outHtml.replace(/<.*?property="og:title".*?>/gm, '')
|
||||
const title = metadata.title.replace(/(<([^>]+)>)/gi, "").trim();
|
||||
outHtml = outHtml.replace(
|
||||
/<title>.*?<\/title>/gm,
|
||||
`<title>${blog_title} — ${title}</title>`,
|
||||
);
|
||||
outHtml = outHtml.replace(/<.*?property="og:title".*?>/gm, "");
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:title" content="${title}">\n</head>`,
|
||||
)
|
||||
outHtml = outHtml.replace(/<.*?property="og:description".*?>/gm, '')
|
||||
);
|
||||
outHtml = outHtml.replace(/<.*?property="og:description".*?>/gm, "");
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:description" content="${blog_title}">\n</head>`,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (metadata.thumbnail) {
|
||||
outHtml = outHtml.replace(/<.*?property="og:image".*?>/gm, '')
|
||||
outHtml = outHtml.replace(/<.*?property="og:image".*?>/gm, "");
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:image" content="${metadata.thumbnail.replace('./', articlesConfig['base_url'] + metadata.path + '/')}">\n</head>`,
|
||||
)
|
||||
`<meta property="og:image" content="${metadata.thumbnail.replace("./", articlesConfig.base_url + metadata.path + "/")}">\n</head>`,
|
||||
);
|
||||
}
|
||||
return outHtml
|
||||
return outHtml;
|
||||
}
|
||||
|
||||
function addFeedArticle(metadata: Record<string, string>, 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`);
|
||||
|
||||
+77
-61
@@ -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<string, unknown>,
|
||||
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 = /<pre>\s*<code class="language-mermaid">([\s\S]*)<\/code>\s*<\/pre>/m
|
||||
const LATEX_REGEX = /\$\$((?:(?!\$\$)[\s\S])*)\$\$/m;
|
||||
const MERMAID_REGEX =
|
||||
/<pre>\s*<code class="language-mermaid">([\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], `<i>katex error: ${ex}</i>`)
|
||||
outHtml = outHtml.replace(
|
||||
match[0],
|
||||
`<i>katex error: ${ex as Error}</i>`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} 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], `<pre class="mermaid">\n${match[1]}\n</pre>`)
|
||||
match = MERMAID_REGEX.exec(outHtml);
|
||||
if (match?.[1]) {
|
||||
outHtml = outHtml.replace(
|
||||
match[0],
|
||||
`<pre class="mermaid">\n${match[1]}\n</pre>`,
|
||||
);
|
||||
}
|
||||
} 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<Article | null> {
|
||||
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<Article | null> {
|
||||
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<ArticleMetadata[]> {
|
||||
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;
|
||||
}
|
||||
|
||||
+10
-10
@@ -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
|
||||
|
||||
+6
-6
@@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ onUpdated(updateDynamicContent)
|
||||
|
||||
<template>
|
||||
<div v-if="!loading" class="home">
|
||||
<template v-for="(metadata, index) in articles" v-bind:key="index">
|
||||
<template v-for="(metadata, index) in articles" :key="index">
|
||||
<div v-if="(!metadata.draft || !PROD) && metadata.path" class="article-item">
|
||||
<RouterLink :to="metadata.path">
|
||||
<h2 v-html="metadata.title"></h2>
|
||||
|
||||
+5
-5
@@ -10,11 +10,11 @@ import articlesConfig from './articles/config.json'
|
||||
export default ({ mode }: { mode: string }) => {
|
||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }
|
||||
|
||||
process.env.VITE_BASE_URL = articlesConfig['base_url']
|
||||
process.env.VITE_APP_TITLE = articlesConfig['title']
|
||||
process.env.VITE_APP_TITLE_NO_HTML = articlesConfig['title'].replace(/(<([^>]+)>)/gi, '').trim()
|
||||
process.env.VITE_APP_LANG = articlesConfig['lang']
|
||||
process.env.VITE_CUSTOM_HEAD = articlesConfig['custom_head']
|
||||
process.env.VITE_BASE_URL = articlesConfig.base_url
|
||||
process.env.VITE_APP_TITLE = articlesConfig.title
|
||||
process.env.VITE_APP_TITLE_NO_HTML = articlesConfig.title.replace(/(<([^>]+)>)/gi, '').trim()
|
||||
process.env.VITE_APP_LANG = articlesConfig.lang
|
||||
process.env.VITE_CUSTOM_HEAD = articlesConfig.custom_head
|
||||
|
||||
return defineConfig({
|
||||
plugins: [vue(), vueDevTools(), mdPlugin({ mode: [Mode.HTML] })],
|
||||
|
||||
Reference in New Issue
Block a user