Skip to content

Commit de32028

Browse files
committed
fix: restore document update dates
1 parent 5ee90ff commit de32028

8 files changed

Lines changed: 82 additions & 27 deletions

File tree

.github/workflows/pages.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ jobs:
2626
steps:
2727
- name: Check out repository
2828
uses: actions/checkout@v6
29+
with:
30+
fetch-depth: 0
2931

3032
- name: Set up Node.js
3133
uses: actions/setup-node@v6

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
22
dist/
33
*.log
4+
docs/last-updated.json

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
</noscript>
120120

121121
<script src="./vendor/gitalk.min.js"></script>
122-
<script src="./scripts/site.js?v=20260716-10"></script>
122+
<script src="./scripts/site.js?v=20260716-11"></script>
123123
<script src="./vendor/docsify.min.js"></script>
124124
<script src="./vendor/prism-bash.min.js"></script>
125125
<script src="./vendor/search.min.js"></script>

docs/scripts/site.js

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const REPOSITORY = "https://github.com/ArchLinuxStudio/ArchLinuxTutorial";
55
const THEME_STORAGE_KEY = "archtutorial-theme";
6-
const updateCache = new Map();
6+
let lastUpdatedManifestPromise;
77
let activeMarkdown = "";
88
let activeFile = "README.md";
99

@@ -78,30 +78,21 @@
7878
}
7979

8080
async function fetchLastUpdated(file) {
81-
const normalized = normalizeFile(file);
82-
if (updateCache.has(normalized)) return updateCache.get(normalized);
83-
84-
const storageKey = `archtutorial-updated:${normalized}`;
85-
const cached = sessionStorage.getItem(storageKey);
86-
if (cached) {
87-
updateCache.set(normalized, cached);
88-
return cached;
81+
if (!lastUpdatedManifestPromise) {
82+
lastUpdatedManifestPromise = fetch(new URL("last-updated.json", document.baseURI), {
83+
cache: "no-cache",
84+
headers: { Accept: "application/json" },
85+
}).then((response) => {
86+
if (!response.ok) throw new Error(`Last-updated metadata responded with ${response.status}`);
87+
return response.json();
88+
});
8989
}
9090

91-
const endpoint =
92-
"https://api.github.com/repos/ArchLinuxStudio/ArchLinuxTutorial/commits" +
93-
`?per_page=1&path=docs/${encodedFilePath(normalized)}`;
94-
const response = await fetch(endpoint, {
95-
headers: { Accept: "application/vnd.github+json" },
96-
});
97-
if (!response.ok) throw new Error(`GitHub responded with ${response.status}`);
98-
99-
const commits = await response.json();
100-
const date = commits?.[0]?.commit?.committer?.date?.slice(0, 10);
101-
if (!date) throw new Error("No commit date returned");
102-
103-
updateCache.set(normalized, date);
104-
sessionStorage.setItem(storageKey, date);
91+
const dates = await lastUpdatedManifestPromise;
92+
const date = dates[normalizeFile(file)];
93+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date || "")) {
94+
throw new Error(`No last-updated date for ${file}`);
95+
}
10596
return date;
10697
}
10798

