Hand roll a SSR markdown parser for reveal

This commit is contained in:
Maciej Jur 2023-04-12 02:02:46 +02:00
parent e5c6d7f1e8
commit bc8c817e92
6 changed files with 147 additions and 535 deletions

View file

@ -12,18 +12,23 @@
"dependencies": { "dependencies": {
"@astrojs/mdx": "^0.18.3", "@astrojs/mdx": "^0.18.3",
"@astrojs/svelte": "^2.1.0", "@astrojs/svelte": "^2.1.0",
"astro": "^2.2.1", "astro": "^2.2.2",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"leaflet": "^1.9.3", "leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3", "leaflet.markercluster": "^1.5.3",
"lunr": "^2.3.9", "lunr": "^2.3.9",
"marked": "^4.3.0",
"rehype-katex": "^6.0.2", "rehype-katex": "^6.0.2",
"rehype-raw": "^6.1.1",
"rehype-stringify": "^9.0.3",
"remark-emoji": "^3.1.1", "remark-emoji": "^3.1.1",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"reveal.js": "^4.4.0", "reveal.js": "^4.4.0",
"sass": "^1.61.0", "sass": "^1.62.0",
"svelte": "^3.58.0", "svelte": "^3.58.0",
"unified": "^10.1.2",
"unist-util-visit": "^4.1.2" "unist-util-visit": "^4.1.2"
}, },
"devDependencies": { "devDependencies": {
@ -32,6 +37,7 @@
"@types/leaflet.markercluster": "^1.5.1", "@types/leaflet.markercluster": "^1.5.1",
"@types/lunr": "^2.3.4", "@types/lunr": "^2.3.4",
"@types/marked": "^4.0.8", "@types/marked": "^4.0.8",
"@types/reveal.js": "^4.4.2" "@types/reveal.js": "^4.4.2",
"@types/unist": "^2.0.6"
} }
} }

View file

