diff --git a/package.json b/package.json index 7c68b427..4748d311 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,14 @@ "@types/node": "^18", "esbuild": "^0.15.14", "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", "json5": "^2.2.1", + "markdown-yaml-metadata-parser": "^3.0.0", "ts-node": "^10.9.1", "typescript": "^4.9.3", "xdm": "^3.4.0" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.5" } } diff --git a/scripts/build.ts b/scripts/build.ts index 5a2c0b17..13a9cc13 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -3,6 +3,8 @@ import path from "path"; import fs from "fs-extra"; import json5 from "json5"; +import YAML from 'js-yaml'; +import metadataParser from 'markdown-yaml-metadata-parser'; import { renderMdx } from "./mdx.ts"; @@ -12,7 +14,6 @@ const PEOPLE_DIR = "people"; const COMMENTS_DIR = "comments"; const DIST_DIR = "dist"; -const DIST_PEOPLE_LIST = "people-list.json"; const projectRoot = path.dirname(path.dirname(url.fileURLToPath(import.meta.url))); const peopleDir = path.join(projectRoot, PEOPLE_DIR); @@ -26,24 +27,38 @@ const people = fs.readdirSync(peopleDir).map(person => ({ // Extract metadata from `people/${dirname}/info.json5` to `dist/people-list.json`. function buildPeopleInfoAndList() { const PEOPLE_LIST_KEYS = ["id", "name", "profileUrl"] as const; - type PeopleMeta = Record<"path" | typeof PEOPLE_LIST_KEYS[number], unknown>; - const peopleList: PeopleMeta[] = []; - for (const { dirname, srcPath, distPath } of people) { - for (const lang of ['', '.zh_hant']) { - const infoFile = fs.readFileSync(path.join(srcPath, `info${lang}.json5`), "utf-8"); + // Read internationalized key names + const infoKeys = YAML.load(fs.readFileSync('info-i18n.yml')) + + // Compile into multiple languages + for (const lang of ['', '.zh_hant']) { + + // Compiled meta of list of people for the front page (contains keys id, name, profileUrl) + const peopleList: PeopleMeta[] = []; + + // For each person + for (const { dirname, srcPath, distPath } of people) { + const infoFile = fs.readFileSync(path.join(srcPath, `info.json5`), "utf-8"); const info = json5.parse(infoFile); - // Add meta information - const peopleMeta = { - path: dirname, - ...Object.fromEntries(PEOPLE_LIST_KEYS.map(key => [key, info[key]])) - } as PeopleMeta; + // Read the page.md of that language + const markdown = fs.readFileSync(path.join(srcPath, `page${lang}.md`), "utf-8"); - // Avoid duplicates - if (peopleList.filter(it => it.id == peopleMeta.id).length == 0) - peopleList.push(peopleMeta); + // Get the markdown header + const mdMeta = metadataParser(markdown).metadata + info.name = mdMeta.name + + // Convert info dict to [[key, value], ...] + // And add info k-v pairs from markdown to the info object in json5 + info.info = [...Object.entries(mdMeta.info ?? {}), ...Object.entries(info.info ?? {})] + + // Convert key names to internationalized key names + let langKey = indexTrim(lang, ".") + if (langKey == '') langKey = "zh_hans" + const keys = infoKeys[langKey]['key'] + info.info = info.info.map(pair => [pair[0] in keys ? keys[pair[0]] : pair[0], pair[1]]) // Combine comments in people/${dirname}/comments/${cf}.json const commentPath = path.join(srcPath, COMMENTS_DIR) @@ -55,11 +70,21 @@ function buildPeopleInfoAndList() { // Write info.json fs.ensureDirSync(distPath); fs.writeFileSync(path.join(distPath, `info${lang}.json`), JSON.stringify(info)); - } - } - // Write people-list.json - fs.writeFileSync(path.join(projectRoot, DIST_DIR, DIST_PEOPLE_LIST), JSON.stringify(peopleList)); + // Create people list meta information + const peopleMeta = { + path: dirname, + ...Object.fromEntries(PEOPLE_LIST_KEYS.map(key => [key, info[key]])) + } as PeopleMeta; + + // Add meta to people list + if (peopleList.filter(it => it.id == peopleMeta.id).length == 0) + peopleList.push(peopleMeta); + } + + // Write people-list.json + fs.writeFileSync(path.join(projectRoot, DIST_DIR, `people-list${lang}.json`), JSON.stringify(peopleList)); + } } // Render `people/${dirname}/page.md` to `dist/people/${dirname}/page.js`. @@ -67,7 +92,8 @@ function buildPeoplePages() { for (const { srcPath, distPath } of people) { for (const lang of ['', '.zh_hant']) { - const markdown = fs.readFileSync(path.join(srcPath, `page${lang}.md`), "utf-8"); + // Read markdown page and remove markdown meta + const markdown = metadataParser(fs.readFileSync(path.join(srcPath, `page${lang}.md`), "utf-8")).content; const result = renderMdx(markdown); fs.ensureDirSync(distPath); @@ -101,3 +127,22 @@ buildPeopleInfoAndList(); buildPeoplePages(); copyPeopleAssets(); copyPublic(); + +/** + * Trim a specific char from a string + * + * @param str String + * @param ch Character (must have len 1) + */ +function indexTrim(str: string, ch: string) { + let start = 0 + let end = str.length + + while (start < end && str[start] === ch) + ++start; + + while (end > start && str[end - 1] === ch) + --end; + + return (start > 0 || end < str.length) ? str.substring(start, end) : str; +} diff --git a/yarn.lock b/yarn.lock index e761e643..7b8d1b83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -201,6 +201,11 @@ dependencies: "@types/unist" "*" +"@types/js-yaml@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== + "@types/mdast@^3.0.0": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" @@ -248,6 +253,18 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + astring@^1.6.0, astring@^1.8.0: version "1.8.3" resolved "https://registry.yarnpkg.com/astring/-/astring-1.8.3.tgz#1a0ae738c7cc558f8e5ddc8e3120636f5cebcb85" @@ -332,6 +349,11 @@ dequal@^2.0.0: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +detect-newline@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -470,6 +492,11 @@ esbuild@^0.15.14: esbuild-windows-64 "0.15.14" esbuild-windows-arm64 "0.15.14" +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + estree-util-attach-comments@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-2.1.0.tgz#47d69900588bcbc6bf58c3798803ec5f1f3008de" @@ -628,6 +655,21 @@ is-reference@^3.0.0: dependencies: "@types/estree" "*" +js-yaml@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" @@ -662,6 +704,14 @@ markdown-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3" integrity sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q== +markdown-yaml-metadata-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/markdown-yaml-metadata-parser/-/markdown-yaml-metadata-parser-3.0.0.tgz#648ce68850453bfc18aa365879350548edc26e38" + integrity sha512-gRxEfuGIpb9pS1nQyASx3+l99e1hyTaK/+zDuvGcZJvr+OlksZ5O+q7opPcQP25j/z7NoOYEp17Lxgq5Sn4vDg== + dependencies: + detect-newline "^3.1.0" + js-yaml "^3.14.1" + mdast-util-definitions@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz#2c1d684b28e53f84938bb06317944bee8efa79db" @@ -1159,6 +1209,11 @@ space-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + stringify-entities@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.3.tgz#cfabd7039d22ad30f3cc435b0ca2c1574fc88ef8"