@@ -313,6 +304,9 @@
313304
homepage: "README.md",
314305
loadNavbar: false,
315306
loadSidebar: true,
307+
alias: {
308+
"/.*/_sidebar.md": "/_sidebar.md",
309+
},
316310
auto2top: true,
317311
routerMode: "hash",
318312
notFoundPage: true,
@@ -321,7 +315,6 @@
321315
topMargin: 88,
322316
externalLinkTarget: "_blank",
323317
externalLinkRel: "noopener noreferrer",
324-
formatUpdated: "{YYYY}-{MM}-{DD}",
325318
search: {
326319
paths: "auto",
327320
depth: 4,

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"node": ">=20"
1414
},
1515
"scripts": {
16-
"dev": "node scripts/sync-vendor.mjs && node node_modules/serve/build/main.js docs -l 3000 --no-clipboard",
17-
"start": "node scripts/sync-vendor.mjs && node node_modules/serve/build/main.js docs -l 3000 --no-clipboard",
16+
"dev": "node scripts/sync-vendor.mjs && node scripts/generate-last-updated.mjs && node node_modules/serve/build/main.js docs -l 3000 --no-clipboard",
17+
"start": "node scripts/sync-vendor.mjs && node scripts/generate-last-updated.mjs && node node_modules/serve/build/main.js docs -l 3000 --no-clipboard",
1818
"sync:vendor": "node scripts/sync-vendor.mjs",
1919
"check": "node scripts/validate-site.mjs",
2020
"build": "node scripts/sync-vendor.mjs && node scripts/build.mjs"

scripts/build.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
22
import { dirname, resolve } from "node:path";
33
import { fileURLToPath } from "node:url";
44
import "./validate-site.mjs";
5+
import { writeLastUpdatedManifest } from "./generate-last-updated.mjs";
56

67
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
78
const output = resolve(root, "dist");
89

910
rmSync(output, { recursive: true, force: true });
1011
mkdirSync(output, { recursive: true });
1112
cpSync(resolve(root, "docs"), output, { recursive: true });
13+
writeLastUpdatedManifest(resolve(output, "last-updated.json"));
1214
writeFileSync(resolve(output, ".nojekyll"), "");
1315

1416
console.log("Static site built in dist/.");

scripts/generate-last-updated.mjs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { execFileSync } from "node:child_process";
2+
import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
3+
import { dirname, relative, resolve } from "node:path";
4+
import { fileURLToPath } from "node:url";
5+
6+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7+
const docsDirectory = resolve(root, "docs");
8+
9+
function markdownFiles(directory) {
10+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
11+
const path = resolve(directory, entry.name);
12+
if (entry.isDirectory()) return markdownFiles(path);
13+
return entry.name.toLowerCase().endsWith(".md") ? [path] : [];
14+
});
15+
}
16+
17+
export function collectLastUpdatedDates() {
18+
const dates = {};
19+
20+
for (const file of markdownFiles(docsDirectory)) {
21+
const documentPath = relative(docsDirectory, file).replaceAll("\\", "/");
22+
const repositoryPath = `docs/${documentPath}`;
23+
const date = execFileSync(
24+
"git",
25+
["log", "-1", "--follow", "--format=%cs", "--", repositoryPath],
26+
{ cwd: root, encoding: "utf8" },
27+
).trim();
28+
29+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
30+
throw new Error(`Could not determine the last commit date for ${repositoryPath}.`);
31+
}
32+
33+
dates[documentPath] = date;
34+
}
35+
36+
return Object.fromEntries(Object.entries(dates).sort(([left], [right]) => left.localeCompare(right)));
37+
}
38+
39+
export function writeLastUpdatedManifest(outputFile) {
40+
const dates = collectLastUpdatedDates();
41+
mkdirSync(dirname(outputFile), { recursive: true });
42+
writeFileSync(outputFile, `${JSON.stringify(dates, null, 2)}\n`, "utf8");
43+
console.log(`Generated last-updated metadata for ${Object.keys(dates).length} documents.`);
44+
}
45+
46+
const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
47+
if (invokedDirectly) {
48+
writeLastUpdatedManifest(resolve(process.argv[2] || resolve(docsDirectory, "last-updated.json")));
49+
}

scripts/validate-site.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@ const files = walk(docsDirectory);
2121
const markdownFiles = files.filter((file) => extname(file).toLowerCase() === ".md");
2222
const index = readFileSync(resolve(docsDirectory, "index.html"), "utf8");
2323
const siteScript = readFileSync(resolve(docsDirectory, "scripts/site.js"), "utf8");
24+
const workflow = readFileSync(resolve(root, ".github/workflows/pages.yml"), "utf8");
25+
const packageSource = readFileSync(resolve(root, "package.json"), "utf8");
2426

2527
assert(markdownFiles.length === 23, `Expected 23 Chinese Markdown files, found ${markdownFiles.length}.`);
2628
assert(index.includes('lang="zh-CN"'), "The document language must remain zh-CN.");
2729
assert(index.includes("./vendor/docsify.min.js"), "The local Docsify bundle is not linked.");
2830
assert(index.includes("./scripts/site.js"), "The site controller is not linked.");
2931
assert(!/darkreader|unpkg\.com|fonts\.googleapis\.com/i.test(index), "Unexpected remote UI dependency found in index.html.");
32+
assert(siteScript.includes('new URL("last-updated.json", document.baseURI)'), "Local last-updated metadata is not loaded.");
33+
assert(!siteScript.includes("api.github.com/repos/"), "Last-updated dates must not depend on the GitHub API at runtime.");
34+
assert(siteScript.includes('"/.*/_sidebar.md": "/_sidebar.md"'), "Nested routes must reuse the root sidebar.");
35+
assert(!siteScript.includes("formatUpdated:"), "Unused Docsify update formatting must stay removed.");
36+
assert(workflow.includes("fetch-depth: 0"), "The deployment checkout must include full Git history.");
37+
assert(packageSource.includes("scripts/generate-last-updated.mjs"), "The local preview does not generate last-updated metadata.");
3038

3139
const commentInvariants = [
3240
'clientID: "296c581fc4b2a837a1e3"',

0 commit comments

Comments
 (0)