@ -3,13 +3,13 @@ lockfileVersion: '6.0'
dependencies: dependencies:
'@astrojs/mdx': '@astrojs/mdx':
specifier: ^0.18.3 specifier: ^0.18.3
version: 0.18.3(astro@2.2.1)(rollup@3.20.2) version: 0.18.3(astro@2.2.2)(rollup@3.20.2)
'@astrojs/svelte': '@astrojs/svelte':
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.1.0(astro@2.2.1)(svelte@3.58.0)(typescript@4.9.5)(vite@4.2.1) version: 2.1.0(astro@2.2.2)(svelte@3.58.0)(typescript@4.9.5)(vite@4.2.1)
astro: astro:
specifier: ^2.2.1 specifier: ^2.2.2
version: 2.2.1(sass@1.61.0) version: 2.2.2(sass@1.62.0)
dayjs: dayjs:
specifier: ^1.11.7 specifier: ^1.11.7
version: 1.11.7 version: 1.11.7
@ -22,27 +22,42 @@ dependencies:
lunr: lunr:
specifier: ^2.3.9 specifier: ^2.3.9
version: 2.3.9 version: 2.3.9
marked:
specifier: ^4.3.0
version: 4.3.0
rehype-katex: rehype-katex:
specifier: ^6.0.2 specifier: ^6.0.2
version: 6.0.2 version: 6.0.2
rehype-raw:
specifier: ^6.1.1
version: 6.1.1
rehype-stringify:
specifier: ^9.0.3
version: 9.0.3
remark-emoji: remark-emoji:
specifier: ^3.1.1 specifier: ^3.1.1
version: 3.1.1 version: 3.1.1
remark-gfm:
specifier: ^3.0.1
version: 3.0.1
remark-math: remark-math:
specifier: ^5.1.1 specifier: ^5.1.1
version: 5.1.1 version: 5.1.1
remark-parse:
specifier: ^10.0.1
version: 10.0.1
remark-rehype:
specifier: ^10.1.0
version: 10.1.0
reveal.js: reveal.js:
specifier: ^4.4.0 specifier: ^4.4.0
version: 4.4.0 version: 4.4.0
sass: sass:
specifier: ^1.61.0 specifier: ^1.62.0
version: 1.61.0 version: 1.62.0
svelte: svelte:
specifier: ^3.58.0 specifier: ^3.58.0
version: 3.58.0 version: 3.58.0
unified:
specifier: ^10.1.2
version: 10.1.2
unist-util-visit: unist-util-visit:
specifier: ^4.1.2 specifier: ^4.1.2
version: 4.1.2 version: 4.1.2
@ -66,6 +81,9 @@ devDependencies:
'@types/reveal.js': '@types/reveal.js':
specifier: ^4.4.2 specifier: ^4.4.2
version: 4.4.2 version: 4.4.2
'@types/unist':
specifier: ^2.0.6
version: 2.0.6
packages: packages:
@ -103,13 +121,13 @@ packages:
vscode-uri: 3.0.7 vscode-uri: 3.0.7
dev: false dev: false
/@astrojs/markdown-remark@2.1.3(astro@2.2.1): /@astrojs/markdown-remark@2.1.3(astro@2.2.2):
resolution: {integrity: sha512-Di8Qbit9p7L7eqKklAJmiW9nVD+XMsNHpaNzCLduWjOonDu9fVgEzdjeDrTVCDtgrvkfhpAekuNXrp5+w4F91g==} resolution: {integrity: sha512-Di8Qbit9p7L7eqKklAJmiW9nVD+XMsNHpaNzCLduWjOonDu9fVgEzdjeDrTVCDtgrvkfhpAekuNXrp5+w4F91g==}
peerDependencies: peerDependencies:
astro: ^2.2.0 astro: ^2.2.0
dependencies: dependencies:
'@astrojs/prism': 2.1.1 '@astrojs/prism': 2.1.1
astro: 2.2.1(sass@1.61.0) astro: 2.2.2(sass@1.62.0)
github-slugger: 1.5.0 github-slugger: 1.5.0
import-meta-resolve: 2.2.2 import-meta-resolve: 2.2.2
rehype-raw: 6.1.1 rehype-raw: 6.1.1
@ -126,11 +144,11 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@astrojs/mdx@0.18.3(astro@2.2.1)(rollup@3.20.2): /@astrojs/mdx@0.18.3(astro@2.2.2)(rollup@3.20.2):
resolution: {integrity: sha512-fFkzYthnFqxmdp6IesvzU6FDHdAGo9bf4dbMOPCREcBfEhATqSpT9gjK/HdJ5s1MfZI8jjYeSC3yzhmNlq62qA==} resolution: {integrity: sha512-fFkzYthnFqxmdp6IesvzU6FDHdAGo9bf4dbMOPCREcBfEhATqSpT9gjK/HdJ5s1MfZI8jjYeSC3yzhmNlq62qA==}
engines: {node: '>=16.12.0'} engines: {node: '>=16.12.0'}
dependencies: dependencies:
'@astrojs/markdown-remark': 2.1.3(astro@2.2.1) '@astrojs/markdown-remark': 2.1.3(astro@2.2.2)
'@astrojs/prism': 2.1.1 '@astrojs/prism': 2.1.1
'@mdx-js/mdx': 2.3.0 '@mdx-js/mdx': 2.3.0
'@mdx-js/rollup': 2.3.0(rollup@3.20.2) '@mdx-js/rollup': 2.3.0(rollup@3.20.2)
@ -160,7 +178,7 @@ packages:
prismjs: 1.29.0 prismjs: 1.29.0
dev: false dev: false
/@astrojs/svelte@2.1.0(astro@2.2.1)(svelte@3.58.0)(typescript@4.9.5)(vite@4.2.1): /@astrojs/svelte@2.1.0(astro@2.2.2)(svelte@3.58.0)(typescript@4.9.5)(vite@4.2.1):
resolution: {integrity: sha512-upfkscrNuZbQvqVB5EG38FPJCgHCxO/LOJLAap75rO/++c1T7ztbVru4uSYVBRJkzTDuH3TS52T8kFTVgHXx/g==} resolution: {integrity: sha512-upfkscrNuZbQvqVB5EG38FPJCgHCxO/LOJLAap75rO/++c1T7ztbVru4uSYVBRJkzTDuH3TS52T8kFTVgHXx/g==}
engines: {node: '>=16.12.0'} engines: {node: '>=16.12.0'}
peerDependencies: peerDependencies:
@ -168,7 +186,7 @@ packages:
svelte: ^3.54.0 svelte: ^3.54.0
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 2.0.4(svelte@3.58.0)(vite@4.2.1) '@sveltejs/vite-plugin-svelte': 2.0.4(svelte@3.58.0)(vite@4.2.1)
astro: 2.2.1(sass@1.61.0) astro: 2.2.2(sass@1.62.0)
svelte: 3.58.0 svelte: 3.58.0
svelte2tsx: 0.5.23(svelte@3.58.0)(typescript@4.9.5) svelte2tsx: 0.5.23(svelte@3.58.0)(typescript@4.9.5)
transitivePeerDependencies: transitivePeerDependencies:
@ -795,7 +813,7 @@ packages:
magic-string: 0.30.0 magic-string: 0.30.0
svelte: 3.58.0 svelte: 3.58.0
svelte-hmr: 0.15.1(svelte@3.58.0) svelte-hmr: 0.15.1(svelte@3.58.0)
vite: 4.2.1(sass@1.61.0) vite: 4.2.1(sass@1.62.0)
vitefu: 0.2.4(vite@4.2.1) vitefu: 0.2.4(vite@4.2.1)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -927,7 +945,6 @@ packages:
/@types/unist@2.0.6: /@types/unist@2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
/@types/yargs-parser@21.0.0: /@types/yargs-parser@21.0.0:
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
@ -1023,8 +1040,8 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/astro@2.2.1(sass@1.61.0): /astro@2.2.2(sass@1.62.0):
resolution: {integrity: sha512-yYPRzh3su38bi3VBCKmYAUBkQSaFQMKFsu8JAVDzFRoGLbskJ/6JkDX2abSB9/iRug8GAKaH/FWxXOTzIsSQ7Q==} resolution: {integrity: sha512-RmV/UiOW3losWRbR8CeFDFDtjBOdFdBTEccXF9sT/ZxLz7T9SglevtOHgKwTSRPgUM9c0LteFDFcHDY3OYBgDg==}
engines: {node: '>=16.12.0', npm: '>=6.14.0'} engines: {node: '>=16.12.0', npm: '>=6.14.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -1035,7 +1052,7 @@ packages:
dependencies: dependencies:
'@astrojs/compiler': 1.3.1 '@astrojs/compiler': 1.3.1
'@astrojs/language-server': 0.28.3 '@astrojs/language-server': 0.28.3
'@astrojs/markdown-remark': 2.1.3(astro@2.2.1) '@astrojs/markdown-remark': 2.1.3(astro@2.2.2)
'@astrojs/telemetry': 2.1.0 '@astrojs/telemetry': 2.1.0
'@astrojs/webapi': 2.1.0 '@astrojs/webapi': 2.1.0
'@babel/core': 7.21.4 '@babel/core': 7.21.4
@ -1071,7 +1088,7 @@ packages:
preferred-pm: 3.0.3 preferred-pm: 3.0.3
prompts: 2.4.2 prompts: 2.4.2
rehype: 12.0.1 rehype: 12.0.1
semver: 7.3.8 semver: 7.4.0
server-destroy: 1.0.1 server-destroy: 1.0.1
shiki: 0.11.1 shiki: 0.11.1
slash: 4.0.0 slash: 4.0.0
@ -1082,7 +1099,7 @@ packages:
typescript: 5.0.4 typescript: 5.0.4
unist-util-visit: 4.1.2 unist-util-visit: 4.1.2
vfile: 5.3.7 vfile: 5.3.7
vite: 4.2.1(sass@1.61.0) vite: 4.2.1(sass@1.62.0)
vitefu: 0.2.4(vite@4.2.1) vitefu: 0.2.4(vite@4.2.1)
yargs-parser: 21.1.1 yargs-parser: 21.1.1
zod: 3.21.4 zod: 3.21.4
@ -1144,7 +1161,7 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
caniuse-lite: 1.0.30001477 caniuse-lite: 1.0.30001477
electron-to-chromium: 1.4.356 electron-to-chromium: 1.4.359
node-releases: 2.0.10 node-releases: 2.0.10
update-browserslist-db: 1.0.10(browserslist@4.21.5) update-browserslist-db: 1.0.10(browserslist@4.21.5)
dev: false dev: false
@ -1382,8 +1399,8 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: false dev: false
/electron-to-chromium@1.4.356: /electron-to-chromium@1.4.359:
resolution: {integrity: sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A==} resolution: {integrity: sha512-OoVcngKCIuNXtZnsYoqlCvr0Cf3NIPzDIgwUfI9bdTFjXCrr79lI0kwQstLPZ7WhCezLlGksZk/BFAzoXC7GDw==}
dev: false dev: false
/emmet@2.4.2: /emmet@2.4.2:
@ -1846,8 +1863,8 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
dev: false dev: false
/is-core-module@2.11.0: /is-core-module@2.12.0:
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==}
dependencies: dependencies:
has: 1.0.3 has: 1.0.3
dev: false dev: false
@ -2102,12 +2119,6 @@ packages:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
dev: false dev: false
/marked@4.3.0:
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
engines: {node: '>= 12'}
hasBin: true
dev: false
/mdast-util-definitions@5.1.2: /mdast-util-definitions@5.1.2:
resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==}
dependencies: dependencies:
@ -3098,7 +3109,7 @@ packages:
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
hasBin: true hasBin: true
dependencies: dependencies:
is-core-module: 2.11.0 is-core-module: 2.12.0
path-parse: 1.0.7 path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0 supports-preserve-symlinks-flag: 1.0.0
dev: false dev: false
@ -3190,8 +3201,8 @@ packages:
suf-log: 2.5.3 suf-log: 2.5.3
dev: false dev: false
/sass@1.61.0: /sass@1.62.0:
resolution: {integrity: sha512-PDsN7BrVkNZK2+dj/dpKQAWZavbAQ87IXqVvw2+oEYI+GwlTWkvbQtL7F2cCNbMqJEYKPh1EcjSxsnqIb/kyaQ==} resolution: {integrity: sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
hasBin: true hasBin: true
dependencies: dependencies:
@ -3213,8 +3224,8 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/semver@7.3.8: /semver@7.4.0:
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} resolution: {integrity: sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==}
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
dependencies: dependencies:
@ -3642,7 +3653,7 @@ packages:
vfile-message: 3.1.4 vfile-message: 3.1.4
dev: false dev: false
/vite@4.2.1(sass@1.61.0): /vite@4.2.1(sass@1.62.0):
resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==} resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true hasBin: true
@ -3671,7 +3682,7 @@ packages:
postcss: 8.4.21 postcss: 8.4.21
resolve: 1.22.2 resolve: 1.22.2
rollup: 3.20.2 rollup: 3.20.2
sass: 1.61.0 sass: 1.62.0
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
dev: false dev: false
@ -3684,7 +3695,7 @@ packages:
vite: vite:
optional: true optional: true
dependencies: dependencies:
vite: 4.2.1(sass@1.61.0) vite: 4.2.1(sass@1.62.0)
dev: false dev: false
/vscode-css-languageservice@6.2.4: /vscode-css-languageservice@6.2.4:

