This commit is contained in:
parent
15f2688ca3
commit
edbce48613
33 changed files with 4943 additions and 623 deletions
|
@ -9,6 +9,7 @@ export interface PageLocation {
|
|||
tags: string[];
|
||||
map: string;
|
||||
root: string;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export interface PageInfoMetdata {
|
||||
|
|
107
utils/page_updater/run.ts
Normal file
107
utils/page_updater/run.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { type Page, postDirectories, generatePageCategory, PageList } from "./update_pagelist.ts"
|
||||
import * as pages from "./pages.ts";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Build the PageList
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* In the new structure, pages are stored under "content/[LANG]/[DIRECTORY]".
|
||||
* The following code:
|
||||
* 1. Reads the "content" directory to determine available language folders.
|
||||
* 2. For each language, loops over the defined postDirectories.
|
||||
* 3. If the expected subdirectory exists, it retrieves pages and adds them to the PageList.
|
||||
*/
|
||||
const contentRoot = "content";
|
||||
// Read all items in the content root and filter for directories (languages)
|
||||
const availableLangs = fs.readdirSync(contentRoot).filter((item) =>
|
||||
fs.statSync(path.join(contentRoot, item)).isDirectory()
|
||||
);
|
||||
|
||||
export const postList = new PageList();
|
||||
|
||||
for (const lang of availableLangs) {
|
||||
// Ensure the language exists in our PageList.
|
||||
if (!postList.languages[lang]) {
|
||||
postList.languages[lang] = { categories: {} };
|
||||
}
|
||||
|
||||
// Loop over each defined post category.
|
||||
for (const postDirectory of postDirectories) {
|
||||
// Construct the expected directory path for this language and category.
|
||||
// E.g., "content/en/site" or "content/fr/collections"
|
||||
const dirPath = path.join(contentRoot, lang, postDirectory.map);
|
||||
|
||||
// If the directory doesn't exist for this language, skip it.
|
||||
if (!fs.existsSync(dirPath)) continue;
|
||||
|
||||
// Retrieve page info for this directory.
|
||||
// We override the "root" with our computed directory path.
|
||||
const pagesInfo = pages.getPagesInfo("", {
|
||||
...postDirectory,
|
||||
root: dirPath,
|
||||
});
|
||||
|
||||
// If no pages are found, skip this category for the language.
|
||||
if (Object.keys(pagesInfo).length === 0) continue;
|
||||
|
||||
// Initialize the category if it doesn't exist.
|
||||
if (!postList.languages[lang].categories[postDirectory.title]) {
|
||||
postList.languages[lang].categories[postDirectory.title] =
|
||||
generatePageCategory({});
|
||||
// Set category metadata.
|
||||
postList.languages[lang].categories[postDirectory.title].title =
|
||||
postDirectory.title;
|
||||
postList.languages[lang].categories[postDirectory.title]
|
||||
.description = postDirectory.description;
|
||||
postList.languages[lang].categories[postDirectory.title].tags =
|
||||
postDirectory.tags;
|
||||
postList.languages[lang].categories[postDirectory.title].show =
|
||||
postDirectory.show;
|
||||
}
|
||||
|
||||
// Process each page in the retrieved pagesInfo.
|
||||
for (const [filePath, page] of Object.entries(pagesInfo)) {
|
||||
// Create a Page object.
|
||||
const pageDict: Page = {
|
||||
metadata: page.metadata,
|
||||
id: page.local_path, // e.g. "en/page.md"
|
||||
url: page.absolute_path.replace("content/", ""), // Remove "content/" prefix
|
||||
hash: page.hash,
|
||||
};
|
||||
|
||||
// Add the page to the appropriate language and category.
|
||||
postList.languages[lang].categories[postDirectory.title].posts.push(
|
||||
pageDict,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sort Pages by Date
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* For each language and category, sort the pages by date (most recent first).
|
||||
*/
|
||||
for (const lang of Object.keys(postList.languages)) {
|
||||
for (const category of Object.values(postList.languages[lang].categories)) {
|
||||
category.posts.sort((a, b) => {
|
||||
if (!a.metadata.date) return 1;
|
||||
if (!b.metadata.date) return -1;
|
||||
return new Date(b.metadata.date).getTime() -
|
||||
new Date(a.metadata.date).getTime();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Output the resulting PageList as JSON.
|
||||
console.log(postList.toJSONString());
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Save the PageList to File
|
||||
// ---------------------------------------------------------------------------
|
||||
fs.writeFileSync("assets/meta/post_list.json", postList.toJSONString());
|
|
@ -1,107 +1,281 @@
|
|||
import * as fs from 'node:fs';
|
||||
import * as pages from './pages.ts';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
// Type for metadata and page info
|
||||
import * as pages from "./pages.ts";
|
||||
import { format } from "date-fns";
|
||||
|
||||
/**
|
||||
* Interface representing a page's essential information.
|
||||
*/
|
||||
export interface Page {
|
||||
/** Metadata for the page (e.g., title, date, etc.) */
|
||||
metadata: pages.PageInfoMetdata;
|
||||
/** The page's identifier, e.g. "en/page.md" */
|
||||
id: string;
|
||||
/** The URL for the page (relative to content) */
|
||||
url: string;
|
||||
/** A hash representing the page's content/version */
|
||||
hash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface representing a category that groups multiple pages.
|
||||
*/
|
||||
export interface PageCategory {
|
||||
/** An array of pages that belong to this category */
|
||||
posts: Page[];
|
||||
/** The title of the category */
|
||||
title: string;
|
||||
/** A description for the category */
|
||||
description: string;
|
||||
/** Tags associated with the category */
|
||||
tags: string[];
|
||||
}
|
||||
export interface PageList {
|
||||
last_generated: string;
|
||||
categories: Record<string, PageCategory>;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
// Function to generate the page list
|
||||
function generatePageCategory(pagesInfo: Record<string, any>): PageCategory {
|
||||
/**
|
||||
* Class representing a collection of pages across multiple languages.
|
||||
*
|
||||
* The structure of a PageList is as follows:
|
||||
* - last_generated: A timestamp for when the list was last updated.
|
||||
* - languages: An object mapping language codes (e.g., "en", "es") to objects
|
||||
* containing categories, which in turn map to PageCategory objects.
|
||||
*/
|
||||
export class PageList {
|
||||
last_generated: string;
|
||||
languages: {
|
||||
[lang: string]: {
|
||||
categories: Record<string, PageCategory>;
|
||||
};
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.last_generated = format(new Date(), "yyyy-MM-dd HH:mm:ss");
|
||||
this.languages = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a JSON string to a PageList instance.
|
||||
* @param json - JSON string representation of a PageList
|
||||
* @returns A new PageList instance
|
||||
*/
|
||||
static fromJSON(json: string): PageList {
|
||||
return Object.assign(new PageList(), JSON.parse(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom JSON serialization.
|
||||
* This method returns a plain object representation, avoiding recursive calls.
|
||||
* @returns A plain object representation of the PageList.
|
||||
*/
|
||||
toJSON(): object {
|
||||
return {
|
||||
last_generated: this.last_generated,
|
||||
languages: this.languages,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JSON string representation of the PageList.
|
||||
* @returns JSON string of the PageList.
|
||||
*/
|
||||
toJSONString(): string {
|
||||
return JSON.stringify(this, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific category for a given language.
|
||||
* @param lang - The language code (e.g., "en")
|
||||
* @param categoryName - The name of the category
|
||||
* @returns The PageCategory if it exists; otherwise, undefined.
|
||||
*/
|
||||
getCategory(lang: string, categoryName: string): PageCategory | undefined {
|
||||
return this.languages[lang]?.categories[categoryName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all pages for a given language and category.
|
||||
* @param lang - The language code (e.g., "en")
|
||||
* @param categoryName - The name of the category
|
||||
* @returns An array of pages, or an empty array if not found.
|
||||
*/
|
||||
getPagesByCategory(lang: string, categoryName: string): Page[] {
|
||||
return this.languages[lang]?.categories[categoryName]?.posts || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the canonical ID for a page by removing the language prefix.
|
||||
* For example, "en/page.md" becomes "page.md".
|
||||
* @param pageId - The full page id including language folder
|
||||
* @returns The canonical page id
|
||||
*/
|
||||
static getCanonicalId(pageId: string): string {
|
||||
return pageId.replace(/^[^/]+\//, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific page by its canonical ID from a given language and category.
|
||||
* @param lang - The language code (e.g., "en")
|
||||
* @param categoryName - The category containing the page
|
||||
* @param canonicalId - The canonical id of the page (language prefix removed)
|
||||
* @returns The Page if found; otherwise, undefined.
|
||||
*/
|
||||
getPage(
|
||||
lang: string,
|
||||
categoryName: string,
|
||||
canonicalId: string,
|
||||
): Page | undefined {
|
||||
const pagesArr = this.getPagesByCategory(lang, categoryName);
|
||||
return pagesArr.find((page) =>
|
||||
PageList.getCanonicalId(page.id) === canonicalId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify all languages in which a page (by its canonical ID) is available.
|
||||
* @param canonicalId - The canonical id of the page (language prefix removed)
|
||||
* @returns An array of language codes where the page exists.
|
||||
*/
|
||||
getAvailableLanguagesForPage(canonicalId: string): string[] {
|
||||
const langs: string[] = [];
|
||||
for (const lang of Object.keys(this.languages)) {
|
||||
const categories = this.languages[lang].categories;
|
||||
for (const categoryName of Object.keys(categories)) {
|
||||
if (
|
||||
categories[categoryName].posts.some((page) =>
|
||||
PageList.getCanonicalId(page.id) === canonicalId
|
||||
)
|
||||
) {
|
||||
langs.push(lang);
|
||||
break; // Page found in this language; no need to check further categories.
|
||||
}
|
||||
}
|
||||
}
|
||||
return langs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate unique pages across all languages.
|
||||
* Each unique page (by canonical id) is listed along with:
|
||||
* - An array of languages in which it is available.
|
||||
* - A mapping of each language to its corresponding Page.
|
||||
* @returns An array of objects representing unique pages.
|
||||
*/
|
||||
enumerateUniquePages(): Array<
|
||||
{
|
||||
canonicalId: string;
|
||||
langs: string[];
|
||||
pages: { [lang: string]: Page };
|
||||
}
|
||||
> {
|
||||
const uniquePages: {
|
||||
[canonicalId: string]: {
|
||||
langs: Set<string>;
|
||||
pages: { [lang: string]: Page };
|
||||
};
|
||||
} = {};
|
||||
|
||||
for (const lang of Object.keys(this.languages)) {
|
||||
const categories = this.languages[lang].categories;
|
||||
for (const categoryName of Object.keys(categories)) {
|
||||
for (const page of categories[categoryName].posts) {
|
||||
const canonicalId = PageList.getCanonicalId(page.id);
|
||||
if (!uniquePages[canonicalId]) {
|
||||
uniquePages[canonicalId] = {
|
||||
langs: new Set(),
|
||||
pages: {},
|
||||
};
|
||||
}
|
||||
uniquePages[canonicalId].langs.add(lang);
|
||||
uniquePages[canonicalId].pages[lang] = page;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.entries(uniquePages).map(([canonicalId, data]) => ({
|
||||
canonicalId,
|
||||
langs: Array.from(data.langs),
|
||||
pages: data.pages,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PageCategory from the provided pagesInfo object.
|
||||
*
|
||||
* @param pagesInfo - An object where keys are paths (e.g., "content/[lang]/...") and values are page data.
|
||||
* @returns A PageCategory object containing an array of Page objects.
|
||||
*/
|
||||
export function generatePageCategory(pagesInfo: Record<string, any>): PageCategory {
|
||||
const pageList: Page[] = [];
|
||||
|
||||
for (const [path, page] of Object.entries(pagesInfo)) {
|
||||
for (const [filePath, page] of Object.entries(pagesInfo)) {
|
||||
// Expect the path in the form "content/[lang]/..."
|
||||
const langMatch = filePath.match(/^content\/([^/]+)\//);
|
||||
const lang = langMatch ? langMatch[1] : "default";
|
||||
|
||||
const pageDict: Page = {
|
||||
metadata: page.metadata,
|
||||
id: page.local_path,
|
||||
url: page.absolute_path.replace("content", ""),
|
||||
hash: page.hash
|
||||
id: page.local_path, // e.g. "en/page.md"
|
||||
url: page.absolute_path.replace("content/", ""), // Remove "content/" prefix
|
||||
hash: page.hash,
|
||||
};
|
||||
pageList.push(pageDict);
|
||||
}
|
||||
|
||||
pageList.forEach(page => {
|
||||
// Format dates consistently.
|
||||
pageList.forEach((page) => {
|
||||
if (page.metadata.date) {
|
||||
page.metadata.date = format(new Date(page.metadata.date), 'yyyy-MM-dd HH:mm:ss');
|
||||
page.metadata.date = format(
|
||||
new Date(page.metadata.date),
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const pageListDict: PageCategory = {
|
||||
return {
|
||||
posts: pageList,
|
||||
title: "",
|
||||
description: "",
|
||||
tags: []
|
||||
tags: [],
|
||||
show: true
|
||||
};
|
||||
|
||||
return pageListDict;
|
||||
}
|
||||
|
||||
const postDirectories: pages.PageLocation[] = [
|
||||
// ---------------------------------------------------------------------------
|
||||
// Directory Definitions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* An array of directories to scan for pages.
|
||||
* Each directory is represented as a PageLocation from the `pages` module.
|
||||
*
|
||||
* Note: The 'map' property should correspond to the subdirectory name that
|
||||
* exists within each language folder. For example, if articles for "Site"
|
||||
* are stored in "content/en/site", "content/fr/site", etc., then map: "site".
|
||||
*/
|
||||
export const postDirectories: pages.PageLocation[] = [
|
||||
{
|
||||
title: "Site",
|
||||
description: "Articles to test site functionality",
|
||||
tags: ["site"],
|
||||
map: "site",
|
||||
root: "content/site"
|
||||
root: "", // Not used in the new structure
|
||||
show: false
|
||||
},
|
||||
{
|
||||
title: "Collections",
|
||||
description: "Articles that are collections of information: Lists, Awesome lists, etc.",
|
||||
description:
|
||||
"Articles that are collections of information: Lists, Awesome lists, etc.",
|
||||
tags: ["collection"],
|
||||
map: "collections",
|
||||
root: "content/collections"
|
||||
root: "",
|
||||
show: true
|
||||
},
|
||||
{
|
||||
title: "Guides",
|
||||
description: "Guides and tutorials",
|
||||
tags: ["guide"],
|
||||
map: "guides",
|
||||
root: "content/guides"
|
||||
}
|
||||
]
|
||||
|
||||
var postList: PageList = {
|
||||
last_generated: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
||||
categories: {}
|
||||
}
|
||||
|
||||
for (const postDirectory of postDirectories) {
|
||||
const pagesInfo = pages.getPagesInfo("", postDirectory);
|
||||
postList.categories[postDirectory.title] = generatePageCategory(pagesInfo);
|
||||
postList.categories[postDirectory.title].title = postDirectory.title;
|
||||
postList.categories[postDirectory.title].description = postDirectory.description;
|
||||
postList.categories[postDirectory.title].tags = postDirectory.tags;
|
||||
}
|
||||
|
||||
// Sort the posts by date
|
||||
for (const category of Object.values(postList.categories)) {
|
||||
category.posts.sort((a, b) => {
|
||||
if (!a.metadata.date) {
|
||||
return 1;
|
||||
}
|
||||
if (!b.metadata.date) {
|
||||
return -1;
|
||||
}
|
||||
return new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime();
|
||||
});
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(postList, null, 2));
|
||||
|
||||
// Output to assets/blog_list.json (overwriting)
|
||||
fs.writeFileSync("assets/meta/post_list.json", JSON.stringify(postList, null, 2));
|
||||
root: "",
|
||||
show: true
|
||||
},
|
||||
];
|
Loading…
Add table
Add a link
Reference in a new issue