CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

| Download
Project: KOB1
Views: 16973
1
/**
2
* The reveal.js markdown plugin. Handles parsing of
3
* markdown inside of presentations as well as loading
4
* of external markdown documents.
5
*/
6
(function( root, factory ) {
7
if (typeof define === 'function' && define.amd) {
8
root.marked = require( './marked' );
9
root.RevealMarkdown = factory( root.marked );
10
root.RevealMarkdown.initialize();
11
} else if( typeof exports === 'object' ) {
12
module.exports = factory( require( './marked' ) );
13
} else {
14
// Browser globals (root is window)
15
root.RevealMarkdown = factory( root.marked );
16
root.RevealMarkdown.initialize();
17
}
18
}( this, function( marked ) {
19
20
var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
21
DEFAULT_NOTES_SEPARATOR = 'notes?:',
22
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
23
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
24
25
var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
26
27
28
/**
29
* Retrieves the markdown contents of a slide section
30
* element. Normalizes leading tabs/whitespace.
31
*/
32
function getMarkdownFromSlide( section ) {
33
34
// look for a <script> or <textarea data-template> wrapper
35
var template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
36
37
// strip leading whitespace so it isn't evaluated as code
38
var text = ( template || section ).textContent;
39
40
// restore script end tags
41
text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
42
43
var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
44
leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
45
46
if( leadingTabs > 0 ) {
47
text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
48
}
49
else if( leadingWs > 1 ) {
50
text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
51
}
52
53
return text;
54
55
}
56
57
/**
58
* Given a markdown slide section element, this will
59
* return all arguments that aren't related to markdown
60
* parsing. Used to forward any other user-defined arguments
61
* to the output markdown slide.
62
*/
63
function getForwardedAttributes( section ) {
64
65
var attributes = section.attributes;
66
var result = [];
67
68
for( var i = 0, len = attributes.length; i < len; i++ ) {
69
var name = attributes[i].name,
70
value = attributes[i].value;
71
72
// disregard attributes that are used for markdown loading/parsing
73
if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
74
75
if( value ) {
76
result.push( name + '="' + value + '"' );
77
}
78
else {
79
result.push( name );
80
}
81
}
82
83
return result.join( ' ' );
84
85
}
86
87
/**
88
* Inspects the given options and fills out default
89
* values for what's not defined.
90
*/
91
function getSlidifyOptions( options ) {
92
93
options = options || {};
94
options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
95
options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
96
options.attributes = options.attributes || '';
97
98
return options;
99
100
}
101
102
/**
103
* Helper function for constructing a markdown slide.
104
*/
105
function createMarkdownSlide( content, options ) {
106
107
options = getSlidifyOptions( options );
108
109
var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
110
111
if( notesMatch.length === 2 ) {
112
content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
113
}
114
115
// prevent script end tags in the content from interfering
116
// with parsing
117
content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
118
119
return '<script type="text/template">' + content + '</script>';
120
121
}
122
123
/**
124
* Parses a data string into multiple slides based
125
* on the passed in separator arguments.
126
*/
127
function slidify( markdown, options ) {
128
129
options = getSlidifyOptions( options );
130
131
var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
132
horizontalSeparatorRegex = new RegExp( options.separator );
133
134
var matches,
135
lastIndex = 0,
136
isHorizontal,
137
wasHorizontal = true,
138
content,
139
sectionStack = [];
140
141
// iterate until all blocks between separators are stacked up
142
while( matches = separatorRegex.exec( markdown ) ) {
143
notes = null;
144
145
// determine direction (horizontal by default)
146
isHorizontal = horizontalSeparatorRegex.test( matches[0] );
147
148
if( !isHorizontal && wasHorizontal ) {
149
// create vertical stack
150
sectionStack.push( [] );
151
}
152
153
// pluck slide content from markdown input
154
content = markdown.substring( lastIndex, matches.index );
155
156
if( isHorizontal && wasHorizontal ) {
157
// add to horizontal stack
158
sectionStack.push( content );
159
}
160
else {
161
// add to vertical stack
162
sectionStack[sectionStack.length-1].push( content );
163
}
164
165
lastIndex = separatorRegex.lastIndex;
166
wasHorizontal = isHorizontal;
167
}
168
169
// add the remaining slide
170
( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
171
172
var markdownSections = '';
173
174
// flatten the hierarchical stack, and insert <section data-markdown> tags
175
for( var i = 0, len = sectionStack.length; i < len; i++ ) {
176
// vertical
177
if( sectionStack[i] instanceof Array ) {
178
markdownSections += '<section '+ options.attributes +'>';
179
180
sectionStack[i].forEach( function( child ) {
181
markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
182
} );
183
184
markdownSections += '</section>';
185
}
186
else {
187
markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
188
}
189
}
190
191
return markdownSections;
192
193
}
194
195
/**
196
* Parses any current data-markdown slides, splits
197
* multi-slide markdown into separate sections and
198
* handles loading of external markdown.
199
*/
200
function processSlides() {
201
202
var sections = document.querySelectorAll( '[data-markdown]'),
203
section;
204
205
for( var i = 0, len = sections.length; i < len; i++ ) {
206
207
section = sections[i];
208
209
if( section.getAttribute( 'data-markdown' ).length ) {
210
211
var xhr = new XMLHttpRequest(),
212
url = section.getAttribute( 'data-markdown' );
213
214
datacharset = section.getAttribute( 'data-charset' );
215
216
// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
217
if( datacharset != null && datacharset != '' ) {
218
xhr.overrideMimeType( 'text/html; charset=' + datacharset );
219
}
220
221
xhr.onreadystatechange = function() {
222
if( xhr.readyState === 4 ) {
223
// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
224
if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
225
226
section.outerHTML = slidify( xhr.responseText, {
227
separator: section.getAttribute( 'data-separator' ),
228
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
229
notesSeparator: section.getAttribute( 'data-separator-notes' ),
230
attributes: getForwardedAttributes( section )
231
});
232
233
}
234
else {
235
236
section.outerHTML = '<section data-state="alert">' +
237
'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
238
'Check your browser\'s JavaScript console for more details.' +
239
'<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
240
'</section>';
241
242
}
243
}
244
};
245
246
xhr.open( 'GET', url, false );
247
248
try {
249
xhr.send();
250
}
251
catch ( e ) {
252
alert( '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 );
253
}
254
255
}
256
else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
257
258
section.outerHTML = slidify( getMarkdownFromSlide( section ), {
259
separator: section.getAttribute( 'data-separator' ),
260
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
261
notesSeparator: section.getAttribute( 'data-separator-notes' ),
262
attributes: getForwardedAttributes( section )
263
});
264
265
}
266
else {
267
section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
268
}
269
}
270
271
}
272
273
/**
274
* Check if a node value has the attributes pattern.
275
* If yes, extract it and add that value as one or several attributes
276
* the the terget element.
277
*
278
* You need Cache Killer on Chrome to see the effect on any FOM transformation
279
* directly on refresh (F5)
280
* http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
281
*/
282
function addAttributeInElement( node, elementTarget, separator ) {
283
284
var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
285
var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
286
var nodeValue = node.nodeValue;
287
if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
288
289
var classes = matches[1];
290
nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
291
node.nodeValue = nodeValue;
292
while( matchesClass = mardownClassRegex.exec( classes ) ) {
293
elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
294
}
295
return true;
296
}
297
return false;
298
}
299
300
/**
301
* Add attributes to the parent element of a text node,
302
* or the element of an attribute node.
303
*/
304
function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
305
306
if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
307
previousParentElement = element;
308
for( var i = 0; i < element.childNodes.length; i++ ) {
309
childElement = element.childNodes[i];
310
if ( i > 0 ) {
311
j = i - 1;
312
while ( j >= 0 ) {
313
aPreviousChildElement = element.childNodes[j];
314
if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
315
previousParentElement = aPreviousChildElement;
316
break;
317
}
318
j = j - 1;
319
}
320
}
321
parentSection = section;
322
if( childElement.nodeName == "section" ) {
323
parentSection = childElement ;
324
previousParentElement = childElement ;
325
}
326
if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
327
addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
328
}
329
}
330
}
331
332
if ( element.nodeType == Node.COMMENT_NODE ) {
333
if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
334
addAttributeInElement( element, section, separatorSectionAttributes );
335
}
336
}
337
}
338
339
/**
340
* Converts any current data-markdown slides in the
341
* DOM to HTML.
342
*/
343
function convertSlides() {
344
345
var sections = document.querySelectorAll( '[data-markdown]');
346
347
for( var i = 0, len = sections.length; i < len; i++ ) {
348
349
var section = sections[i];
350
351
// Only parse the same slide once
352
if( !section.getAttribute( 'data-markdown-parsed' ) ) {
353
354
section.setAttribute( 'data-markdown-parsed', true )
355
356
var notes = section.querySelector( 'aside.notes' );
357
var markdown = getMarkdownFromSlide( section );
358
359
section.innerHTML = marked( markdown );
360
addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) ||
361
section.parentNode.getAttribute( 'data-element-attributes' ) ||
362
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
363
section.getAttribute( 'data-attributes' ) ||
364
section.parentNode.getAttribute( 'data-attributes' ) ||
365
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
366
367
// If there were notes, we need to re-add them after
368
// having overwritten the section's HTML
369
if( notes ) {
370
section.appendChild( notes );
371
}
372
373
}
374
375
}
376
377
}
378
379
// API
380
return {
381
382
initialize: function() {
383
if( typeof marked === 'undefined' ) {
384
throw 'The reveal.js Markdown plugin requires marked to be loaded';
385
}
386
387
if( typeof hljs !== 'undefined' ) {
388
marked.setOptions({
389
highlight: function( code, lang ) {
390
return hljs.highlightAuto( code, [lang] ).value;
391
}
392
});
393
}
394
395
var options = Reveal.getConfig().markdown;
396
397
if ( options ) {
398
marked.setOptions( options );
399
}
400
401
processSlides();
402
convertSlides();
403
},
404
405
// TODO: Do these belong in the API?
406
processSlides: processSlides,
407
convertSlides: convertSlides,
408
slidify: slidify
409
410
};
411
412
}));
413
414