Compare commits

..

6 Commits

Author SHA1 Message Date
codex-bot a4c9349fce Keep retired document pages readable outside the catalog 2026-11-05 22:27:12 +08:00
Zayden-Jung 4c4471c2bd Merge pull request '优先展示 Endorphin,撤下重复的 Endor 导航' (#52) from codex/endorphin-catalog into main
Verify and deploy Tabularium / verify-and-deploy (push) Failing after 10m31s
Reviewed-on: #52
2026-11-05 14:15:15 +00:00
codex-bot 53aef1eb64 Prioritize Endorphin and retire Endor from the catalog 2026-11-05 22:13:19 +08:00
Zayden-Jung 18af3802c6 Merge pull request 'Verify canonical document source URLs without redirects' (#50) from codex/verify-canonical-doc-routes into main
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 13m16s
Reviewed-on: #50
2026-11-04 13:54:18 +00:00
codex-bot 4e9ff80828 Verify canonical document source URLs without redirects 2026-11-04 21:46:13 +08:00
Zayden-Jung 0e1f99639f Merge pull request 'Upload document images through the API' (#49) from codex/document-image-api into main
Verify and deploy Tabularium / verify-and-deploy (push) Failing after 19m23s
Reviewed-on: #49
2026-11-04 13:23:36 +00:00
5 changed files with 36 additions and 9 deletions
+16 -1
View File
@@ -200,11 +200,15 @@ async function main() {
for (const doc of searchIndex as { slug: string; sourceSlug: string }[]) { for (const doc of searchIndex as { slug: string; sourceSlug: string }[]) {
assert.ok(!doc.slug.split("/").some((part) => /^\d+-/.test(part)), doc.slug); assert.ok(!doc.slug.split("/").some((part) => /^\d+-/.test(part)), doc.slug);
const legacy = await request(`/docs/${doc.sourceSlug}?from=legacy`, { headers }); const legacy = await request(`/docs/${doc.sourceSlug}?from=legacy`, { headers });
assert.ok(legacy.headers.get("cache-control")?.includes("no-store"));
if (doc.sourceSlug === doc.slug) {
assert.equal(legacy.status, 200, doc.sourceSlug);
} else {
assert.equal(legacy.status, 308, doc.sourceSlug); assert.equal(legacy.status, 308, doc.sourceSlug);
const destination = new URL(legacy.headers.get("location")!, base); const destination = new URL(legacy.headers.get("location")!, base);
assert.equal(destination.pathname, `/docs/${doc.slug}`); assert.equal(destination.pathname, `/docs/${doc.slug}`);
assert.equal(destination.searchParams.get("from"), "legacy"); assert.equal(destination.searchParams.get("from"), "legacy");
assert.ok(legacy.headers.get("cache-control")?.includes("no-store")); }
} }
const routes = ["/", ...searchIndex.map((doc: { slug: string }) => `/docs/${doc.slug}`)]; const routes = ["/", ...searchIndex.map((doc: { slug: string }) => `/docs/${doc.slug}`)];
const raw = await request("/api/documents/source?slug=endor/endor-purpose-draft", { headers }); const raw = await request("/api/documents/source?slug=endor/endor-purpose-draft", { headers });
@@ -254,6 +258,17 @@ async function main() {
for (const id of ids) assert.ok(html.includes(`href="#${id}"`), `TOC anchor: ${route}#${id}`); for (const id of ids) assert.ok(html.includes(`href="#${id}"`), `TOC anchor: ${route}#${id}`);
} }
} }
assert.ok(!home.includes('data-category="Endor"'), "Superseded Endor collection is absent from navigation");
if (searchIndex.some((doc: { collection: string }) => doc.collection === "Endorphin")) {
assert.equal([...home.matchAll(/data-category="([^"]+)"/g)][0]?.[1], "Endorphin", "Endorphin is the first category");
assert.ok(home.includes('/docs/endorphin/value-proposition'), "Endorphin opens at its value proposition");
}
// Retired documents remain reachable for historical links and gallery checks.
if (!purpose) {
const archived = await request("/docs/endor/endor-purpose-draft", { headers });
assert.equal(archived.status, 200);
purpose = await archived.text();
}
assert.ok(home.includes("资料库")); assert.ok(home.includes("资料库"));
assert.ok(home.includes("/docs/source-studies/pretext/overview")); assert.ok(home.includes("/docs/source-studies/pretext/overview"));
// Inline preview and zoom dialog must both use the compressed browser asset. // Inline preview and zoom dialog must both use the compressed browser asset.
+11 -2
View File
@@ -1,5 +1,11 @@
import type { DocLink } from "@/lib/docs"; import type { DocLink } from "@/lib/docs";
// Keep superseded Endor pages addressable without offering them as current material.
export function catalogDocuments<T extends DocLink>(docs: T[]): T[] {
return docs.filter((doc) => doc.collection !== "Endor 文档")
.sort((a, b) => Number(b.collection === "Endorphin") - Number(a.collection === "Endorphin"));
}
export function collectionLabel(name: string) { export function collectionLabel(name: string) {
return name.replace(/ 文档$| 源码教程$|问题集$/g, ""); return name.replace(/ 文档$| 源码教程$|问题集$/g, "");
} }
@@ -15,10 +21,13 @@ export function collectionIcon(name?: string): "book" | "code" | "layers" | "nod
if (name?.includes("垃圾回收")) return "nodes"; if (name?.includes("垃圾回收")) return "nodes";
return "book"; return "book";
} }
export function collectionEntries(docs: DocLink[]) { export function collectionEntries(documents: DocLink[]) {
const docs = catalogDocuments(documents);
return [...new Set(docs.map((doc) => doc.collection))].map((name) => { return [...new Set(docs.map((doc) => doc.collection))].map((name) => {
const items = docs.filter((doc) => doc.collection === name); const items = docs.filter((doc) => doc.collection === name);
const entry = items.find((doc) => doc.slug.endsWith("/overview")) ?? items[0]; const entry = items.find((doc) => doc.slug.endsWith("/overview"))
?? (name === "Endorphin" ? items.find((doc) => doc.featured) : undefined)
?? items[0];
return { name, entry, count: items.length }; return { name, entry, count: items.length };
}); });
} }
+2 -1
View File
@@ -1,4 +1,5 @@
import { randomUUID, createHash } from "node:crypto"; import { randomUUID, createHash } from "node:crypto";
import { catalogDocuments } from "./collections";
import { openSiteDatabase } from "./site-database"; import { openSiteDatabase } from "./site-database";
import { DocumentValidationError, DocumentConflictError } from "./document-errors"; import { DocumentValidationError, DocumentConflictError } from "./document-errors";
import { sectionSchema, loadSections, reconcileSections, persistSections, splitSections, outline, import { sectionSchema, loadSections, reconcileSections, persistSections, splitSections, outline,
@@ -188,7 +189,7 @@ export async function readDoc(slug: string): Promise<Doc> {
} }
export async function listDocs(): Promise<DocLink[]> { export async function listDocs(): Promise<DocLink[]> {
return allDocuments().map(({ content: _, ...doc }) => { void _; return doc; }); return catalogDocuments(allDocuments()).map(({ content: _, ...doc }) => { void _; return doc; });
} }
// Explicit, atomic bootstrap only. Runtime reads never import repository data. // Explicit, atomic bootstrap only. Runtime reads never import repository data.
+2 -1
View File
@@ -1,4 +1,5 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { catalogDocuments } from "@/lib/collections";
import { allDocuments } from "@/lib/docs"; import { allDocuments } from "@/lib/docs";
// The proxy authenticates this endpoint, including index content. // The proxy authenticates this endpoint, including index content.
@@ -9,7 +10,7 @@ export default async function handler(request: NextApiRequest, response: NextApi
return response.status(405).end(); return response.status(405).end();
} }
try { try {
const index = allDocuments().map(({ content, ...doc }) => ({ ...doc, searchText: content })); const index = catalogDocuments(allDocuments()).map(({ content, ...doc }) => ({ ...doc, searchText: content }));
return response.status(200).json(index); return response.status(200).json(index);
} catch { } catch {
return response.status(503).json({ error: "文档数据库暂时不可用" }); return response.status(503).json({ error: "文档数据库暂时不可用" });
+2 -1
View File
@@ -58,7 +58,8 @@ export default function DocPage({
const [active, setActive] = useState(""); const [active, setActive] = useState("");
const siblings = docs.filter((d) => d.collection === doc.collection); const siblings = docs.filter((d) => d.collection === doc.collection);
const collectionEntry = collectionEntries(docs).find((c) => c.name === doc.collection)!.entry; // Historical pages remain readable even when their collection is outside the catalog.
const collectionEntry = collectionEntries(docs).find((c) => c.name === doc.collection)?.entry ?? doc;
const index = siblings.findIndex((d) => d.slug === doc.slug); const index = siblings.findIndex((d) => d.slug === doc.slug);
useEffect(() => { useEffect(() => {
const observer = new IntersectionObserver( const observer = new IntersectionObserver(