Asciidoc rendering in backend

This commit is contained in:
Daniel Kluge 2022-11-03 14:49:32 +01:00
parent 55d57d6a80
commit 652d84e296
6 changed files with 92 additions and 36 deletions

View File

@ -17,6 +17,20 @@ hljs.registerLanguage("bash", bash);
hljs.registerLanguage("console", bash); hljs.registerLanguage("console", bash);
hljs.registerLanguage("shell", bash); hljs.registerLanguage("shell", bash);
interface APISuccess {
type: "success";
html: string;
date: string;
repoUrl: string;
}
interface APIError {
type: "error";
html: string;
}
export type APIReturn = APISuccess | APIError;
export const projectEmpty = "<div>Kein Projekt ausgewählt.</div>"; export const projectEmpty = "<div>Kein Projekt ausgewählt.</div>";
const projectNotFoundHtml = `<div class="${"error"}">Sorry! There is no data for this project. Please check back later to see if that changed!</div>`; const projectNotFoundHtml = `<div class="${"error"}">Sorry! There is no data for this project. Please check back later to see if that changed!</div>`;
const projectServerErrorHtml = `<div class="${"error"}">Sorry! A server error happend when the project data was fetched!</div>`; const projectServerErrorHtml = `<div class="${"error"}">Sorry! A server error happend when the project data was fetched!</div>`;
@ -48,18 +62,19 @@ export async function getContentList() {
} }
} }
export async function generateContent(content: Project|Diary, selectedPage?: number): Promise<string> { export async function generateContent(content: Project|Diary, selectedPage?: number, api: boolean = false): Promise<string|APIReturn> {
if(!content) return projectEmpty; if(!content) return api ? {type: "error", html: projectEmpty} : projectEmpty;
switch (content.type) { switch (content.type) {
case "project": return await generateProjectHTML(content); case "project": return await generateProjectHTML(content, api);
case "diary": return await generateDiaryHTML(content, selectedPage); case "diary": return await generateDiaryHTML(content, selectedPage, api);
default: return projectNotFoundHtml; default: return projectNotFoundHtml;
} }
} }
async function generateProjectHTML(project: Project): Promise<string> { async function generateProjectHTML(project: Project, api: boolean = false): Promise<string|APIReturn> {
// First we test if the file exist // First we test if the file exist
if(!projectFiles.find((f) => f.name === `${project.name}.adoc`)) return projectNotFoundHtml; if(!projectFiles.find((f) => f.name === `${project.name}.adoc`)) return api ? { type: "error", html: projectNotFoundHtml } : projectNotFoundHtml;
// Resolve the path // Resolve the path
const path = resolve(projectPath, `${project.name}.adoc`); const path = resolve(projectPath, `${project.name}.adoc`);
@ -72,8 +87,19 @@ async function generateProjectHTML(project: Project): Promise<string> {
const pathsCorrected = rawAd.replace(/(image[:]+)(.*\.[a-zA-Z]+)\[/g, "$1/content/projects/$2["); const pathsCorrected = rawAd.replace(/(image[:]+)(.*\.[a-zA-Z]+)\[/g, "$1/content/projects/$2[");
const adDoc = ad.load(pathsCorrected, { attributes: { showtitle: true } }); const adDoc = ad.load(pathsCorrected, { attributes: { showtitle: true } });
// Convert to HTML
const converted = adDoc.convert(adDoc).toString();
// For the API we want the HTML and the date only
if (api) return {
type: "success",
html: converted,
date: new Date(adDoc.getAttribute("docdatetime")).toISOString(),
repoUrl: `https://git.c0ntroller.de/c0ntroller/frontpage-content/src/branch/${process.env.IS_DEV ? "dev" : "senpai"}/projects/${project.name}.adoc`,
};
// Return and add the footer // Return and add the footer
return `${adDoc.convert(adDoc).toString()} return `${converted}
<hr> <hr>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
@ -83,18 +109,18 @@ async function generateProjectHTML(project: Project): Promise<string> {
} catch (e) { } catch (e) {
// Something gone wrong // Something gone wrong
console.error(e); console.error(e);
return projectServerErrorHtml; return api ? { type: "error", html: projectServerErrorHtml } : projectServerErrorHtml;
} }
} }
async function generateDiaryHTML(diary: Diary, selectedPage?: number): Promise<string> { async function generateDiaryHTML(diary: Diary, selectedPage?: number, api: boolean = false): Promise<string|APIReturn> {
// First we test if the file exist // First we test if the file exist
if(!diaryFiles.find((f) => f.isFile() && f.name === `${diary.name}.adoc`)) return projectNotFoundHtml; if(!diaryFiles.find((f) => f.isFile() && f.name === `${diary.name}.adoc`)) return api ? { type: "error", html: projectNotFoundHtml } : projectNotFoundHtml;
// First we need the page number and the path to load // First we need the page number and the path to load
const page: number = Number.parseInt(selectedPage?.toString() || "0") - 1; const page: number = Number.parseInt(selectedPage?.toString() || "0") - 1;
// If the page number is not -1, a directory must exist // If the page number is not -1, a directory must exist
if (page !== -1 && !diaryFiles.find((f) => f.isDirectory() && f.name === diary.name)) return projectNotFoundHtml; if (page !== -1 && !diaryFiles.find((f) => f.isDirectory() && f.name === diary.name)) return api ? { type: "error", html: projectNotFoundHtml } : projectNotFoundHtml;
// Next we load the correct path // Next we load the correct path
const path = page === -1 ? resolve(diaryPath, `${diary.name}.adoc`) : resolve(diaryPath, diary.name, `${diary.entries[page].filename}.adoc`); const path = page === -1 ? resolve(diaryPath, `${diary.name}.adoc`) : resolve(diaryPath, diary.name, `${diary.entries[page].filename}.adoc`);
@ -107,8 +133,20 @@ async function generateDiaryHTML(diary: Diary, selectedPage?: number): Promise<s
const pathsCorrected = rawAd.replace(/(image[:]{1,2})(.*\.[a-zA-Z]+)\[/g, "$1/content/diaries/$2["); const pathsCorrected = rawAd.replace(/(image[:]{1,2})(.*\.[a-zA-Z]+)\[/g, "$1/content/diaries/$2[");
const adDoc = ad.load(pathsCorrected, { attributes: { showtitle: true } }); const adDoc = ad.load(pathsCorrected, { attributes: { showtitle: true } });
const gitfile = page === -1 ? `${diary.name}.adoc` : `${diary.name}/${diary.entries[page].filename}.adoc`; const gitfile = page === -1 ? `${diary.name}.adoc` : `${diary.name}/${diary.entries[page].filename}.adoc`;
// Convert to HTML
const converted = adDoc.convert(adDoc).toString();
// For the API we want the HTML and the date only
if (api) return {
type: "success",
html: converted,
date: new Date(adDoc.getAttribute("docdatetime")).toISOString(),
repoUrl: `https://git.c0ntroller.de/c0ntroller/frontpage-content/src/branch/${process.env.IS_DEV ? "dev" : "senpai"}/diaries/${gitfile}`,
};
// Return and add the footer // Return and add the footer
return `${adDoc.convert(adDoc).toString()} return `${converted}
<hr> <hr>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
@ -118,7 +156,7 @@ async function generateDiaryHTML(diary: Diary, selectedPage?: number): Promise<s
} catch (e) { } catch (e) {
// Something gone wrong // Something gone wrong
console.error(e); console.error(e);
return projectServerErrorHtml; return api ? { type: "error", html: projectServerErrorHtml } : projectServerErrorHtml;
} }
} }

View File

@ -2,16 +2,12 @@
// It is used by the terminal. // It is used by the terminal.
import type { Project, Diary } from "./types"; import type { Project, Diary } from "./types";
import asciidoctor from "asciidoctor"; import type { APIReturn } from "./generateBackend";
export const projectEmpty = "<div>Kein Projekt ausgewählt.</div>"; export const projectEmpty = "<div>Kein Projekt ausgewählt.</div>";
const projectNotFoundHtml = `<div class="${"error"}">Sorry! There is no data for this project. Please check back later to see if that changed!</div>`; const projectNotFoundHtml = `<div class="${"error"}">Sorry! There is no data for this project. Please check back later to see if that changed!</div>`;
const projectServerErrorHtml = `<div class="${"error"}">Sorry! A server error happend when the project data was fetched!</div>`; const projectServerErrorHtml = `<div class="${"error"}">Sorry! A server error happend when the project data was fetched!</div>`;
const ad = asciidoctor();
const isDev = typeof window !== "undefined" ? window.location.host.startsWith("dev") : false;
export async function generateContent(content: Project|Diary, selectedPage?: number): Promise<string> { export async function generateContent(content: Project|Diary, selectedPage?: number): Promise<string> {
if(!content) return projectEmpty; if(!content) return projectEmpty;
switch (content.type) { switch (content.type) {
@ -22,33 +18,37 @@ export async function generateContent(content: Project|Diary, selectedPage?: num
} }
async function generateProjectHTML(project: Project): Promise<string> { async function generateProjectHTML(project: Project): Promise<string> {
const resp = await fetch(`/content/projects/${project.name}.adoc`); const resp = await fetch(`/api/contentRendering?name=${project.name}`);
if (resp.status !== 200) return projectServerErrorHtml; if (resp.status !== 200) return projectServerErrorHtml;
const rawAd = await resp.text(); const response = await resp.json() as APIReturn;
const pathsCorrected = rawAd.replace(/(image[:]+)(.*\.[a-zA-Z]+)\[/g, "$1/content/projects/$2[");
const adDoc = ad.load(pathsCorrected, { attributes: { showtitle: true } }); if (!response || !response.type) return projectServerErrorHtml;
return `${adDoc.convert(adDoc).toString()} if (response.type === "error") return response.html;
else {
return `${response.html}
<hr> <hr>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated: ${new Date(adDoc.getAttribute("docdatetime")).toLocaleString()} | <a href="https://git.c0ntroller.de/c0ntroller/frontpage-content/src/branch/${isDev ? "dev" : "senpai"}/projects/${project.name}.adoc" target="_blank">Document source</a> Last updated: ${new Date(response.date).toLocaleString()} | <a href="${response.repoUrl}" target="_blank">Document source</a>
</div> </div>
</div>`; </div>`;
}
} }
async function generateDiaryHTML(diary: Diary, selectedPage?: number): Promise<string> { async function generateDiaryHTML(diary: Diary, selectedPage?: number): Promise<string> {
const page: number = Number.parseInt(selectedPage?.toString() || "0") - 1; const url = `/api/contentRendering?name=${diary.name}${selectedPage ? `&page=${selectedPage}` : ""}`;
const resp = page === -1 ? await fetch(`/content/diaries/${diary.name}.adoc`) : await fetch(`/content/diaries/${diary.name}/${diary.entries[page].filename}.adoc`); const resp = await fetch(url);
if (resp.status !== 200) return projectServerErrorHtml; const response = await resp.json() as APIReturn;
const rawAd = await resp.text();
const pathsCorrected = rawAd.replace(/(image[:]{1,2})(.*\.[a-zA-Z]+)\[/g, "$1/content/diaries/$2["); if (!response || !response.type) return projectServerErrorHtml;
const adDoc = ad.load(pathsCorrected, { attributes: { showtitle: true } }); if (response.type === "error") return response.html;
const gitfile = page === -1 ? `${diary.name}.adoc` : `${diary.name}/${diary.entries[page].filename}.adoc`; return `${response.html}
return `${adDoc.convert(adDoc).toString()}
<hr> <hr>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated: ${new Date(adDoc.getAttribute("docdatetime")).toLocaleString()} | <a href="https://git.c0ntroller.de/c0ntroller/frontpage-content/src/branch/${isDev ? "dev" : "senpai"}/diaries/${gitfile}" target="_blank">Document source</a> Last updated: ${new Date(response.date).toLocaleString()} | <a href="${response.repoUrl}" target="_blank">Document source</a>
</div> </div>
</div>`; </div>`;
} }

View File

@ -0,0 +1,18 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { generateContent, getContentList } from "../../lib/content/generateBackend";
import type { APIReturn } from "../../lib/content/generateBackend";
import type { ContentList, Diary, Project } from "../../lib/content/types";
export default async function handler(req: NextApiRequest, res: NextApiResponse<string>) {
if (!req.query || !req.query.name) return res.status(400).end();
const list: ContentList = await getContentList();
if (!list) return res.status(500).end();
const content: Project | Diary | undefined = list.find((c) => c.name === req.query.name);
if (!content) return res.status(404).end();
const rendered = await generateContent(content, req.query.page ? parseInt(req.query.page as string) : undefined, true) as APIReturn;
res.status(200).json(JSON.stringify(rendered));
res.end();
}

View File

@ -18,7 +18,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
if (!contentEntry) return { notFound: true }; if (!contentEntry) return { notFound: true };
const contentHtml = await generateContent(contentEntry); const contentHtml = await generateContent(contentEntry) as string;
const contentPrepared = prepareDOM(contentHtml); const contentPrepared = prepareDOM(contentHtml);
return { return {

View File

@ -18,7 +18,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
if (!contentEntry || !page || typeof page !== "string") return { notFound: true }; if (!contentEntry || !page || typeof page !== "string") return { notFound: true };
const contentHtml = await generateContent(contentEntry, Number.parseInt(page)); const contentHtml = await generateContent(contentEntry, Number.parseInt(page)) as string;
const contentPrepared = prepareDOM(contentHtml); const contentPrepared = prepareDOM(contentHtml);
return { return {

View File

@ -19,7 +19,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
if (!contentEntry) return { notFound: true }; if (!contentEntry) return { notFound: true };
const contentHtml = await generateContent(contentEntry); const contentHtml = await generateContent(contentEntry) as string;
const contentPrepared = prepareDOM(contentHtml); const contentPrepared = prepareDOM(contentHtml);
return { return {