ToC is now automaticcaly server-side rendered.
This commit is contained in:
parent
dd376a22af
commit
0f3acaf7c2
|
@ -28,10 +28,10 @@ I can *promise* you that there is no malicious code or dependency in this projec
|
|||
The project depends on the following packages (installed when you ran `npm i`):
|
||||
* express (a web server framework)
|
||||
* nunjucks (a templating engine)
|
||||
* nunjucks-append (an extension that adds more functionality to nunjucks)
|
||||
* nunjucks-markdown (markdown support in nunjucks)
|
||||
* marked (markdown parser/compiler)
|
||||
* highlight.js (syntax highlighting)
|
||||
* jsdom (an extension that can use the DOM server-side - used to render the ToC)
|
||||
* chokidar (reload templates if they change on disk)
|
||||
* fs (used for filesystem operations)
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
const niceObject = {
|
||||
type: "the greatest",
|
||||
knownFor: [
|
||||
"indenting",
|
||||
"everything",
|
||||
{
|
||||
and: "wanting",
|
||||
to: "keep",
|
||||
it: "so"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,9 +1,5 @@
|
|||
{% macro header(size, text) %}
|
||||
{% append "toc" %}
|
||||
<a href="#{{ text | replace(" ", "-") }}" class="toc-{{ size }}">{{ text }}</a>
|
||||
{% endappend %}
|
||||
|
||||
<a name="{{ text | replace(" ", "-") }}"></a>
|
||||
<a name="{{ text | replace(" ", "-") }}" data-orig-text="{{ text }}" class="toc-anchor toc-anchor-{{ size }}"></a>
|
||||
<{{ size }}>
|
||||
{{ text }}
|
||||
</{{ size }}>
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
{% extends "templates/article.njk" %}
|
||||
{% import "components/toc.njk" as toc with context %}
|
||||
|
||||
{% block header %}
|
||||
How to - Article template
|
||||
{% endblock %}
|
||||
|
||||
{% block toc %}
|
||||
{% output "toc" %}{% endoutput %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="content">
|
||||
|
||||
{{ toc.header("h1", "Introduction", tocList) }}
|
||||
<p>
|
||||
I want to start off by saying that this article template (as of now) is really fricking shit.
|
||||
The code base is somewhat okay in complexity, but I do not feel like it is done in a efficient/good
|
||||
way and won't work without CSS enabled (although, most people don't disable CSS).
|
||||
Please use with causition until I've implemented something better that would work without CSS.
|
||||
</p>
|
||||
|
||||
{{ toc.header("h2", "Terminology", tocList) }}
|
||||
<p>
|
||||
Whenever I say "ToC" in this article I am refering to "Table of Contents". (I couldn't be bothered
|
||||
writing it out all the times...)
|
||||
</p>
|
||||
|
||||
{{ toc.header("h1", "Usage", tocList ) }}
|
||||
<p>
|
||||
To use the template you would simply extend <code>templates/article.njk</code> and
|
||||
import <code>components/toc.njk</code>:
|
||||
|
||||
<code class="code-block">
|
||||
{{ '
|
||||
{% extends "templates/article.njk" %}
|
||||
{% import "components/toc.njk" as toc with context %}
|
||||
|
||||
{% block header %}
|
||||
Your header
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="content">
|
||||
<p>
|
||||
Your content goes here
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}' | escape | replace("\n", "", 1) | replace("\n", "<br>") | replace(" ", " ") }}
|
||||
</code>
|
||||
|
||||
You would use the <code>header</code> block to define a header, and then you place your
|
||||
content inside a <code>div</code> with the class <code>content</code> in your <code>body</code>-block.
|
||||
</p>
|
||||
|
||||
{{ toc.header("h2", "Adding headers to the ToC", tocList) }}
|
||||
<p>
|
||||
Adding headers is kind of easy, <code>{% raw %}{% import "components/toc.njk" as toc with context %}{% endraw %}</code>
|
||||
imports a macro that will create a header and automatically add it to the table of contents. You use it as such:
|
||||
|
||||
<code class="code-block">
|
||||
{{ '{{ toc.header(size, text, tocList) }}' }}
|
||||
</code>
|
||||
|
||||
<code>size</code> is a string and is a header level (aka <code>h1</code>, <code>h2</code>, <code>h3</code>, etc.),
|
||||
<code>text</code> is a string that holds the text you would want to display, and finally <code>tocList</code> is a
|
||||
variable inherited from <code>template/article.njk</code> that keeps a hold of which headers should go in the ToC. <br>
|
||||
|
||||
If you would want to display an <code>{{ '<h1>' }}</code> with the text "Introduction" you would do:
|
||||
|
||||
<code class="code-block">
|
||||
{{ '{{ toc.header("h1", "Introduction", tocList) }}' }}
|
||||
</code>
|
||||
</p>
|
||||
|
||||
{{ toc.header("h2", "Generate the ToC", tocList) }}
|
||||
<p>
|
||||
Sadly it isn't enough to just add the heading to the ToC. You will have to generate the ToC as well, but
|
||||
this isn't hard at all to do. You simply add <code>{{ '{{ toc.generate(tocList) }}' }}</code> to the end
|
||||
of your <code>body</code>-block.
|
||||
</p>
|
||||
|
||||
{{ toc.header("h1", "Example code", tocList) }}
|
||||
<code class="code-block">
|
||||
{{ '
|
||||
{% extends "templates/article.njk" %}
|
||||
{% import "components/toc.njk" as toc with context %}
|
||||
|
||||
{% block header %}
|
||||
Your header
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="content">
|
||||
{{ toc.header("h1", "Introduction", tocList) }}
|
||||
<p>
|
||||
Your introduction goes here...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{ toc.generate(tocList) }}
|
||||
{% endblock %}' | escape | replace("\n", "", 1) | replace("\n", "<br>") | replace(" ", " ") }}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,29 +0,0 @@
|
|||
{% extends "templates/article.njk" %}
|
||||
{% import "components/toc.njk" as toc with context %}
|
||||
{% import "components/code.njk" as code with context %}
|
||||
|
||||
{% block header %}
|
||||
Markdown
|
||||
{% endblock %}
|
||||
|
||||
{% block toc %}
|
||||
{% output "toc" %}{% endoutput %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% markdown %}
|
||||
# Test of markdown
|
||||
|
||||
{{ code.import("code/test.js", "javascript") }}
|
||||
|
||||
* A list item
|
||||
* A list item
|
||||
* A list item
|
||||
* A list item
|
||||
|
||||
*italic* **bold**
|
||||
|
||||
{{ toc.header("h1", "test header") }}
|
||||
|
||||
{% endmarkdown %}
|
||||
{% endblock %}
|
25
content/pages/docs/main.njk
Normal file
25
content/pages/docs/main.njk
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends "templates/article.njk" %}
|
||||
{% import "components/toc.njk" as toc with context %}
|
||||
|
||||
{% block head %}
|
||||
<title>qwik cms - docs - main</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
qwik cms - docs
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ toc.header("h1", "Basic idea") }}
|
||||
<p>
|
||||
Our CMS can be used in multiple (different) ways thanks to the underlying technologies.
|
||||
The basic idea is that every page is a file. Although how you treat that file is really
|
||||
up to you. It can be plain HTML, nunjucks/njk (a templating language for HTML) or even
|
||||
markdown (although not "plain markdown").
|
||||
</p>
|
||||
{{ toc.header("h2", "Views on bloat") }}
|
||||
<p>
|
||||
I do not want to bloat this CMS, but I must admit that it is bloated.
|
||||
{{ hello }}
|
||||
</p>
|
||||
{% endblock %}
|
16
content/pages/docs/main_md.njk
Normal file
16
content/pages/docs/main_md.njk
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends "templates/article_md.njk" %}
|
||||
|
||||
{% block head %}
|
||||
<title>qwik cms - docs - main</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
qwik cms - docs
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
# Test
|
||||
Heyyy you there!
|
||||
|
||||
## Undertest
|
||||
{% endblock %}
|
|
@ -1,16 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hello World!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
Welcome to {{ serverName }}! <br>
|
||||
<a href="/test1">Test 1</a> <br>
|
||||
<a href="/test2">Test 2</a> <br>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||
<p>
|
||||
Test 1
|
||||
</p>
|
|
@ -1,3 +0,0 @@
|
|||
<p>
|
||||
Test 2
|
||||
</p>
|
|
@ -1,133 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{% include "templates/defaultTags.njk" %}
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
Actual Header
|
||||
</header>
|
||||
|
||||
<h1>Header 1</h1>
|
||||
<h2>Header 2</h2>
|
||||
<h3>Header 3</h3>
|
||||
<h4>Header 4</h4>
|
||||
<h5>Header 5</h5>
|
||||
<h6>Header 6</h6>
|
||||
|
||||
<p>
|
||||
This is a paragraph. Lorem ipsum dolor sit, amet consectetur adipisicing elit. Voluptate non ut commodi quisquam in, a dicta rem expedita tenetur quis ratione aut nesciunt corporis soluta similique eius accusantium, optio numquam? Dolorem, voluptates!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i>Italic text</i> <br>
|
||||
<b>Bold text</b> <br>
|
||||
<a href="./">This is an example link</a>
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
This is a blockquote. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quidem voluptates soluta dolorem esse aperiam natus provident nemo, dicta dolores quae velit officia minus expedita cum modi eveniet minima suscipit mollitia aspernatur ad.
|
||||
</blockquote>
|
||||
|
||||
<ul>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<ul>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
<ul>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<ol>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<li>This is a list item!</li>
|
||||
<ol>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
<ol>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
<li>This is a nested list.</li>
|
||||
</ol>
|
||||
</ol>
|
||||
</ol>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Animal</th>
|
||||
<th>Name</th>
|
||||
<th>Age</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bunny</td>
|
||||
<td>Jumpy</td>
|
||||
<td>2 years</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cat</td>
|
||||
<td>Biter</td>
|
||||
<td>4 months</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bunny</td>
|
||||
<td>Jumpy</td>
|
||||
<td>2 years</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cat</td>
|
||||
<td>Biter</td>
|
||||
<td>4 months</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bunny</td>
|
||||
<td>Jumpy</td>
|
||||
<td>2 years</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cat</td>
|
||||
<td>Biter</td>
|
||||
<td>4 months</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bunny</td>
|
||||
<td>Jumpy</td>
|
||||
<td>2 years</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cat</td>
|
||||
<td>Biter</td>
|
||||
<td>4 months</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<code class="code-block">
|
||||
{{ '
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
' | escape | replace("\n", "", 1) | replace("\n", "<br>") | replace(" ", " ") }}
|
||||
</code>
|
||||
</body>
|
||||
</html>
|
|
@ -17,7 +17,10 @@
|
|||
<div class="toc-container">
|
||||
<div class="toc">
|
||||
<p class="toc-title">Table of Contents</p>
|
||||
{% block toc %}{% endblock %}
|
||||
{% for tocLink in tocLinks %}
|
||||
{{ tocLink | safe }}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
31
content/templates/article_md.njk
Normal file
31
content/templates/article_md.njk
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{% include "./defaultTags.njk" %}
|
||||
<link rel="stylesheet" href="/assets/css/article.css">
|
||||
<link rel="stylesheet" href="/assets/css/syntax.css">
|
||||
{% block head %}
|
||||
<title>{{ serverName }}</title>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
|
||||
<div class="toc-container">
|
||||
<div class="toc">
|
||||
<p class="toc-title">Table of Contents</p>
|
||||
{% for tocLink in tocLinks %}
|
||||
{{ tocLink | safe }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% markdown %}
|
||||
{% block main %}{% endblock %}
|
||||
{% endmarkdown %}
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
38
index.js
38
index.js
|
@ -1,14 +1,17 @@
|
|||
import express from 'express'
|
||||
import njk from 'nunjucks'
|
||||
import njkAppend from 'nunjucks-append'
|
||||
import njkMarkdown from 'nunjucks-markdown'
|
||||
import marked from 'marked'
|
||||
import fs from 'fs'
|
||||
import { requestHandler } from './libs/requestHandler.js'
|
||||
import express from 'express'
|
||||
|
||||
import njk from 'nunjucks'
|
||||
import njkMarkdown from 'nunjucks-markdown'
|
||||
|
||||
import marked from 'marked'
|
||||
import hljs from 'highlight.js'
|
||||
|
||||
import { requestHandler } from './libs/requestHandler.js'
|
||||
import { mdRenderer } from './libs/mdRenderer.js'
|
||||
|
||||
|
||||
// Load in config
|
||||
const ConfigFile = fs.readFileSync('./config.json')
|
||||
const Config = JSON.parse(ConfigFile)
|
||||
|
||||
|
@ -16,12 +19,15 @@ const Config = JSON.parse(ConfigFile)
|
|||
|
||||
const Server = express()
|
||||
|
||||
|
||||
|
||||
// Some config
|
||||
Server.use('/assets', express.static(Config.assetsDir))
|
||||
|
||||
const njkEnv = njk.configure(
|
||||
Config.contentDir,
|
||||
{
|
||||
autoescape: false,
|
||||
autoescape: true,
|
||||
watch: true,
|
||||
trimBlocks: true,
|
||||
lstripBlocks: true,
|
||||
|
@ -35,21 +41,21 @@ marked.setOptions({
|
|||
return hljs.highlight(language, code)
|
||||
.value.replace(/\n/g, "<br>").replace(/•/g, " ")
|
||||
},
|
||||
gfm: true,
|
||||
smartypants: true,
|
||||
smartLists: true,
|
||||
silent: true
|
||||
gfm: true
|
||||
})
|
||||
|
||||
njkAppend.initialise(njkEnv)
|
||||
marked.use({ renderer: mdRenderer })
|
||||
|
||||
njkMarkdown.register(njkEnv, marked)
|
||||
|
||||
|
||||
Server.get(
|
||||
'*',
|
||||
(req, res) => requestHandler(req, res, Config)
|
||||
)
|
||||
|
||||
// Send all requests to the requestHandler.
|
||||
Server.get('*', (req, res) => requestHandler(req, res, Config))
|
||||
|
||||
|
||||
|
||||
// Start the server
|
||||
Server.listen(
|
||||
Config.serverPort,
|
||||
() => {
|
||||
|
|
10
libs/mdRenderer.js
Normal file
10
libs/mdRenderer.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export const mdRenderer = {
|
||||
heading(text, level) {
|
||||
return `
|
||||
<a name="${text.replace(/\s/g, "-")}" data-orig-text="${text}" class="toc-anchor toc-anchor-h${level}"></a>
|
||||
<h${level}>
|
||||
${text}
|
||||
</h${level}>
|
||||
`
|
||||
}
|
||||
}
|
35
libs/njkRenderer.js
Normal file
35
libs/njkRenderer.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import fs from 'fs'
|
||||
import njk from 'nunjucks'
|
||||
import { JSDOM } from 'jsdom'
|
||||
|
||||
// Load in config
|
||||
const ConfigFile = fs.readFileSync('./config.json')
|
||||
const Config = JSON.parse(ConfigFile)
|
||||
|
||||
function generateToc(dom) {
|
||||
const tocAnchors = dom.window.document.querySelectorAll(".toc-anchor")
|
||||
let tocLinks = []
|
||||
|
||||
// This basically creates a proper link for the ToC. :)))
|
||||
tocAnchors.forEach(anchor => tocLinks.push(`<a href="#${anchor.name}" class="${anchor.classList[1].replace("-anchor", "")}">${anchor.getAttribute('data-orig-text')}</a>`))
|
||||
|
||||
return tocLinks
|
||||
}
|
||||
|
||||
function generateContext(renderedNjk) {
|
||||
const dom = new JSDOM(renderedNjk)
|
||||
|
||||
return {
|
||||
serverName: Config.serverName,
|
||||
tocLinks: generateToc(dom)
|
||||
}
|
||||
}
|
||||
|
||||
export function njkRenderer(path) {
|
||||
const njkFile = fs.readFileSync(path).toString()
|
||||
const renderedNjk = njk.renderString(njkFile)
|
||||
|
||||
const context = generateContext(renderedNjk)
|
||||
|
||||
return njk.renderString(njkFile, context)
|
||||
}
|
|
@ -1,15 +1,12 @@
|
|||
import fs from 'fs'
|
||||
import { njkRenderer } from './njkRenderer.js'
|
||||
|
||||
export function requestHandler(req, res, Config) {
|
||||
const context = {
|
||||
serverName: Config.serverName
|
||||
}
|
||||
if (fs.existsSync(`./${Config.contentDir}/pages/${req.path}.njk`))
|
||||
return res.send(njkRenderer(`./${Config.contentDir}/pages/${req.path}.njk`))
|
||||
|
||||
if (fs.existsSync(`./${Config.contentDir}/pages/${req.path}.njk`, context))
|
||||
return res.render(`pages/${req.path}.njk`)
|
||||
|
||||
if (fs.existsSync(`./${Config.contentDir}/pages/${req.path}/index.njk`, context))
|
||||
return res.render(`pages/${req.path}/index.njk`)
|
||||
if (fs.existsSync(`./${Config.contentDir}/pages/${req.path}/index.njk`))
|
||||
return res.send(njkRenderer(`./${Config.contentDir}/pages/${req.path}/index.njk`))
|
||||
|
||||
return res.status(404).render('errors/404.njk', context)
|
||||
}
|
|
@ -20,9 +20,9 @@
|
|||
"chokidar": "^3.5.2",
|
||||
"express": "^4.17.1",
|
||||
"highlight.js": "^11.2.0",
|
||||
"jsdom": "^16.7.0",
|
||||
"marked": "^2.1.3",
|
||||
"nunjucks": "^3.2.3",
|
||||
"nunjucks-append": "^0.0.3",
|
||||
"nunjucks-markdown": "^2.0.1"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user