View file

@ -1,478 +0,0 @@
/* Copyright (C) 2011-2023 Hakim El Hattab, http://hakim.se, and reveal.js contributors
* License: MIT (https://github.com/hakimel/reveal.js/blob/master/LICENSE)
*
* The reveal.js markdown plugin. Handles parsing of
* markdown inside of presentations as well as loading
* of external markdown documents.
*/
import { marked } from 'marked';
const DEFAULT_SLIDE_SEPARATOR = '\r?\n---\r?\n',
DEFAULT_NOTES_SEPARATOR = 'notes?:',
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
const SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
const CODE_LINE_NUMBER_REGEX = /\[([\s\d,|-]*)\]/;
const HTML_ESCAPE_MAP = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
};
const Plugin = () => {
// The reveal.js instance this plugin is attached to
let deck;
/**
* Retrieves the markdown contents of a slide section
* element. Normalizes leading tabs/whitespace.
*/
function getMarkdownFromSlide( section ) {
// look for a <script> or <textarea data-template> wrapper
var template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
// strip leading whitespace so it isn't evaluated as code
var text = ( template || section ).textContent;
// restore script end tags
text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
if( leadingTabs > 0 ) {
text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}(.*)','g'), function(m, p1) { return '\n' + p1 ; } );
}
else if( leadingWs > 1 ) {
text = text.replace( new RegExp('\\n? {' + leadingWs + '}(.*)', 'g'), function(m, p1) { return '\n' + p1 ; } );
}
return text;
}
/**
* Given a markdown slide section element, this will
* return all arguments that aren't related to markdown
* parsing. Used to forward any other user-defined arguments
* to the output markdown slide.
*/
function getForwardedAttributes( section ) {
var attributes = section.attributes;
var result = [];
for( var i = 0, len = attributes.length; i < len; i++ ) {
var name = attributes[i].name,
value = attributes[i].value;
// disregard attributes that are used for markdown loading/parsing
if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
if( value ) {
result.push( name + '="' + value + '"' );
}
else {
result.push( name );
}
}
return result.join( ' ' );
}
/**
* Inspects the given options and fills out default
* values for what's not defined.
*/
function getSlidifyOptions( options ) {
options = options || {};
options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
options.attributes = options.attributes || '';
return options;
}
/**
* Helper function for constructing a markdown slide.
*/
function createMarkdownSlide( content, options ) {
options = getSlidifyOptions( options );
var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
if( notesMatch.length === 2 ) {
content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
}
// prevent script end tags in the content from interfering
// with parsing
content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
return '<script type="text/template">' + content + '</script>';
}
/**
* Parses a data string into multiple slides based
* on the passed in separator arguments.
*/
function slidify( markdown, options ) {
options = getSlidifyOptions( options );
var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
horizontalSeparatorRegex = new RegExp( options.separator );
var matches,
lastIndex = 0,
isHorizontal,
wasHorizontal = true,
content,
sectionStack = [];
// iterate until all blocks between separators are stacked up
while( matches = separatorRegex.exec( markdown ) ) {
var notes = null;
// determine direction (horizontal by default)
isHorizontal = horizontalSeparatorRegex.test( matches[0] );
if( !isHorizontal && wasHorizontal ) {
// create vertical stack
sectionStack.push( [] );
}
// pluck slide content from markdown input
content = markdown.substring( lastIndex, matches.index );
if( isHorizontal && wasHorizontal ) {
// add to horizontal stack
sectionStack.push( content );
}
else {
// add to vertical stack
sectionStack[sectionStack.length-1].push( content );
}
lastIndex = separatorRegex.lastIndex;
wasHorizontal = isHorizontal;
}
// add the remaining slide
( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
var markdownSections = '';
// flatten the hierarchical stack, and insert <section data-markdown> tags
for( var i = 0, len = sectionStack.length; i < len; i++ ) {
// vertical
if( sectionStack[i] instanceof Array ) {
markdownSections += '<section '+ options.attributes +'>';
sectionStack[i].forEach( function( child ) {
markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
} );
markdownSections += '</section>';
}
else {
markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
}
}
return markdownSections;
}
/**
* Parses any current data-markdown slides, splits
* multi-slide markdown into separate sections and
* handles loading of external markdown.
*/
function processSlides( scope ) {
return new Promise( function( resolve ) {
var externalPromises = [];
[].slice.call( scope.querySelectorAll( 'section[data-markdown]:not([data-markdown-parsed])') ).forEach( function( section, i ) {
if( section.getAttribute( 'data-markdown' ).length ) {
externalPromises.push( loadExternalMarkdown( section ).then(
// Finished loading external file
function( xhr, url ) {
section.outerHTML = slidify( xhr.responseText, {
separator: section.getAttribute( 'data-separator' ),
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
notesSeparator: section.getAttribute( 'data-separator-notes' ),
attributes: getForwardedAttributes( section )
});
},
// Failed to load markdown
function( xhr, url ) {
section.outerHTML = '<section data-state="alert">' +
'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
'Check your browser\'s JavaScript console for more details.' +
'<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
'</section>';
}
) );
}
else {
section.outerHTML = slidify( getMarkdownFromSlide( section ), {
separator: section.getAttribute( 'data-separator' ),
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
notesSeparator: section.getAttribute( 'data-separator-notes' ),
attributes: getForwardedAttributes( section )
});
}
});
Promise.all( externalPromises ).then( resolve );
} );
}
function loadExternalMarkdown( section ) {
return new Promise( function( resolve, reject ) {
var xhr = new XMLHttpRequest(),
url = section.getAttribute( 'data-markdown' );
var datacharset = section.getAttribute( 'data-charset' );
// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
if( datacharset != null && datacharset != '' ) {
xhr.overrideMimeType( 'text/html; charset=' + datacharset );
}
xhr.onreadystatechange = function( section, xhr ) {
if( xhr.readyState === 4 ) {
// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
resolve( xhr, url );
}
else {
reject( xhr, url );
}
}
}.bind( this, section, xhr );
xhr.open( 'GET', url, true );
try {
xhr.send();
}
catch ( e ) {
console.warn( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
resolve( xhr, url );
}
} );
}
/**
* Check if a node value has the attributes pattern.
* If yes, extract it and add that value as one or several attributes
* to the target element.
*
* You need Cache Killer on Chrome to see the effect on any FOM transformation
* directly on refresh (F5)
* http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
*/
function addAttributeInElement( node, elementTarget, separator ) {
var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"]+?)\"|(data-[^\"= ]+?)(?=[\" ])", 'mg' );
var nodeValue = node.nodeValue;
var matches,
matchesClass;
if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
var classes = matches[1];
nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
node.nodeValue = nodeValue;
while( matchesClass = mardownClassRegex.exec( classes ) ) {
if( matchesClass[2] ) {
elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
} else {
elementTarget.setAttribute( matchesClass[3], "" );
}
}
return true;
}
return false;
}
/**
* Add attributes to the parent element of a text node,
* or the element of an attribute node.
*/
function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
var previousParentElement = element;
for( var i = 0; i < element.childNodes.length; i++ ) {
var childElement = element.childNodes[i];
if ( i > 0 ) {
var j = i - 1;
while ( j >= 0 ) {
var aPreviousChildElement = element.childNodes[j];
if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
previousParentElement = aPreviousChildElement;
break;
}
j = j - 1;
}
}
var parentSection = section;
if( childElement.nodeName == "section" ) {
parentSection = childElement ;
previousParentElement = childElement ;
}
if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
}
}
}
if ( element.nodeType == Node.COMMENT_NODE ) {
if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
addAttributeInElement( element, section, separatorSectionAttributes );
}
}
}
/**
* Converts any current data-markdown slides in the
* DOM to HTML.
*/
function convertSlides() {
var sections = deck.getRevealElement().querySelectorAll( '[data-markdown]:not([data-markdown-parsed])');
[].slice.call( sections ).forEach( function( section ) {
section.setAttribute( 'data-markdown-parsed', true )
var notes = section.querySelector( 'aside.notes' );
var markdown = getMarkdownFromSlide( section );
section.innerHTML = marked( markdown );
addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) ||
section.parentNode.getAttribute( 'data-element-attributes' ) ||
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
section.getAttribute( 'data-attributes' ) ||
section.parentNode.getAttribute( 'data-attributes' ) ||
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
// If there were notes, we need to re-add them after
// having overwritten the section's HTML
if( notes ) {
section.appendChild( notes );
}
} );
return Promise.resolve();
}
function escapeForHTML( input ) {
return input.replace( /([&<>'"])/g, char => HTML_ESCAPE_MAP[char] );
}
return {
id: 'markdown',
/**
* Starts processing and converting Markdown within the
* current reveal.js deck.
*/
init: function( reveal ) {
deck = reveal;
let { renderer, animateLists, ...markedOptions } = deck.getConfig().markdown || {};
if( !renderer ) {
renderer = new marked.Renderer();
renderer.code = ( code, language ) => {
// Off by default
let lineNumbers = '';
// Users can opt in to show line numbers and highlight
// specific lines.
// ```javascript [] show line numbers
// ```javascript [1,4-8] highlights lines 1 and 4-8
if( CODE_LINE_NUMBER_REGEX.test( language ) ) {
lineNumbers = language.match( CODE_LINE_NUMBER_REGEX )[1].trim();
lineNumbers = `data-line-numbers="${lineNumbers}"`;
language = language.replace( CODE_LINE_NUMBER_REGEX, '' ).trim();
}
// Escape before this gets injected into the DOM to
// avoid having the HTML parser alter our code before
// highlight.js is able to read it
code = escapeForHTML( code );
return `<pre><code ${lineNumbers} class="${language}">${code}</code></pre>`;
};
}
if( animateLists === true ) {
renderer.listitem = text => `<li class="fragment">${text}</li>`;
}
marked.setOptions( {
renderer,
...markedOptions
} );
return processSlides( deck.getRevealElement() ).then( convertSlides );
},
// TODO: Do these belong in the API?
processSlides: processSlides,
convertSlides: convertSlides,
slidify: slidify,
marked: marked
}
};
export default Plugin;

View file

@ -0,0 +1,70 @@
import type { Node, Parent } from 'unist';
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from 'remark-gfm';
import remarkRehype from "remark-rehype";
import rehypeRaw from "rehype-raw";
import rehypeStringify from "rehype-stringify";
import { visit } from "unist-util-visit";
interface CodeNode extends Node {
type: 'code';
lang?: string;
meta?: string;
value: string;
}
const ESCAPED_CHARS: {[key: string]: string} = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
};
const REGEX_HL_LINES = /\[([\s\d,|-]*)\]/;
function transformCode(node: CodeNode, index: number, parent: Parent) {
if (!node.meta || !REGEX_HL_LINES.test(node.meta)) return;
const langtag = node.lang ? ` class="${node.lang}" ` : ''
const numbers = node.meta.match(REGEX_HL_LINES)![1];
const escaped = node.value.replace(/[&<>"']/g, match => ESCAPED_CHARS[match] || '');
parent.children[index] = {
type: 'html',
value: `<pre><code data-line-numbers="${numbers}"${langtag}>${escaped}</code></pre>`,
} as any;
}
function codePassthrough() {
return (tree: Node, _: any) => {
visit(tree, 'code', transformCode);
}
}
const compiler = unified()
.use(remarkParse)
.use(remarkGfm)
.use(codePassthrough)
.use(remarkRehype, {allowDangerousHtml: true})
.use(rehypeRaw)
.use(rehypeStringify);
const SPLIT_H = /\n-----\n/;
const SPLIT_V = /\n---\n/;
function wrapSection(content: string): string {
return `<section>${content}</section>`;
}
export function compile(text: string): string {
return text
.split(SPLIT_H)
.map(stacks => stacks.split(SPLIT_V).map(slide => String(compiler.processSync(slide))))
.map(stack => (stack.length > 1)
? wrapSection(stack.map(wrapSection).join(''))
: wrapSection(stack[0]))
.join('');
}

View file

@ -1,19 +1,24 @@
import type { Node, Parent } from 'unist';
import { visit } from "unist-util-visit"; import { visit } from "unist-util-visit";
import type { Node } from "unist-util-visit/lib";
interface RubyNode extends Node {
type: 'html';
value: string;
}
const regex = /\{.+?\}\(.+?\)/g; const regex = /\{.+?\}\(.+?\)/g;
const group = /^\{(.+?)\}\((.+?)\)$/; const group = /^\{(.+?)\}\((.+?)\)$/;
const template = "<ruby>$1<rp>(</rp><rt>$2</rt><rp>)</rp></ruby>"; const template = "<ruby>$1<rp>(</rp><rt>$2</rt><rp>)</rp></ruby>";
function toRuby(ruby: string) { function toRuby(ruby: string): RubyNode {
return ({ return {
type: "html", type: "html",
value: ruby.replace(group, template), value: ruby.replace(group, template),
}) }
} }
function transformRuby(node: { value: string }, index: number, parent: any) { function transformRuby(node: {value: string}, index: number, parent: Parent) {
if (!regex.test(node.value)) return; if (!regex.test(node.value)) return;
const text = node.value.split(regex).map(value => ({ type: "text", value})); const text = node.value.split(regex).map(value => ({ type: "text", value}));

View file

@ -4,6 +4,7 @@ import 'reveal.js/dist/theme/black.css';
import 'reveal.js/plugin/highlight/monokai.css'; import 'reveal.js/plugin/highlight/monokai.css';
import Base from '../../layouts/Base.astro'; import Base from '../../layouts/Base.astro';
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import { compile } from '../../assets/reveal-ssr/markdown';
export async function getStaticPaths () { export async function getStaticPaths () {
@ -16,20 +17,17 @@ const { entry } = Astro.props;
<Base> <Base>
<div class="reveal"> <div class="reveal">
<div class="slides"> <div class="slides" set:html={compile(entry.body)}></div>
<section data-markdown data-separator="^\n-----\n$" data-separator-vertical="^\n---\n$">
<textarea data-template>{entry.body}</textarea>
</section>
</div>
</div> </div>
<script> <script>
import Reveal from 'reveal.js'; import Reveal from 'reveal.js';
import Markdown from '../../assets/reveal-md';
import Highlight from 'reveal.js/plugin/highlight/highlight'; import Highlight from 'reveal.js/plugin/highlight/highlight';
Reveal.initialize({ Reveal.initialize({
hash: true, hash: true,
plugins: [Markdown, Highlight] plugins: [
Highlight,
]
}); });
</script> </script>