aboutsummaryrefslogtreecommitdiffhomepage
path: root/presentation/plugin/markdown/markdown.js
diff options
context:
space:
mode:
Diffstat (limited to 'presentation/plugin/markdown/markdown.js')
-rwxr-xr-xpresentation/plugin/markdown/markdown.js436
1 files changed, 436 insertions, 0 deletions
diff --git a/presentation/plugin/markdown/markdown.js b/presentation/plugin/markdown/markdown.js
new file mode 100755
index 0000000..c800bce
--- /dev/null
+++ b/presentation/plugin/markdown/markdown.js
@@ -0,0 +1,436 @@
+/**
+ * The reveal.js markdown plugin. Handles parsing of
+ * markdown inside of presentations as well as loading
+ * of external markdown documents.
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ root.marked = require('./marked');
+ root.RevealMarkdown = factory(root.marked);
+ } else if (typeof exports === 'object') {
+ module.exports = factory(require('./marked'));
+ } else {
+ // Browser globals (root is window)
+ root.RevealMarkdown = factory(root.marked);
+ }
+}(this, function (marked) {
+
+ var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
+ DEFAULT_NOTES_SEPARATOR = 'notes?:',
+ DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
+ DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
+
+ var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
+
+
+ /**
+ * 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'), '\n');
+ } else if (leadingWs > 1) {
+ text = text.replace(new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n');
+ }
+
+ 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)) {
+ 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() {
+
+ return new Promise(function (resolve) {
+
+ var externalPromises = [];
+
+ [].slice.call(document.querySelectorAll('[data-markdown]')).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 if (section.getAttribute('data-separator') || section.getAttribute('data-separator-vertical') || section.getAttribute('data-separator-notes')) {
+
+ 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)
+ });
+
+ } else {
+ section.innerHTML = createMarkdownSlide(getMarkdownFromSlide(section));
+ }
+
+ });
+
+ Promise.all(externalPromises).then(resolve);
+
+ });
+
+ }
+
+ function loadExternalMarkdown(section) {
+
+ return new Promise(function (resolve, reject) {
+
+ var xhr = new XMLHttpRequest(),
+ url = section.getAttribute('data-markdown');
+
+ 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) {
+ 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);
+ 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("([^\"= ]+?)=\"([^\"=]+?)\"", 'mg');
+ var nodeValue = node.nodeValue;
+ 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)) {
+ elementTarget.setAttribute(matchesClass[1], matchesClass[2]);
+ }
+ 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) {
+ previousParentElement = element;
+ for (var i = 0; i < element.childNodes.length; i++) {
+ childElement = element.childNodes[i];
+ if (i > 0) {
+ j = i - 1;
+ while (j >= 0) {
+ aPreviousChildElement = element.childNodes[j];
+ if (typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR") {
+ previousParentElement = aPreviousChildElement;
+ break;
+ }
+ j = j - 1;
+ }
+ }
+ 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 = document.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();
+
+ }
+
+ // API
+ var RevealMarkdown = {
+
+ /**
+ * Starts processing and converting Markdown within the
+ * current reveal.js deck.
+ *
+ * @param {function} callback function to invoke once
+ * we've finished loading and parsing Markdown
+ */
+ init: function (callback) {
+
+ if (typeof marked === 'undefined') {
+ throw 'The reveal.js Markdown plugin requires marked to be loaded';
+ }
+
+ if (typeof hljs !== 'undefined') {
+ marked.setOptions({
+ highlight: function (code, lang) {
+ return hljs.highlightAuto(code, [lang]).value;
+ }
+ });
+ }
+
+ // marked can be configured via reveal.js config options
+ var options = Reveal.getConfig().markdown;
+ if (options) {
+ marked.setOptions(options);
+ }
+
+ return processSlides().then(convertSlides);
+
+ },
+
+ // TODO: Do these belong in the API?
+ processSlides: processSlides,
+ convertSlides: convertSlides,
+ slidify: slidify
+
+ };
+
+ // Register our plugin so that reveal.js will call our
+ // plugin 'init' method as part of the initialization
+ Reveal.registerPlugin('markdown', RevealMarkdown);
+
+ return RevealMarkdown;
+
+}));