Stackedit/src/extensions/markdownExt.js
2017-07-25 09:19:39 +01:00

198 lines
5.9 KiB
JavaScript

import Prism from 'prismjs';
import markdownitAbbr from 'markdown-it-abbr';
import markdownitDeflist from 'markdown-it-deflist';
import markdownitFootnote from 'markdown-it-footnote';
import markdownitSub from 'markdown-it-sub';
import markdownitSup from 'markdown-it-sup';
import extensionSvc from '../services/extensionSvc';
const coreBaseRules = [
'normalize',
'block',
'inline',
'linkify',
'replacements',
'smartquotes',
];
const blockBaseRules = [
'code',
'fence',
'blockquote',
'hr',
'list',
'reference',
'heading',
'lheading',
'html_block',
'table',
'paragraph',
];
const inlineBaseRules = [
'text',
'newline',
'escape',
'backticks',
'strikethrough',
'emphasis',
'link',
'image',
'autolink',
'html_inline',
'entity',
];
const inlineBaseRules2 = [
'balance_pairs',
'strikethrough',
'emphasis',
'text_collapse',
];
extensionSvc.onGetOptions((options, properties) => {
options.abbr = properties['ext:markdown:abbr'] !== 'false';
options.breaks = properties['ext:markdown:breaks'] === 'true';
options.deflist = properties['ext:markdown:deflist'] !== 'false';
options.del = properties['ext:markdown:del'] !== 'false';
options.fence = properties['ext:markdown:fence'] !== 'false';
options.footnote = properties['ext:markdown:footnote'] !== 'false';
options.linkify = properties['ext:markdown:linkify'] !== 'false';
options.sub = properties['ext:markdown:sub'] !== 'false';
options.sup = properties['ext:markdown:sup'] !== 'false';
options.table = properties['ext:markdown:table'] !== 'false';
options.typographer = properties['ext:markdown:typographer'] !== 'false';
});
extensionSvc.onInitConverter(0, (markdown, options) => {
markdown.set({
html: true,
breaks: !!options.breaks,
linkify: !!options.linkify,
typographer: !!options.typographer,
langPrefix: 'prism language-',
});
markdown.core.ruler.enable(coreBaseRules);
const blockRules = blockBaseRules.slice();
if (!options.fence) {
blockRules.splice(blockRules.indexOf('fence'), 1);
}
if (!options.table) {
blockRules.splice(blockRules.indexOf('table'), 1);
}
markdown.block.ruler.enable(blockRules);
const inlineRules = inlineBaseRules.slice();
const inlineRules2 = inlineBaseRules2.slice();
if (!options.del) {
inlineRules.splice(blockRules.indexOf('strikethrough'), 1);
inlineRules2.splice(blockRules.indexOf('strikethrough'), 1);
}
markdown.inline.ruler.enable(inlineRules);
markdown.inline.ruler2.enable(inlineRules2);
if (options.abbr) {
markdown.use(markdownitAbbr);
}
if (options.deflist) {
markdown.use(markdownitDeflist);
}
if (options.footnote) {
markdown.use(markdownitFootnote);
}
if (options.sub) {
markdown.use(markdownitSub);
}
if (options.sup) {
markdown.use(markdownitSup);
}
markdown.core.ruler.before('replacements', 'anchors', (state) => {
const anchorHash = {};
let headingOpenToken;
let headingContent;
state.tokens.cl_each((token) => {
if (token.type === 'heading_open') {
headingContent = '';
headingOpenToken = token;
} else if (token.type === 'heading_close') {
headingOpenToken.headingContent = headingContent;
// Slugify according to http://pandoc.org/README.html#extension-auto_identifiers
let slug = headingContent
.replace(/\s/g, '-') // Replace all spaces and newlines with hyphens
.replace(/[\0-,/:-@[-^`{-~]/g, '') // Remove all punctuation, except underscores, hyphens, and periods
.toLowerCase(); // Convert all alphabetic characters to lowercase
// Remove everything up to the first letter
let i;
for (i = 0; i < slug.length; i += 1) {
const charCode = slug.charCodeAt(i);
if ((charCode >= 0x61 && charCode <= 0x7A) || charCode > 0x7E) {
break;
}
}
// If nothing left after this, use `section`
slug = slug.slice(i) || 'section';
let anchor = slug;
let index = 1;
while (Object.prototype.hasOwnProperty.call(anchorHash, anchor)) {
anchor = `${slug}-${index}`;
index += 1;
}
anchorHash[anchor] = true;
headingOpenToken.headingAnchor = anchor;
headingOpenToken.attrs = [
['id', anchor],
];
headingOpenToken = undefined;
} else if (headingOpenToken) {
headingContent += token.children.cl_reduce((result, child) => {
if (child.type !== 'footnote_ref') {
return result + child.content;
}
return result;
}, '');
}
});
});
// Wrap tables into a div for scrolling
markdown.renderer.rules.table_open = (tokens, idx, opts) =>
`<div class="table-wrapper">${markdown.renderer.renderToken(tokens, idx, opts)}`;
markdown.renderer.rules.table_close = (tokens, idx, opts) =>
`${markdown.renderer.renderToken(tokens, idx, opts)}</div>`;
// Transform style into align attribute to pass the HTML sanitizer
const textAlignLength = 'text-align:'.length;
markdown.renderer.rules.td_open = (tokens, idx, opts) => {
const token = tokens[idx];
if (token.attrs && token.attrs.length && token.attrs[0][0] === 'style') {
token.attrs = [
['align', token.attrs[0][1].slice(textAlignLength)],
];
}
return markdown.renderer.renderToken(tokens, idx, opts);
};
markdown.renderer.rules.th_open = markdown.renderer.rules.td_open;
markdown.renderer.rules.footnote_ref = (tokens, idx) => {
const n = Number(tokens[idx].meta.id + 1).toString();
let id = `fnref${n}`;
if (tokens[idx].meta.subId > 0) {
id += `:${tokens[idx].meta.subId}`;
}
return `<sup class="footnote-ref"><a href="#fn${n}" id="${id}">${n}</a></sup>`;
};
});
extensionSvc.onSectionPreview((elt) => {
elt.querySelectorAll('.prism').cl_each((prismElt) => {
if (!prismElt.highlighted) {
Prism.highlightElement(prismElt);
}
prismElt.highlighted = true;
});
});