@posttext/modules
Version:
The official modules for Posttext
675 lines (674 loc) • 23.6 kB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import Prism from 'prismjs';
import loadLanguages from 'prismjs/components/index.js';
import stripIndent from 'strip-indent';
import qrcode from 'qrcode';
import katex from 'katex';
import { extractParagraphs } from '@posttext/ptlib';
const KATEX_STATE = Symbol('KatexState');
const CODE_BLOCK_STATE = Symbol('CodeBlockState');
export const TOC = Symbol('Toc');
export const tocDef = [
null,
'section',
'subsection',
'subsubsection',
];
export const tagResolvers = (_options) => {
return {
__root__: {
resolve: async function* () {
const content = yield {
name: 'getBlockInlines',
transform: {
...extractParagraphs,
},
};
yield {
name: 'html',
template: `{{{ data.content }}}`,
data: {
content,
},
};
},
},
posttext: {
resolve: async function* () {
const attrs = yield {
name: 'getAttrs',
};
const title = attrs.title;
yield {
name: 'metadata',
metadata: {
title,
},
};
},
},
comment: {
resolve: async function* () {
/* pass */
},
},
title: {
resolve: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<h1>{{{ data.content }}}</h1>',
data: {
content: content !== null && content !== void 0 ? content : '',
},
};
},
},
section: {
preload: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'send',
topic: TOC,
data: {
tagName: 'section',
content,
},
};
},
resolve: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<h2>{{{ data.content }}}</h2>',
data: {
content: content !== null && content !== void 0 ? content : '',
},
};
},
},
subsection: {
preload: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'send',
topic: TOC,
data: {
tagName: 'subsection',
content,
},
};
},
resolve: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<h3>{{{ data.content }}}</h3>',
data: {
content: content !== null && content !== void 0 ? content : '',
},
};
},
},
subsubsection: {
preload: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'send',
topic: TOC,
data: {
tagName: 'subsubsection',
content,
},
};
},
resolve: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<h4>{{{ data.content }}}</h4>',
data: {
content: content !== null && content !== void 0 ? content : '',
},
};
},
},
bold: {
resolve: async function* () {
var _a;
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<b>{{{ data.content }}}</b>',
type: 'inline',
data: {
content: (_a = content === null || content === void 0 ? void 0 : content.replace(/\s\s\n/g, '<br>')) !== null && _a !== void 0 ? _a : '',
},
};
},
},
italic: {
resolve: async function* () {
var _a;
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<i>{{{ data.content }}}</i>',
type: 'inline',
data: {
content: (_a = content === null || content === void 0 ? void 0 : content.replace(/\s\s\n/g, '<br>')) !== null && _a !== void 0 ? _a : '',
},
};
},
},
underline: {
resolve: async function* () {
var _a;
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<u>{{{ data.content }}}<u>',
type: 'inline',
data: {
content: (_a = content === null || content === void 0 ? void 0 : content.replace(/\s\s\n/g, '<br>')) !== null && _a !== void 0 ? _a : '',
},
};
},
},
list: {
resolve: async function* () {
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<ul>{{{ data.content }}}</ul>',
data: {
content: content !== null && content !== void 0 ? content : '',
},
};
},
},
item: {
resolve: async function* () {
var _a;
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: '<li>{{{ data.content }}}</li>',
data: {
content: (_a = content === null || content === void 0 ? void 0 : content.replace(/\s\s\n/g, '<br>')) !== null && _a !== void 0 ? _a : '',
},
};
},
},
code: {
preload: async function* () {
const state = yield {
name: 'getState',
symbol: CODE_BLOCK_STATE,
};
if (!state.languagesLoaded) {
state.languagesLoaded = true;
loadLanguages();
yield {
name: 'addDeps',
deps: [
{
type: 'css',
src: 'prismjs/themes/prism.css',
},
],
};
}
},
resolve: async function* () {
const params = yield {
name: 'getParams',
};
const language = params[0] &&
Object.keys(Prism.languages).indexOf(params[0])
? params[0]
: 'text';
const BEGIN_NEWLINE = /^\r?\n/;
const END_NEWLINE = /\r?\n[\t ]+$/;
const rawTextContent = yield {
name: 'textContent',
index: 0,
};
const textContent = rawTextContent
? stripIndent(rawTextContent)
.replace(BEGIN_NEWLINE, '')
.replace(END_NEWLINE, '')
: '';
const code = language !== 'text'
? Prism.highlight(textContent, Prism.languages[language], language)
: textContent;
yield {
name: 'html',
template: '<pre class="std_code"><code class="language-{{ data.language }}">{{{ data.code }}}</code></pre>',
data: {
language,
code,
},
};
},
},
"code'": {
preload: async function* () {
const state = yield {
name: 'getState',
symbol: CODE_BLOCK_STATE,
};
if (!state.languagesLoaded) {
state.languagesLoaded = true;
loadLanguages();
yield {
name: 'addDeps',
deps: [
{
type: 'css',
src: 'prismjs/themes/prism.css',
},
],
};
}
},
resolve: async function* () {
const params = yield {
name: 'getParams',
};
const language = params[0] &&
Object.keys(Prism.languages).indexOf(params[0])
? params[0]
: 'text';
const BEGIN_NEWLINE = /^\r?\n/;
const END_NEWLINE = /\r?\n[\t ]+$/;
const rawTextContent = yield {
name: 'textContent',
index: 0,
};
const textContent = rawTextContent
? stripIndent(rawTextContent)
.replace(BEGIN_NEWLINE, '')
.replace(END_NEWLINE, '')
: '';
const code = language !== 'text'
? Prism.highlight(textContent, Prism.languages[language], language)
: textContent;
yield {
name: 'html',
template: '<code class="std_code language-{{ data.language }}">{{{ data.code }}}</code>',
type: 'inline',
data: {
language,
code,
},
};
},
},
katex: {
preload: async function* () {
const state = yield {
name: 'getState',
symbol: KATEX_STATE,
};
if (!state.katexLoaded) {
state.katexLoaded = true;
yield {
name: 'addDeps',
deps: [
{
type: 'css',
src: 'katex/dist/katex.css',
},
],
};
}
},
resolve: async function* () {
const rawTextContent = yield {
name: 'textContent',
};
const content = katex.renderToString(rawTextContent, {
throwOnError: false,
displayMode: true,
});
yield {
name: 'html',
template: `{{{ data.content }}}`,
data: {
content,
},
};
},
},
"katex'": {
preload: async function* () {
const state = yield {
name: 'getState',
symbol: KATEX_STATE,
};
if (!state.katexLoaded) {
state.katexLoaded = true;
yield {
name: 'addDeps',
deps: [
{
type: 'css',
src: 'katex/dist/katex.css',
},
],
};
}
},
resolve: async function* () {
const rawTextContent = yield {
name: 'textContent',
};
const content = katex.renderToString(rawTextContent, {
throwOnError: false,
});
yield {
name: 'html',
template: `{{{ data.content }}}`,
type: 'inline',
data: {
content,
},
};
},
},
toc: {
resolve: async function* () {
const tocItems = yield {
name: 'receive',
topic: TOC,
};
const stack = [];
stack.push({
content: '',
children: [],
previous: [],
});
while (stack.length > 1 || tocItems.length) {
let currentItem = stack.pop();
const nextItem = tocItems.shift();
const nextItemLevel = (nextItem === null || nextItem === void 0 ? void 0 : nextItem.tagName)
? tocDef.indexOf(nextItem.tagName)
: -1;
if (nextItemLevel === stack.length) {
const counter = stack
.concat([currentItem])
.slice(1)
.map((item) => item.previous.length + 1)
.join('.');
const renderedItem = yield {
name: 'html',
template: `
<li class="std_toc__item">
<span class="std_toc__number">{{ data.counter }}</span>
<span class="std_toc__text">{{ data.content }}</span>
{{#if data.children.length}}
<ul class="std_toc__list">
{{#each data.children}}
{{{ this }}}
{{/each}}
</ul>
{{/if}}
</li>
`,
emit: false,
data: {
content: currentItem.content,
children: currentItem.children,
counter,
},
};
currentItem = {
content: nextItem.content,
children: [],
previous: currentItem.previous.concat([
renderedItem,
]),
};
stack.push(currentItem);
}
else if (nextItemLevel > stack.length) {
stack.push(currentItem);
while (nextItemLevel > stack.length) {
stack.push({
content: '',
children: [],
previous: [],
});
}
stack.push({
content: nextItem.content,
children: [],
previous: [],
});
}
else {
const counter = stack
.concat([currentItem])
.slice(1)
.map((item) => item.previous.length + 1)
.join('.');
if (nextItem) {
tocItems.unshift(nextItem);
}
let parentItem = stack.pop();
const renderedItem = yield {
name: 'html',
template: `
<li class="std_toc__item">
<span class="std_toc__number">{{ data.counter }}</span>
<span class="std_toc__text">{{ data.content }}</span>
{{#if data.children.length}}
<ul class="std_toc__list">
{{#each data.children}}
{{{ this }}}
{{/each}}
</ul>
{{/if}}
</li>
`,
emit: false,
data: {
content: currentItem.content,
children: currentItem.children,
counter,
},
};
parentItem = {
content: parentItem.content,
children: currentItem.previous.concat([
renderedItem,
]),
previous: parentItem.previous,
};
stack.push(parentItem);
}
}
const rootItem = stack.pop();
const content = yield {
name: 'getBlock',
index: 0,
};
yield {
name: 'html',
template: `
<h2 class="std_toc__title">
{{{ data.content }}}
</h2>
<ul class="std_toc__list">
{{#each data.items}}
{{{ this }}}
{{/each}}
</ul>
`,
data: {
items: rootItem.children,
content,
},
};
},
},
blockquote: {
resolve: async function* () {
const content = yield {
name: 'getBlockInlines',
transform: {
...extractParagraphs,
},
};
yield {
name: 'html',
template: `
<blockquote class="std_blockquote">
{{{ data.content }}}
</blockquote>
`,
data: {
content,
},
};
},
},
image: {
resolve: async function* () {
var _a;
const link = yield (_a = {
name: 'textContent',
index: 0,
}) !== null && _a !== void 0 ? _a : '';
const alt = yield {
name: 'textContent',
index: 1,
};
if (!link) {
return;
}
const isUrl = link.match(/(^|^http:|^https:)\/\//);
if (isUrl) {
yield {
name: 'html',
template: `
<div class="std_image">
<img src="{{ data.link }}"{{#if data.alt}} alt="{{ data.alt }}"{{/if}}>
</div>
`,
data: {
link,
alt,
},
};
}
else {
const dest = yield {
name: 'copyFile',
src: link,
dest: `images/${link.split(/[\\/]/).slice(-1)[0]}`,
};
yield {
name: 'html',
template: `
<div class="std_image">
<img src="{{ data.link }}"{{#if data.alt}} alt="{{ data.alt }}"{{/if}}>
</div>
`,
data: {
link: dest,
alt,
},
};
}
},
},
qrcode: {
resolve: async function* () {
var _a;
const content = yield (_a = {
name: 'textContent',
index: 0,
}) !== null && _a !== void 0 ? _a : '';
const svg = await new Promise((resolve, reject) => {
qrcode.toString(content, {
type: 'svg',
margin: 1,
}, (err, text) => {
if (err) {
reject(err);
}
resolve(text);
});
});
yield {
name: 'html',
template: '<div class="std_qrcode">{{{ data.content }}}</div>',
data: {
content: svg,
},
};
},
},
box: {
resolve: async function* () {
const content = yield {
name: 'getBlockInlines',
transform: {
...extractParagraphs,
},
};
yield {
name: 'html',
template: `
<div class="std_box">
{{{ data.content }}}
</div>
`,
data: {
content,
},
};
},
},
};
};