fix: Optimize docs page API (7MB→19KB), fix categories, add MI translations
- Add summary projection to Document.list() excluding heavy content fields - Fix 23 documents with invalid categories (framework/governance/reference) - Archive 9 duplicate documents (kept canonical short-slug versions) - Add Te Reo Māori UI translations to docs-app.js and document-cards.js - Refactor language checks to be extensible (no more hardcoded EN/DE/FR) - Remove unused font preloads from docs.html (fixes browser warning) - Add resources to valid publish categories Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
28cb1139a9
commit
0ef20a6e8d
5 changed files with 84 additions and 35 deletions
|
|
@ -38,10 +38,6 @@
|
||||||
<meta name="apple-mobile-web-app-title" content="Tractatus">
|
<meta name="apple-mobile-web-app-title" content="Tractatus">
|
||||||
<link rel="apple-touch-icon" href="/images/tractatus-icon-new.svg">
|
<link rel="apple-touch-icon" href="/images/tractatus-icon-new.svg">
|
||||||
|
|
||||||
<!-- Preload critical fonts for faster initial render -->
|
|
||||||
<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>
|
|
||||||
<link rel="preload" href="/fonts/inter-700.woff2" as="font" type="font/woff2" crossorigin>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/css/fonts.css?v=0.1.2.1770750464740">
|
<link rel="stylesheet" href="/css/fonts.css?v=0.1.2.1770750464740">
|
||||||
<link rel="stylesheet" href="/css/tailwind.css?v=0.1.2.1770750464740">
|
<link rel="stylesheet" href="/css/tailwind.css?v=0.1.2.1770750464740">
|
||||||
<link rel="stylesheet" href="/css/tractatus-theme.min.css?v=0.1.2.1770750464740">
|
<link rel="stylesheet" href="/css/tractatus-theme.min.css?v=0.1.2.1770750464740">
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,14 @@ class DocumentCards {
|
||||||
practical: 'Guides pratiques',
|
practical: 'Guides pratiques',
|
||||||
technical: 'Détails techniques',
|
technical: 'Détails techniques',
|
||||||
reference: 'Documentation de référence'
|
reference: 'Documentation de référence'
|
||||||
|
},
|
||||||
|
mi: {
|
||||||
|
title: 'Aratohu Tae',
|
||||||
|
critical: 'Ngā wāhanga waipuke',
|
||||||
|
conceptual: 'Ngā whakamāramatanga ariā',
|
||||||
|
practical: 'Ngā aratohu whaihua',
|
||||||
|
technical: 'Ngā taipitopito hangarau',
|
||||||
|
reference: 'Ngā tuhinga tohutoro'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,32 @@ const UI_TRANSLATIONS = {
|
||||||
'advanced-topics': 'Sujets avancés',
|
'advanced-topics': 'Sujets avancés',
|
||||||
'business-leadership': 'Business & Leadership'
|
'business-leadership': 'Business & Leadership'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mi: {
|
||||||
|
pageTitle: 'Ngā Tuhinga Anga',
|
||||||
|
pageSubtitle: 'Ngā tautukunga hangarau, ngā aratohu, me ngā rauemi tohutoro',
|
||||||
|
documentsHeading: 'Ngā Tuhinga',
|
||||||
|
searchButton: 'Rapu',
|
||||||
|
backToDocuments: 'Hoki ki ngā Tuhinga',
|
||||||
|
selectDocument: 'Kōwhiria he Tuhinga',
|
||||||
|
selectDocumentDesc: 'Kōwhiria he tuhinga mai i te taha ki te tīmata pānui',
|
||||||
|
loadingDocument: 'Kei te uta te tuhinga...',
|
||||||
|
errorLoadingDoc: 'He hapa i te utanga o te tuhinga',
|
||||||
|
tableOfContents: 'Rārangi Kaupapa',
|
||||||
|
downloadPdf: 'Tikiake PDF',
|
||||||
|
github: 'GitHub',
|
||||||
|
publicRepository: 'Pūtahi Tūmatanui',
|
||||||
|
publicRepositoryDesc: 'Waehere pūtake, tauira me ngā koha',
|
||||||
|
readmeQuickStart: 'README & Tīmata Tere',
|
||||||
|
readmeQuickStartDesc: 'Aratohu whakauru me te tīmata',
|
||||||
|
categories: {
|
||||||
|
'getting-started': 'Tīmata',
|
||||||
|
'resources': 'Ngā Rauemi',
|
||||||
|
'research-theory': 'Rangahau & Ariā',
|
||||||
|
'technical-reference': 'Tohutoro Hangarau',
|
||||||
|
'advanced-topics': 'Kaupapa Matatau',
|
||||||
|
'business-leadership': 'Pakihi & Hautūtanga'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -95,17 +121,15 @@ function updatePageUI(lang = currentLanguage) {
|
||||||
const t = getUITranslations(lang);
|
const t = getUITranslations(lang);
|
||||||
|
|
||||||
// Update page header
|
// Update page header
|
||||||
|
const allPageTitles = Object.values(UI_TRANSLATIONS).map(t => t.pageTitle);
|
||||||
const pageTitle = document.querySelector('h1');
|
const pageTitle = document.querySelector('h1');
|
||||||
if (pageTitle && pageTitle.textContent === UI_TRANSLATIONS.en.pageTitle ||
|
if (pageTitle && allPageTitles.includes(pageTitle.textContent)) {
|
||||||
pageTitle.textContent === UI_TRANSLATIONS.de.pageTitle ||
|
|
||||||
pageTitle.textContent === UI_TRANSLATIONS.fr.pageTitle) {
|
|
||||||
pageTitle.textContent = t.pageTitle;
|
pageTitle.textContent = t.pageTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allPageSubtitles = Object.values(UI_TRANSLATIONS).map(t => t.pageSubtitle);
|
||||||
const pageSubtitle = document.querySelector('.text-gray-600.mt-2');
|
const pageSubtitle = document.querySelector('.text-gray-600.mt-2');
|
||||||
if (pageSubtitle && (pageSubtitle.textContent === UI_TRANSLATIONS.en.pageSubtitle ||
|
if (pageSubtitle && allPageSubtitles.includes(pageSubtitle.textContent)) {
|
||||||
pageSubtitle.textContent === UI_TRANSLATIONS.de.pageSubtitle ||
|
|
||||||
pageSubtitle.textContent === UI_TRANSLATIONS.fr.pageSubtitle)) {
|
|
||||||
pageSubtitle.textContent = t.pageSubtitle;
|
pageSubtitle.textContent = t.pageSubtitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,20 +140,17 @@ function updatePageUI(lang = currentLanguage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update sidebar Documents heading
|
// Update sidebar Documents heading
|
||||||
|
const allDocsHeadings = Object.values(UI_TRANSLATIONS).map(t => t.documentsHeading);
|
||||||
const docsHeading = document.querySelector('aside h3');
|
const docsHeading = document.querySelector('aside h3');
|
||||||
if (docsHeading && (docsHeading.textContent === UI_TRANSLATIONS.en.documentsHeading ||
|
if (docsHeading && allDocsHeadings.includes(docsHeading.textContent)) {
|
||||||
docsHeading.textContent === UI_TRANSLATIONS.de.documentsHeading ||
|
|
||||||
docsHeading.textContent === UI_TRANSLATIONS.fr.documentsHeading)) {
|
|
||||||
docsHeading.textContent = t.documentsHeading;
|
docsHeading.textContent = t.documentsHeading;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update GitHub section heading
|
// Update GitHub section heading
|
||||||
|
const allGithubLabels = Object.values(UI_TRANSLATIONS).map(t => t.github);
|
||||||
const githubHeadings = document.querySelectorAll('aside h3');
|
const githubHeadings = document.querySelectorAll('aside h3');
|
||||||
githubHeadings.forEach(heading => {
|
githubHeadings.forEach(heading => {
|
||||||
if (heading.textContent.trim() === UI_TRANSLATIONS.en.github ||
|
if (allGithubLabels.includes(heading.textContent.trim())) {
|
||||||
heading.textContent.trim() === UI_TRANSLATIONS.de.github ||
|
|
||||||
heading.textContent.trim() === UI_TRANSLATIONS.fr.github) {
|
|
||||||
// Keep the SVG icon, just update text
|
|
||||||
const textNode = Array.from(heading.childNodes).find(node => node.nodeType === Node.TEXT_NODE);
|
const textNode = Array.from(heading.childNodes).find(node => node.nodeType === Node.TEXT_NODE);
|
||||||
if (textNode) {
|
if (textNode) {
|
||||||
textNode.textContent = t.github;
|
textNode.textContent = t.github;
|
||||||
|
|
@ -138,31 +159,28 @@ function updatePageUI(lang = currentLanguage) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update GitHub links
|
// Update GitHub links
|
||||||
|
const allRepoTitles = Object.values(UI_TRANSLATIONS).map(t => t.publicRepository);
|
||||||
|
const allReadmeTitles = Object.values(UI_TRANSLATIONS).map(t => t.readmeQuickStart);
|
||||||
|
const allRepoDescs = Object.values(UI_TRANSLATIONS).map(t => t.publicRepositoryDesc);
|
||||||
|
const allReadmeDescs = Object.values(UI_TRANSLATIONS).map(t => t.readmeQuickStartDesc);
|
||||||
|
|
||||||
const githubLinks = document.querySelectorAll('aside a[href*="github.com"]');
|
const githubLinks = document.querySelectorAll('aside a[href*="github.com"]');
|
||||||
githubLinks.forEach(link => {
|
githubLinks.forEach(link => {
|
||||||
const titleDiv = link.querySelector('.text-sm.font-medium');
|
const titleDiv = link.querySelector('.text-sm.font-medium');
|
||||||
const descDiv = link.querySelector('.text-xs.text-gray-500');
|
const descDiv = link.querySelector('.text-xs.text-gray-500');
|
||||||
|
|
||||||
if (titleDiv) {
|
if (titleDiv) {
|
||||||
if (titleDiv.textContent === UI_TRANSLATIONS.en.publicRepository ||
|
if (allRepoTitles.includes(titleDiv.textContent)) {
|
||||||
titleDiv.textContent === UI_TRANSLATIONS.de.publicRepository ||
|
|
||||||
titleDiv.textContent === UI_TRANSLATIONS.fr.publicRepository) {
|
|
||||||
titleDiv.textContent = t.publicRepository;
|
titleDiv.textContent = t.publicRepository;
|
||||||
} else if (titleDiv.textContent === UI_TRANSLATIONS.en.readmeQuickStart ||
|
} else if (allReadmeTitles.includes(titleDiv.textContent)) {
|
||||||
titleDiv.textContent === UI_TRANSLATIONS.de.readmeQuickStart ||
|
|
||||||
titleDiv.textContent === UI_TRANSLATIONS.fr.readmeQuickStart) {
|
|
||||||
titleDiv.textContent = t.readmeQuickStart;
|
titleDiv.textContent = t.readmeQuickStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descDiv) {
|
if (descDiv) {
|
||||||
if (descDiv.textContent === UI_TRANSLATIONS.en.publicRepositoryDesc ||
|
if (allRepoDescs.includes(descDiv.textContent)) {
|
||||||
descDiv.textContent === UI_TRANSLATIONS.de.publicRepositoryDesc ||
|
|
||||||
descDiv.textContent === UI_TRANSLATIONS.fr.publicRepositoryDesc) {
|
|
||||||
descDiv.textContent = t.publicRepositoryDesc;
|
descDiv.textContent = t.publicRepositoryDesc;
|
||||||
} else if (descDiv.textContent === UI_TRANSLATIONS.en.readmeQuickStartDesc ||
|
} else if (allReadmeDescs.includes(descDiv.textContent)) {
|
||||||
descDiv.textContent === UI_TRANSLATIONS.de.readmeQuickStartDesc ||
|
|
||||||
descDiv.textContent === UI_TRANSLATIONS.fr.readmeQuickStartDesc) {
|
|
||||||
descDiv.textContent = t.readmeQuickStartDesc;
|
descDiv.textContent = t.readmeQuickStartDesc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ async function listDocuments(req, res) {
|
||||||
filter.audience = audience;
|
filter.audience = audience;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use summary projection for list endpoint (returns ~7KB instead of ~7MB)
|
||||||
|
const summary = req.query.fields !== 'full';
|
||||||
|
|
||||||
if (quadrant && !audience) {
|
if (quadrant && !audience) {
|
||||||
documents = await Document.findByQuadrant(quadrant, {
|
documents = await Document.findByQuadrant(quadrant, {
|
||||||
limit: parseInt(limit),
|
limit: parseInt(limit),
|
||||||
|
|
@ -48,7 +51,8 @@ async function listDocuments(req, res) {
|
||||||
documents = await Document.list({
|
documents = await Document.list({
|
||||||
limit: parseInt(limit),
|
limit: parseInt(limit),
|
||||||
skip: parseInt(skip),
|
skip: parseInt(skip),
|
||||||
filter
|
filter,
|
||||||
|
summary
|
||||||
});
|
});
|
||||||
total = await Document.count(filter);
|
total = await Document.count(filter);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -223,8 +223,8 @@ class Document {
|
||||||
|
|
||||||
// Validate category is in allowed list
|
// Validate category is in allowed list
|
||||||
const validCategories = [
|
const validCategories = [
|
||||||
'getting-started', 'technical-reference', 'research-theory',
|
'getting-started', 'resources', 'research-theory', 'technical-reference',
|
||||||
'advanced-topics', 'case-studies', 'business-leadership', 'archives'
|
'advanced-topics', 'business-leadership', 'archives'
|
||||||
];
|
];
|
||||||
if (!validCategories.includes(category)) {
|
if (!validCategories.includes(category)) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -310,13 +310,36 @@ class Document {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all documents
|
* List all documents
|
||||||
|
* @param {object} options
|
||||||
|
* @param {boolean} options.summary - If true, return only summary fields (for sidebar/listing)
|
||||||
*/
|
*/
|
||||||
static async list(options = {}) {
|
static async list(options = {}) {
|
||||||
const collection = await getCollection('documents');
|
const collection = await getCollection('documents');
|
||||||
const { limit = 50, skip = 0, sort = { order: 1, 'metadata.date_created': -1 }, filter = {} } = options;
|
const { limit = 50, skip = 0, sort = { order: 1, 'metadata.date_created': -1 }, filter = {}, summary = false } = options;
|
||||||
|
|
||||||
return await collection
|
const query = collection.find(filter);
|
||||||
.find(filter)
|
|
||||||
|
if (summary) {
|
||||||
|
query.project({
|
||||||
|
title: 1,
|
||||||
|
slug: 1,
|
||||||
|
category: 1,
|
||||||
|
order: 1,
|
||||||
|
visibility: 1,
|
||||||
|
quadrant: 1,
|
||||||
|
persistence: 1,
|
||||||
|
audience: 1,
|
||||||
|
download_formats: 1,
|
||||||
|
'metadata.date_created': 1,
|
||||||
|
'metadata.date_updated': 1,
|
||||||
|
'metadata.version': 1,
|
||||||
|
'translations.de.title': 1,
|
||||||
|
'translations.fr.title': 1,
|
||||||
|
'translations.mi.title': 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query
|
||||||
.sort(sort)
|
.sort(sort)
|
||||||
.skip(skip)
|
.skip(skip)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue