mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-08-05 05:25:27 +02:00
implement basic static renderer
This commit is contained in:
parent
f28116a567
commit
2663931f2a
6 changed files with 149 additions and 52 deletions
|
@ -9,6 +9,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "concurrently \"yarn start-backend\" \"yarn build-frontend\"",
|
"start": "concurrently \"yarn start-backend\" \"yarn build-frontend\"",
|
||||||
"dev": "concurrently \"yarn start-backend\" \"yarn build-frontend:dev\"",
|
"dev": "concurrently \"yarn start-backend\" \"yarn build-frontend:dev\"",
|
||||||
|
"build-static": "ts-node src/backend/build-static.ts",
|
||||||
"start-backend": "cross-env NODE_ENV=development npx nodemon --config nodemon.json src/backend/server.ts -c app-config.yaml -c app-config.local.yaml",
|
"start-backend": "cross-env NODE_ENV=development npx nodemon --config nodemon.json src/backend/server.ts -c app-config.yaml -c app-config.local.yaml",
|
||||||
"compile": "tsc && copyfiles -u 3 ./src/**/*.twig ./dist/backend/views && copyfiles -u 1 ./src/**/*.svg ./dist/",
|
"compile": "tsc && copyfiles -u 3 ./src/**/*.twig ./dist/backend/views && copyfiles -u 1 ./src/**/*.svg ./dist/",
|
||||||
"build-frontend": "webpack --mode=production",
|
"build-frontend": "webpack --mode=production",
|
||||||
|
|
94
src/backend/build-static.ts
Normal file
94
src/backend/build-static.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import twig from 'twig';
|
||||||
|
import Page from './models/page.js';
|
||||||
|
import PagesFlatArray from './models/pagesFlatArray.js';
|
||||||
|
import appConfig from './utils/appConfig.js';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import('./utils/twig.js');
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import mkdirp from 'mkdirp';
|
||||||
|
import { createMenuTree } from './utils/menu.js';
|
||||||
|
import { EntityId } from './database/types.js';
|
||||||
|
import PagesOrder from './controllers/pagesOrder.js';
|
||||||
|
|
||||||
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const distPath = path.resolve(cwd, 'dist');
|
||||||
|
const pagesOrder = await PagesOrder.getAll();
|
||||||
|
const allPages = await Page.getAll();
|
||||||
|
|
||||||
|
await mkdirp(distPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render template with twig by path
|
||||||
|
*
|
||||||
|
* @param filePath - path to template
|
||||||
|
* @param data - data to render template
|
||||||
|
*/
|
||||||
|
function renderTemplate(filePath: string, data: Record<string, unknown>): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
twig.renderFile(path.resolve(dirname, filePath), data, (err, html) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
resolve(html);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders single page
|
||||||
|
*
|
||||||
|
* @param page - page to render
|
||||||
|
*/
|
||||||
|
async function renderPage(page: Page): Promise<void> {
|
||||||
|
console.log(`Rendering page ${page.uri}`);
|
||||||
|
const pageParent = await page.getParent();
|
||||||
|
const pageId = page._id;
|
||||||
|
|
||||||
|
if (!pageId) {
|
||||||
|
throw new Error('Page id is not defined');
|
||||||
|
}
|
||||||
|
const parentIdOfRootPages = '0' as EntityId;
|
||||||
|
const previousPage = await PagesFlatArray.getPageBefore(pageId);
|
||||||
|
const nextPage = await PagesFlatArray.getPageAfter(pageId);
|
||||||
|
const menu = createMenuTree(parentIdOfRootPages, allPages, pagesOrder, 2);
|
||||||
|
const result = await renderTemplate('./views/pages/page.twig', {
|
||||||
|
page,
|
||||||
|
pageParent,
|
||||||
|
previousPage,
|
||||||
|
nextPage,
|
||||||
|
menu,
|
||||||
|
config: appConfig.frontend,
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(result);
|
||||||
|
const filename = page.uri === '' ? 'index.html' : `${page.uri}.html`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.resolve(distPath, filename), result);
|
||||||
|
console.log(`Page ${page.uri} rendered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render all pages
|
||||||
|
*/
|
||||||
|
for (const page of allPages) {
|
||||||
|
await renderPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render index page
|
||||||
|
*/
|
||||||
|
async function renderIndexPage(): Promise<void> {
|
||||||
|
console.log('Rendering index page');
|
||||||
|
const result = await renderTemplate('./views/pages/index.twig', {
|
||||||
|
config: appConfig.frontend,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filename = 'index.html';
|
||||||
|
|
||||||
|
await fs.writeFile(path.resolve(distPath, filename), result);
|
||||||
|
console.log('Index page rendered');
|
||||||
|
}
|
||||||
|
|
||||||
|
await renderIndexPage();
|
|
@ -1,57 +1,10 @@
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import Pages from '../../controllers/pages.js';
|
import Pages from '../../controllers/pages.js';
|
||||||
import PagesOrder from '../../controllers/pagesOrder.js';
|
import PagesOrder from '../../controllers/pagesOrder.js';
|
||||||
import Page from '../../models/page.js';
|
|
||||||
import asyncMiddleware from '../../utils/asyncMiddleware.js';
|
import asyncMiddleware from '../../utils/asyncMiddleware.js';
|
||||||
import PageOrder from '../../models/pageOrder.js';
|
|
||||||
import { EntityId } from '../../database/types.js';
|
import { EntityId } from '../../database/types.js';
|
||||||
import { isEqualIds } from '../../database/index.js';
|
import { createMenuTree } from '../../utils/menu.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* Process one-level pages list to parent-children list
|
|
||||||
*
|
|
||||||
* @param {string} parentPageId - parent page id
|
|
||||||
* @param {Page[]} pages - list of all available pages
|
|
||||||
* @param {PagesOrder[]} pagesOrder - list of pages order
|
|
||||||
* @param {number} level - max level recursion
|
|
||||||
* @param {number} currentLevel - current level of element
|
|
||||||
* @returns {Page[]}
|
|
||||||
*/
|
|
||||||
function createMenuTree(parentPageId: EntityId, pages: Page[], pagesOrder: PageOrder[], level = 1, currentLevel = 1): Page[] {
|
|
||||||
const childrenOrder = pagesOrder.find(order => isEqualIds(order.data.page, parentPageId));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* branch is a page children in tree
|
|
||||||
* if we got some children order on parents tree, then we push found pages in order sequence
|
|
||||||
* otherwise just find all pages includes parent tree
|
|
||||||
*/
|
|
||||||
let ordered: any[] = [];
|
|
||||||
|
|
||||||
if (childrenOrder) {
|
|
||||||
ordered = childrenOrder.order.map((pageId: EntityId) => {
|
|
||||||
return pages.find(page => isEqualIds(page._id, pageId));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const unordered = pages.filter(page => isEqualIds(page._parent, parentPageId));
|
|
||||||
const branch = Array.from(new Set([...ordered, ...unordered]));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* stop recursion when we got the passed max level
|
|
||||||
*/
|
|
||||||
if (currentLevel === level + 1) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each parents children can have subbranches
|
|
||||||
*/
|
|
||||||
return branch.filter(page => page && page._id).map(page => {
|
|
||||||
return Object.assign({
|
|
||||||
children: createMenuTree(page._id, pages, pagesOrder, level, currentLevel + 1),
|
|
||||||
}, page.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware for all /page/... routes
|
* Middleware for all /page/... routes
|
||||||
|
|
49
src/backend/utils/menu.ts
Normal file
49
src/backend/utils/menu.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { EntityId } from '../database/types.js';
|
||||||
|
import Page from '../models/page.js';
|
||||||
|
import PageOrder from '../models/pageOrder.js';
|
||||||
|
import { isEqualIds } from '../database/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process one-level pages list to parent-children list
|
||||||
|
*
|
||||||
|
* @param parentPageId - parent page id
|
||||||
|
* @param pages - list of all available pages
|
||||||
|
* @param pagesOrder - list of pages order
|
||||||
|
* @param level - max level recursion
|
||||||
|
* @param currentLevel - current level of element
|
||||||
|
*/
|
||||||
|
export function createMenuTree(parentPageId: EntityId, pages: Page[], pagesOrder: PageOrder[], level = 1, currentLevel = 1): Page[] {
|
||||||
|
const childrenOrder = pagesOrder.find(order => isEqualIds(order.data.page, parentPageId));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* branch is a page children in tree
|
||||||
|
* if we got some children order on parents tree, then we push found pages in order sequence
|
||||||
|
* otherwise just find all pages includes parent tree
|
||||||
|
*/
|
||||||
|
let ordered: any[] = [];
|
||||||
|
|
||||||
|
if (childrenOrder) {
|
||||||
|
ordered = childrenOrder.order.map((pageId: EntityId) => {
|
||||||
|
return pages.find(page => isEqualIds(page._id, pageId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const unordered = pages.filter(page => isEqualIds(page._parent, parentPageId));
|
||||||
|
const branch = Array.from(new Set([...ordered, ...unordered]));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stop recursion when we got the passed max level
|
||||||
|
*/
|
||||||
|
if (currentLevel === level + 1) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each parents children can have subbranches
|
||||||
|
*/
|
||||||
|
return branch.filter(page => page && page._id).map(page => {
|
||||||
|
return Object.assign({
|
||||||
|
children: createMenuTree(page._id, pages, pagesOrder, level, currentLevel + 1),
|
||||||
|
}, page.data);
|
||||||
|
});
|
||||||
|
}
|
|
@ -12,13 +12,13 @@
|
||||||
<script>
|
<script>
|
||||||
</script>
|
</script>
|
||||||
<body class="greeting-body">
|
<body class="greeting-body">
|
||||||
{% include "components/header.twig" %}
|
{% include "../components/header.twig" %}
|
||||||
<div class="greeting-content">
|
<div class="greeting-content">
|
||||||
{{ svg('frog') }}
|
{{ svg('frog') }}
|
||||||
<p class="greeting-content__message">
|
<p class="greeting-content__message">
|
||||||
It’s time to create the first page!
|
It’s time to create the first page!
|
||||||
</p>
|
</p>
|
||||||
{% include 'components/button.twig' with {label: 'Add page', icon: 'plus', size: 'small', url: '/page/new'} %}
|
{% include '../components/button.twig' with {label: 'Add page', icon: 'plus', size: 'small', url: '/page/new'} %}
|
||||||
</div>
|
</div>
|
||||||
{% if config.yandexMetrikaId is not empty %}
|
{% if config.yandexMetrikaId is not empty %}
|
||||||
<script type="text/javascript" >
|
<script type="text/javascript" >
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'layout.twig' %}
|
{% extends '../layout.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<article class="page" data-module="page">
|
<article class="page" data-module="page">
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
{% include 'components/navigator.twig' with {previousPage: previousPage, nextPage: nextPage} %}
|
{% include '../components/navigator.twig' with {previousPage: previousPage, nextPage: nextPage} %}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue