diff --git a/src/client/navigation.ts b/src/client/navigation.ts
index c3abcff..fb8985a 100644
--- a/src/client/navigation.ts
+++ b/src/client/navigation.ts
@@ -1,4 +1,4 @@
-import { MetaGame, Menu } from '../common/types';
+import { MetaGame, Menu, RenderedPage } from '../common/types';
import { Slug } from '../common/slug';
import { clearNotices, notify } from './notices';
import { anchorHeaderFix, addAnchorLinks } from './anchors';
@@ -94,9 +94,9 @@ async function navigate(slug, replace = false, loadData = true) {
return;
}
- const data = await req.json().catch(() => {
+ const data: RenderedPage = (await req.json().catch(() => {
throw new Error('Error parsing page data');
- });
+ })) as RenderedPage;
document.querySelector('#content').innerHTML = data.content;
console.log('NAV RESULT', data);
@@ -125,7 +125,7 @@ async function navigate(slug, replace = false, loadData = true) {
if (showExclusiveNotice) {
notify(
- `This page contains sections that are irrelevant to your selected game. If you're missing a section, consider changing your game.`,
+ `This page contains sections that are irrelevant to your selected game. If you're missing a section, consider changing your game.`,
'eye-off'
);
}
@@ -150,7 +150,7 @@ async function navigate(slug, replace = false, loadData = true) {
document.querySelector('html').className = 'theme-' + info.game;
- document.title = `${data.title || 'Page not found'} - ${games[info.game].name} Wiki`;
+ document.title = `${data.meta.title || 'Page not found'} - ${games[info.game].name} Wiki`;
document.querySelector('#current-game').innerText = games[info.game].name;
document.querySelector('link[rel=icon]').href = games[info.game].favicon || games[info.game].icon;
@@ -159,10 +159,10 @@ async function navigate(slug, replace = false, loadData = true) {
document.querySelector('.top-nav .game a').href = `/${info.game}`;
- if (loadData || data.file) {
+ if (loadData || data.path) {
// Update the edit button to reflect our current page
document.querySelector('.edit a').href = `https://github.com/StrataSource/Wiki/edit/main/${
- data.file || '404.md'
+ data.path || '404.md'
}`;
}
diff --git a/src/common/types.ts b/src/common/types.ts
index 2404542..a7a4929 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -34,40 +34,22 @@ export interface MetaGame {
features: string[];
}
-export interface Article {
- id: string;
- content: HTMLString;
- name: string;
- slug: Slug;
- file: string;
- meta: PageMeta;
-}
-
export interface PageMeta {
title?: string;
features?: string[];
+ example?: string;
}
export interface RenderedPage {
+ path: string;
content: HTMLString;
meta: PageMeta;
}
-export interface Index {
- [gameID: string]: {
- id: string;
- categories: {
- [categoryID: string]: {
- topics: {
- [topicID: string]: {
- articles: {
- [articleID: string]: Article;
- };
- };
- };
- };
- };
- };
+export interface Article {
+ id: string;
+ slug: Slug;
+ page: RenderedPage;
}
export interface MenuArticle {
diff --git a/src/exporter/export.ts b/src/exporter/export.ts
index 7e252cb..051a0b7 100644
--- a/src/exporter/export.ts
+++ b/src/exporter/export.ts
@@ -1,43 +1,16 @@
import fs from 'fs-extra';
+
import { PageHandler } from './pages';
import { Templater } from './template';
-import { Renderer } from './render';
import { Slug } from '../common/slug';
-import { MetaGame } from '../common/types';
+import { MetaGame, Article, HTMLString } from '../common/types';
export class Exporter {
pageHandler: PageHandler;
- templater: Templater;
- renderer: Renderer;
games: MetaGame[];
constructor() {
- this.pageHandler = new PageHandler(this);
- this.templater = new Templater();
- this.renderer = new Renderer();
-
- // Read the pages folder, anything with a meta.json is a "game"
- this.games = fs
- .readdirSync('../pages')
- .filter((game) => fs.existsSync(`../pages/${game}/meta.json`))
- .map(
- (game) =>
- ({
- ...fs.readJSONSync(`../pages/${game}/meta.json`),
- id: game
- } as MetaGame)
- );
-
- // For each game, register up a handler for its game exclusive block
- for (const game of this.games) {
- this.renderer.registerGame(game.id, game.nameShort || game.name || game.id);
- }
-
- // For each game, cache all articles
- console.log('Caching articles...');
- for (const game of this.games) {
- this.pageHandler.cacheArticles(game.id);
- }
+ this.pageHandler = new PageHandler();
}
export() {
@@ -52,28 +25,20 @@ export class Exporter {
step();
this.clean();
-
- step();
this.copyAssets();
-
- step();
this.copyResources();
step();
- this.templater.generateNav(this.games);
+ this.findGameMeta();
+ this.copyGameMeta();
step();
this.pageHandler.buildIndex(this.games);
+ fs.writeFileSync('public/ajax/menu.json', JSON.stringify(this.pageHandler.menu));
step();
this.saveAllPages();
- step();
- this.generateSpecialPages();
-
- step();
- this.copyGameMeta();
-
const endTime = performance.now();
console.log('Done!');
@@ -100,44 +65,87 @@ export class Exporter {
fs.copySync('static', 'public');
}
- saveAllPages() {
- for (const gameMeta of this.games)
- for (const category of Object.values(this.pageHandler.index[gameMeta.id].categories))
- for (const topic of Object.values(category.topics))
- for (const article of Object.values(topic.articles)) {
- this.pageHandler.savePage(gameMeta, article);
- }
+ findGameMeta() {
+ // Read the pages folder, anything with a meta.json is a "game"
+ this.games = fs
+ .readdirSync('../pages')
+ .filter((game) => fs.existsSync(`../pages/${game}/meta.json`))
+ .map(
+ (game) =>
+ ({
+ ...fs.readJSONSync(`../pages/${game}/meta.json`),
+ id: game
+ } as MetaGame)
+ );
}
copyGameMeta() {
+ // Copy over the game meta data
const games = {};
- for (const game of this.games) {
- games[game.id] = game;
- }
-
+ for (const game of this.games) games[game.id] = game;
fs.writeFileSync('public/ajax/games.json', JSON.stringify(games));
}
- generateSpecialPages() {
- for (const game of this.games) {
- const content = this.renderer.renderPage(`../pages/${game.id}/index.md`);
+ /**
+ * Saves an article to the right directories
+ * @param templater The target template
+ * @param metaGame The game meta data
+ * @param article The article
+ */
+ savePage(templater: Templater, metaGame: MetaGame, article: Article): void {
+ const path = article.slug.toString().split('/').slice(0, -1).join('/');
+
+ // Write article JSON meta to file
+ fs.mkdirSync('public/ajax/article/' + path, { recursive: true });
+ fs.writeFileSync('public/ajax/article/' + path + '/' + article.id + '.json', JSON.stringify(article.page));
+
+ // Generate HTML content
+ const content = templater.applyTemplate({
+ metaGame: metaGame,
+ html: article.page.content,
+ title: article.page.meta.title || article.id,
+ menuTopics: this.pageHandler.menu[metaGame.id][article.slug.category]
+ });
+
+ // Writing HTML to file
+ // console.log('public/' + path);
+ fs.mkdirSync('public/' + path, { recursive: true });
+ fs.writeFileSync('public/' + path + '/' + article.id + '.html', content);
+ }
- this.pageHandler.savePage(game, {
- ...content,
+ saveAllPages() {
+ // Read template HTML
+ const templateMain: HTMLString = fs.readFileSync('templates/main.html', 'utf8');
+ const template404: HTMLString = fs.readFileSync('templates/404.html', 'utf8');
+
+ // Create the templater for articles
+ const templater = new Templater(templateMain);
+
+ // Apply templates for all pages
+ for (const game of this.games) {
+ // Generate the nav bar links
+ templater.generateNav(game);
+
+ // Save the Home page
+ const content = this.pageHandler.articleCache[game.id]['index'];
+ content.meta.title = 'Home';
+ this.savePage(templater, game, {
+ id: 'index',
slug: new Slug(game.id),
- name: content.meta.title || 'Home',
- file: `/pages/${game.id}/index.md`,
- id: 'index'
+ page: content
});
- this.pageHandler.savePage(game, {
- ...content,
- slug: new Slug(game.id),
- name: 'Home',
- file: '',
+ // Save the 404 page
+ this.savePage(templater, game, {
id: '404',
- content: fs.readFileSync('templates/404.html', 'utf8')
+ slug: new Slug(game.id),
+ page: { content: template404, meta: { title: '404' }, path: '' }
});
+
+ // Save all articles
+ for (const article of this.pageHandler.gameArticles[game.id]) {
+ this.savePage(templater, game, article);
+ }
}
}
}
diff --git a/src/exporter/pages.ts b/src/exporter/pages.ts
index 1034421..e012824 100644
--- a/src/exporter/pages.ts
+++ b/src/exporter/pages.ts
@@ -1,19 +1,22 @@
-import { Slug } from '../common/slug';
-import { Article, MetaGame, Index, Menu, MenuArticle, MenuTopic, RenderedPage } from '../common/types';
import fs from 'fs-extra';
-import { Exporter } from './export';
+
+import { Slug } from '../common/slug';
+import { Article, MetaGame, Menu, MenuArticle, MenuTopic, RenderedPage } from '../common/types';
+import { Renderer } from './render';
export class PageHandler {
- private exporter: Exporter;
+ renderer: Renderer;
menu: Menu;
- index: Index;
articleCache: { [path: string]: { [article: string]: RenderedPage } };
+ gameArticles: { [game: string]: Article[] };
- constructor(exporter) {
- this.exporter = exporter;
+ constructor() {
+ this.renderer = new Renderer();
+ this.menu = {};
this.articleCache = {};
+ this.gameArticles = {};
}
/**
@@ -43,39 +46,41 @@ export class PageHandler {
const articleID = articleName.slice(0, ext);
// Render and cache it!
- const result = this.exporter.renderer.renderPage(topicPath + '/' + articleName);
+ const result = this.renderer.renderPage(topicPath + '/' + articleName);
this.articleCache[topicDir][articleID] = result;
}
}
buildIndex(games: MetaGame[]): void {
- const index: Index = {};
- const menu: Menu = {};
+ // For each game, register up a handler for its game exclusive block
+ for (const game of games) {
+ this.renderer.registerGame(game.id, game.nameShort || game.name || game.id);
+ }
- // For each game, for each category, for each topic, render all articles
+ // For each game, cache all articles
+ console.log('Caching articles...');
for (const game of games) {
- index[game.id] = {
- id: game.id,
- categories: {}
- };
+ this.cacheArticles(game.id);
+ }
- menu[game.id] = {};
+ // For each game, for each category, for each topic, build a list of articles
+ console.log('Building article index...');
+ for (const game of games) {
+ this.gameArticles[game.id] = [];
+ this.menu[game.id] = {};
for (const category of game.categories) {
- // Insert the category into the index
- index[game.id].categories[category.id] = {
- topics: {}
- };
-
// If this category is just a redirect, we don't need to render any pages
if (category.redirect) continue;
// It's a normal category, so we'll need to fill in its topics
const menuTopics: MenuTopic[] = [];
- menu[game.id][category.id] = menuTopics;
+ this.menu[game.id][category.id] = menuTopics;
for (const topic of category.topics) {
+ // If the topic lacks a specific path, we'll use its id
if (!topic.path) topic.path = topic.id;
+ // All articles on the topic
// Array of tuples of [article, page]
const articles: [string, RenderedPage][] = [];
@@ -87,7 +92,7 @@ export class PageHandler {
continue;
}
- // Pull in the articles we want from the topic
+ // Pull in only the articles we want from the topic
const topic = this.articleCache[path];
for (const articleFile of Object.keys(topic)) {
const page = topic[articleFile];
@@ -101,17 +106,14 @@ export class PageHandler {
)
continue;
- // Push the parent and the name
+ // Push the article
articles.push([articleFile, page]);
}
}
+ // At this point, the topic should have articles
if (articles.length === 0) throw new Error(`Could not locate articles: ${game.id}/${topic.path}/`);
- index[game.id].categories[category.id].topics[topic.id] = {
- articles: {}
- };
-
// Add topic to menu
const articleList: MenuArticle[] = [];
const menuTopic: MenuTopic = {
@@ -128,16 +130,12 @@ export class PageHandler {
const slug = new Slug(game.id, category.id, topic.id, articleID);
- const article: Article = {
+ // Add article to index
+ this.gameArticles[game.id].push({
id: articleID,
- content: page.content,
- name: meta.title || articleID,
slug: slug,
- meta: meta
- };
-
- // Add article to index
- index[game.id].categories[category.id].topics[topic.id].articles[articleID] = article;
+ page: page
+ });
// Add the article to menu
const entry: MenuArticle = {
@@ -153,41 +151,10 @@ export class PageHandler {
}
// Add to collection of all articles
- console.log(`Pushed article ${article.id}`);
+ // console.log(`Pushed article ${articleID}`);
}
}
}
}
-
- fs.writeFileSync('public/ajax/menu.json', JSON.stringify(menu));
-
- this.menu = menu;
- this.index = index;
- }
-
- /**
- * Saves an article to the right directories
- * @param {Object} article The article object
- */
- savePage(metaGame: MetaGame, article: Article): void {
- const path = article.slug.toString().split('/').slice(0, -1).join('/');
-
- // Writing JSON meta to file
- console.log('public/ajax/article/' + path);
- fs.mkdirSync('public/ajax/article/' + path, { recursive: true });
- fs.writeFileSync('public/ajax/article/' + path + '/' + article.id + '.json', JSON.stringify(article));
-
- // Writing HTML to file
- console.log('public/' + path);
- fs.mkdirSync('public/' + path, { recursive: true });
- fs.writeFileSync(
- 'public/' + path + '/' + article.id + '.html',
- this.exporter.templater.applyTemplate({
- metaGame: metaGame,
- html: article.content,
- title: article.name,
- menuTopics: this.menu[metaGame.id][article.slug.category]
- })
- );
}
}
diff --git a/src/exporter/render.ts b/src/exporter/render.ts
index 7810fd5..f885d16 100644
--- a/src/exporter/render.ts
+++ b/src/exporter/render.ts
@@ -66,6 +66,7 @@ export class Renderer {
render(str: MarkdownString): RenderedPage {
this.tempMetaValue = {};
return {
+ path: '',
content: this.md.render(str),
meta: this.tempMetaValue
};
@@ -78,11 +79,16 @@ export class Renderer {
*/
renderPage(path: string): RenderedPage {
console.log(`Rendering file ${path}`);
-
- return this.render(
+ const page = this.render(
fs.readFileSync(path, {
encoding: 'utf8'
})
);
+
+ // Store paths relative to root directory
+ if (path.startsWith('../')) path = path.slice(3);
+ page.path = path;
+
+ return page;
}
}
diff --git a/src/exporter/template.ts b/src/exporter/template.ts
index aa2d943..fc72ce2 100644
--- a/src/exporter/template.ts
+++ b/src/exporter/template.ts
@@ -1,4 +1,3 @@
-import fs from 'fs-extra';
import { HTMLString, MenuTopic, MetaGame } from '../common/types';
export interface TemplaterArgs {
@@ -10,6 +9,11 @@ export interface TemplaterArgs {
export class Templater {
navs: Record = {};
+ templateContent: HTMLString;
+
+ constructor(templateContent: HTMLString) {
+ this.templateContent = templateContent;
+ }
applyTemplate({ metaGame, html, title, menuTopics }: TemplaterArgs): HTMLString {
const replacers: Record = {};
@@ -36,7 +40,7 @@ export class Templater {
replacers.branch = process.env.CF_PAGES_BRANCH || 'UNAVAILABLE';
// Read template HTML
- let res: HTMLString = fs.readFileSync('templates/main.html', 'utf8');
+ let res: HTMLString = this.templateContent;
// Replacing values from opts in HTML
for (const [key, value] of Object.entries(replacers)) {
@@ -69,17 +73,15 @@ export class Templater {
/**
* Generates the top nav links for all pages
- * @param games Array of all game meta data
+ * @param game Game metadata
*/
- generateNav(games: MetaGame[]) {
- for (const game of games) {
- // For each of the game's categories, shove a link into the nav bar to it
- let str = '';
- for (const category of game.categories) {
- const link = category.redirect || `/${game.id}/${category.id}/${category.home}`;
- str += `${category.label}`;
- }
- this.navs[game.id] = str;
+ generateNav(game: MetaGame) {
+ // For each of the game's categories, shove a link into the nav bar to it
+ let str = '';
+ for (const category of game.categories) {
+ const link = category.redirect || `/${game.id}/${category.id}/${category.home}`;
+ str += `${category.label}`;
}
+ this.navs[game.id] = str;
}
}