Skip to content

Instantly share code, notes, and snippets.

@mcsf
Created February 9, 2018 00:06
Show Gist options
  • Save mcsf/57a4f328ff20ace2b3423bb69c2bcc53 to your computer and use it in GitHub Desktop.
Save mcsf/57a4f328ff20ace2b3423bb69c2bcc53 to your computer and use it in GitHub Desktop.
diff --git a/blocks/api/raw-handling/markdown-converter.js b/blocks/api/raw-handling/markdown-converter.js
new file mode 100644
index 00000000..a2d3fa63
--- /dev/null
+++ b/blocks/api/raw-handling/markdown-converter.js
@@ -0,0 +1,77 @@
+/**
+ * External dependencies
+ */
+import showdown from 'showdown';
+
+export default class MarkdownConverter {
+ makeHtml( html ) {
+ // Oh, the horror!
+ this.converter = makeConverter( html );
+ return this.converter.makeHtml( html );
+ }
+}
+
+function makeConverter( html ) {
+ let options = {};
+ const tags = collectShortcodes( html );
+ if ( tags.length ) {
+ const regex = tagsToRegex( collectShortcodes( html ) );
+ showdown.extension( 'ignoreShortcodes', ignoreShortcodes( regex ) );
+ options.extensions = [ 'ignoreShortcodes' ];
+ }
+ const converter = new showdown.Converter( options );
+ converter.setOption( 'noHeaderId', true );
+ converter.setOption( 'tables', true );
+ converter.setOption( 'literalMidWordUnderscores', true );
+ return converter;
+}
+
+function collectShortcodes( html ) {
+ let match;
+ const tags = [];
+ const re = /\[\/([a-z][a-z0-9_-]*)\]/g;
+ while ( ( match = re.exec( html ) ) !== null ) {
+ tags.push( match[ 1 ] );
+ }
+ return tags;
+}
+
+function tagsToRegex( tags ) {
+ return new RegExp(
+ '(' +
+ // TODO regex-escape tag
+ tags.map( tag => (
+ `\\[${tag}\\](.*)\\[\\/${tag}\\]`
+ ) ).join( '|' ) +
+ ')',
+ 'g'
+ );
+}
+
+function ignoreShortcodes( regex ) {
+ return function() {
+ let matches = [];
+ return [
+ {
+ type: 'lang',
+ regex,
+ replace( string ) {
+ matches.push( string );
+ const n = matches.length - 1;
+ return '%PLACEHOLDER' + n + '%';
+ },
+ },
+ {
+ type: 'output',
+ filter(text) {
+ for (let i=0; i< matches.length; ++i) {
+ const pat = '%PLACEHOLDER' + i + '% *';
+ text = text.replace(new RegExp(pat, 'g'), matches[i]);
+ }
+ matches = [];
+ return text;
+ },
+ }
+ ];
+ };
+}
diff --git a/blocks/api/raw-handling/test/markdown-converter.js b/blocks/api/raw-handling/test/markdown-converter.js
new file mode 100644
index 00000000..23882eff
--- /dev/null
+++ b/blocks/api/raw-handling/test/markdown-converter.js
@@ -0,0 +1,38 @@
+/**
+ * External dependencies
+ */
+import { equal } from 'assert';
+
+/**
+ * Internal dependencies
+ */
+import MarkdownConverter from '../markdown-converter';
+
+describe( 'MarkdownConverter', () => {
+ describe( 'when using shortcode extension', () => {
+ const converter = new MarkdownConverter();
+
+ it( 'should ignore formatting inside blacklisted shortcodes', () => {
+ const input1 = '[my_shortcode]hello, _there_.[/my_shortcode]';
+ const output1 = '<p>[my_shortcode]hello, _there_.[/my_shortcode]</p>';
+ equal( converter.makeHtml( input1 ), output1 );
+
+ const input2 = '[other_shortcode]hello, _there_.[/other_shortcode]';
+ const output2 = '<p>[other_shortcode]hello, _there_.[/other_shortcode]</p>';
+ equal( converter.makeHtml( input2 ), output2 );
+ } );
+
+ it( 'should do the same for multi-occurrence strings', () => {
+ const input = '[my_shortcode]hello, _there_.[/my_shortcode] [other_shortcode]hello, _there_.[/other_shortcode]';
+
+ const output = '<p>[my_shortcode]hello, _there_.[/my_shortcode][other_shortcode]hello, _there_.[/other_shortcode]</p>';
+ equal( converter.makeHtml( input ), output );
+ } );
+
+ it( 'should ignore content around unclosed shortcode tags', () => {
+ const input = '**oh**, [unknown]hello, _there_.';
+ const output = '<p><strong>oh</strong>, [unknown]hello, <em>there</em>.</p>';
+ equal( converter.makeHtml( input ), output );
+ } );
+ } );
+} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment