Hand roll a SSR markdown parser for reveal
This commit is contained in:
parent
e5c6d7f1e8
commit
bc8c817e92
14
package.json
14
package.json
|
@ -12,18 +12,23 @@
|
|||
"dependencies": {
|
||||
"@astrojs/mdx": "^0.18.3",
|
||||
"@astrojs/svelte": "^2.1.0",
|
||||
"astro": "^2.2.1",
|
||||
"astro": "^2.2.2",
|
||||
"dayjs": "^1.11.7",
|
||||
"leaflet": "^1.9.3",
|
||||
"leaflet.markercluster": "^1.5.3",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^4.3.0",
|
||||
"rehype-katex": "^6.0.2",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark-emoji": "^3.1.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"reveal.js": "^4.4.0",
|
||||
"sass": "^1.61.0",
|
||||
"sass": "^1.62.0",
|
||||
"svelte": "^3.58.0",
|
||||
"unified": "^10.1.2",
|
||||
"unist-util-visit": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -32,6 +37,7 @@
|
|||
"@types/leaflet.markercluster": "^1.5.1",
|
||||
"@types/lunr": "^2.3.4",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/reveal.js": "^4.4.2"
|
||||
"@types/reveal.js": "^4.4.2",
|
||||
"@types/unist": "^2.0.6"
|
||||
}
|
||||
}
|
|
@ -3,13 +3,13 @@ lockfileVersion: '6.0'
|
|||
dependencies:
|
||||
'@astrojs/mdx':
|
||||
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':
|
||||
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:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1(sass@1.61.0)
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2(sass@1.62.0)
|
||||
dayjs:
|
||||
specifier: ^1.11.7
|
||||
version: 1.11.7
|
||||
|
@ -22,27 +22,42 @@ dependencies:
|
|||
lunr:
|
||||
specifier: ^2.3.9
|
||||
version: 2.3.9
|
||||
marked:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0
|
||||
rehype-katex:
|
||||
specifier: ^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:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
remark-gfm:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
remark-math:
|
||||
specifier: ^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:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0
|
||||
sass:
|
||||
specifier: ^1.61.0
|
||||
version: 1.61.0
|
||||
specifier: ^1.62.0
|
||||
version: 1.62.0
|
||||
svelte:
|
||||
specifier: ^3.58.0
|
||||
version: 3.58.0
|
||||
unified:
|
||||
specifier: ^10.1.2
|
||||
version: 10.1.2
|
||||
unist-util-visit:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
|
@ -66,6 +81,9 @@ devDependencies:
|
|||
'@types/reveal.js':
|
||||
specifier: ^4.4.2
|
||||
version: 4.4.2
|
||||
'@types/unist':
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6
|
||||
|
||||
packages:
|
||||
|
||||
|
@ -103,13 +121,13 @@ packages:
|
|||
vscode-uri: 3.0.7
|
||||
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==}
|
||||
peerDependencies:
|
||||
astro: ^2.2.0
|
||||
dependencies:
|
||||
'@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
|
||||
import-meta-resolve: 2.2.2
|
||||
rehype-raw: 6.1.1
|
||||
|
@ -126,11 +144,11 @@ packages:
|
|||
- supports-color
|
||||
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==}
|
||||
engines: {node: '>=16.12.0'}
|
||||
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
|
||||
'@mdx-js/mdx': 2.3.0
|
||||
'@mdx-js/rollup': 2.3.0(rollup@3.20.2)
|
||||
|
@ -160,7 +178,7 @@ packages:
|
|||
prismjs: 1.29.0
|
||||
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==}
|
||||
engines: {node: '>=16.12.0'}
|
||||
peerDependencies:
|
||||
|
@ -168,7 +186,7 @@ packages:
|
|||
svelte: ^3.54.0
|
||||
dependencies:
|
||||
'@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
|
||||
svelte2tsx: 0.5.23(svelte@3.58.0)(typescript@4.9.5)
|
||||
transitivePeerDependencies:
|
||||
|
@ -795,7 +813,7 @@ packages:
|
|||
magic-string: 0.30.0
|
||||
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)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -927,7 +945,6 @@ packages:
|
|||
|
||||
/@types/unist@2.0.6:
|
||||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: false
|
||||
|
||||
/@types/yargs-parser@21.0.0:
|
||||
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
|
||||
|
@ -1023,8 +1040,8 @@ packages:
|
|||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/astro@2.2.1(sass@1.61.0):
|
||||
resolution: {integrity: sha512-yYPRzh3su38bi3VBCKmYAUBkQSaFQMKFsu8JAVDzFRoGLbskJ/6JkDX2abSB9/iRug8GAKaH/FWxXOTzIsSQ7Q==}
|
||||
/astro@2.2.2(sass@1.62.0):
|
||||
resolution: {integrity: sha512-RmV/UiOW3losWRbR8CeFDFDtjBOdFdBTEccXF9sT/ZxLz7T9SglevtOHgKwTSRPgUM9c0LteFDFcHDY3OYBgDg==}
|
||||
engines: {node: '>=16.12.0', npm: '>=6.14.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
@ -1035,7 +1052,7 @@ packages:
|
|||
dependencies:
|
||||
'@astrojs/compiler': 1.3.1
|
||||
'@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/webapi': 2.1.0
|
||||
'@babel/core': 7.21.4
|
||||
|
@ -1071,7 +1088,7 @@ packages:
|
|||
preferred-pm: 3.0.3
|
||||
prompts: 2.4.2
|
||||
rehype: 12.0.1
|
||||
semver: 7.3.8
|
||||
semver: 7.4.0
|
||||
server-destroy: 1.0.1
|
||||
shiki: 0.11.1
|
||||
slash: 4.0.0
|
||||
|
@ -1082,7 +1099,7 @@ packages:
|
|||
typescript: 5.0.4
|
||||
unist-util-visit: 4.1.2
|
||||
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)
|
||||
yargs-parser: 21.1.1
|
||||
zod: 3.21.4
|
||||
|
@ -1144,7 +1161,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001477
|
||||
electron-to-chromium: 1.4.356
|
||||
electron-to-chromium: 1.4.359
|
||||
node-releases: 2.0.10
|
||||
update-browserslist-db: 1.0.10(browserslist@4.21.5)
|
||||
dev: false
|
||||
|
@ -1382,8 +1399,8 @@ packages:
|
|||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.356:
|
||||
resolution: {integrity: sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A==}
|
||||
/electron-to-chromium@1.4.359:
|
||||
resolution: {integrity: sha512-OoVcngKCIuNXtZnsYoqlCvr0Cf3NIPzDIgwUfI9bdTFjXCrr79lI0kwQstLPZ7WhCezLlGksZk/BFAzoXC7GDw==}
|
||||
dev: false
|
||||
|
||||
/emmet@2.4.2:
|
||||
|
@ -1846,8 +1863,8 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/is-core-module@2.11.0:
|
||||
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
|
||||
/is-core-module@2.12.0:
|
||||
resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==}
|
||||
dependencies:
|
||||
has: 1.0.3
|
||||
dev: false
|
||||
|
@ -2102,12 +2119,6 @@ packages:
|
|||
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==}
|
||||
dependencies:
|
||||
|
@ -3098,7 +3109,7 @@ packages:
|
|||
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
is-core-module: 2.11.0
|
||||
is-core-module: 2.12.0
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
dev: false
|
||||
|
@ -3190,8 +3201,8 @@ packages:
|
|||
suf-log: 2.5.3
|
||||
dev: false
|
||||
|
||||
/sass@1.61.0:
|
||||
resolution: {integrity: sha512-PDsN7BrVkNZK2+dj/dpKQAWZavbAQ87IXqVvw2+oEYI+GwlTWkvbQtL7F2cCNbMqJEYKPh1EcjSxsnqIb/kyaQ==}
|
||||
/sass@1.62.0:
|
||||
resolution: {integrity: sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
|
@ -3213,8 +3224,8 @@ packages:
|
|||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/semver@7.3.8:
|
||||
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
|
||||
/semver@7.4.0:
|
||||
resolution: {integrity: sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
|
@ -3642,7 +3653,7 @@ packages:
|
|||
vfile-message: 3.1.4
|
||||
dev: false
|
||||
|
||||
/vite@4.2.1(sass@1.61.0):
|
||||
/vite@4.2.1(sass@1.62.0):
|
||||
resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
@ -3671,7 +3682,7 @@ packages:
|
|||
postcss: 8.4.21
|
||||
resolve: 1.22.2
|
||||
rollup: 3.20.2
|
||||
sass: 1.61.0
|
||||
sass: 1.62.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: false
|
||||
|
@ -3684,7 +3695,7 @@ packages:
|
|||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 4.2.1(sass@1.61.0)
|
||||
vite: 4.2.1(sass@1.62.0)
|
||||
dev: false
|
||||
|
||||
/vscode-css-languageservice@6.2.4:
|
||||
|
|
|
@ -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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
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;
|
70
src/assets/reveal-ssr/markdown.ts
Normal file
70
src/assets/reveal-ssr/markdown.ts
Normal 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} = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
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('');
|
||||
}
|
|
@ -1,19 +1,24 @@
|
|||
import type { Node, Parent } from 'unist';
|
||||
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 group = /^\{(.+?)\}\((.+?)\)$/;
|
||||
const template = "<ruby>$1<rp>(</rp><rt>$2</rt><rp>)</rp></ruby>";
|
||||
|
||||
function toRuby(ruby: string) {
|
||||
return ({
|
||||
function toRuby(ruby: string): RubyNode {
|
||||
return {
|
||||
type: "html",
|
||||
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;
|
||||
|
||||
const text = node.value.split(regex).map(value => ({ type: "text", value}));
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'reveal.js/dist/theme/black.css';
|
|||
import 'reveal.js/plugin/highlight/monokai.css';
|
||||
import Base from '../../layouts/Base.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { compile } from '../../assets/reveal-ssr/markdown';
|
||||
|
||||
|
||||
export async function getStaticPaths () {
|
||||
|
@ -16,20 +17,17 @@ const { entry } = Astro.props;
|
|||
|
||||
<Base>
|
||||
<div class="reveal">
|
||||
<div class="slides">
|
||||
<section data-markdown data-separator="^\n-----\n$" data-separator-vertical="^\n---\n$">
|
||||
<textarea data-template>{entry.body}</textarea>
|
||||
</section>
|
||||
</div>
|
||||
<div class="slides" set:html={compile(entry.body)}></div>
|
||||
</div>
|
||||
<script>
|
||||
import Reveal from 'reveal.js';
|
||||
import Markdown from '../../assets/reveal-md';
|
||||
import Highlight from 'reveal.js/plugin/highlight/highlight';
|
||||
|
||||
Reveal.initialize({
|
||||
hash: true,
|
||||
plugins: [Markdown, Highlight]
|
||||
plugins: [
|
||||
Highlight,
|
||||
]
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Reference in a new issue