Skip to content

Commit

Permalink
added a short code parse for the editor previews
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsekaean committed Nov 2, 2017
1 parent 10f523a commit 2d98e6b
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 31 deletions.
23 changes: 12 additions & 11 deletions client/dist/bundle.min.js

Large diffs are not rendered by default.

34 changes: 26 additions & 8 deletions client/src/components/MarkdownEditorField/MarkdownEditorField.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ const SimpleMDE = require('simplemde');
import ReactSimpleMDE from 'react-simplemde-editor';
import { provideInjector } from 'lib/Injector';
import jQuery from 'jquery';

import ShortcodeParser from '../ShortCodeParser/ShortcodeParser';
const parser = new ShortcodeParser();

var ss = typeof window.ss !== 'undefined' ? window.ss : {};
if(typeof ss.markdownConfigs == 'undefined') {
ss.markdownConfigs = {};
}


ss.markdownConfigs.readToolbarConfigs = function(feed) {
let data = JSON.parse(feed);
ss.markdownConfigs.readToolbarConfigs = function(data) {
let toolbar = [];

for (var key in data) {
var element = data[key];
if(typeof element == 'string') {
Expand Down Expand Up @@ -48,6 +47,14 @@ ss.markdownConfigs.readToolbarConfigs = function(feed) {
return toolbar;
}

parser.registerShortCode('image_link', function(buffer, opts) {
return opts.url;
});


parser.registerShortCode('embed', function(buffer, opts) {
return '<img src="' + opts.thumbnail + '" width="' + opts.width + '" height="' + opts.height + '">';
});


class MarkdownEditorField extends React.Component {
Expand All @@ -60,20 +67,30 @@ class MarkdownEditorField extends React.Component {
this.props.textarea.value = value;
}

previewRender(plainText){
let parsedText = parser.parse(plainText);
return this.parent.markdown(parsedText);
}

static addCustomAction(key, action) {
ss.markdownConfigs[key] = action;
};

static registerShortCodes(key, callback) {

}

render() {
return (<div className="editor-container">
<ReactSimpleMDE
value = {this.props.textarea.value}
onChange={this.handleChange.bind(this)}
options={{
spellChecker: true,
dragDrop: false,
keyMap: "sublime",
toolbar: this.props.toolbar
dragDrop : false,
keyMap : "sublime",
toolbar : this.props.toolbar,
previewRender: this.previewRender
}}
></ReactSimpleMDE>
</div>);
Expand Down Expand Up @@ -130,7 +147,8 @@ jQuery.entwine('ss', ($) => {
},
refresh() {
let textArea = $(this).parent().find('textarea')[0];
let toolbar = ss.markdownConfigs.readToolbarConfigs(textArea.dataset.config);
let data = JSON.parse(textArea.dataset.config);
let toolbar = ss.markdownConfigs.readToolbarConfigs(data.toolbar);

ReactDOM.render(
<MarkdownEditorField textarea={textArea} toolbar={toolbar}></MarkdownEditorField>,
Expand Down
136 changes: 136 additions & 0 deletions client/src/components/ShortCodeParser/ShortcodeParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
'use strict';


var util = require('util');

const SHORTCODE_ATTRS = /(\s+([a-z0-9\-_]+|([a-z0-9\-_]+)\s*=\s*([a-z0-9\-_]+|\d+\.\d+|'[^']*'|"[^"]*")))*/.toString().slice(1,-1);
const SHORTCODE_SLASH = /\s*\/?\s*/.toString().slice(1,-1);
const SHORTCODE_OPEN = /\[\s*%s/.toString().slice(1,-1);
const SHORTCODE_RIGHT_BRACKET = '\\]';
const SHORTCODE_CLOSE = /\[\s*\/\s*%s\s*\]/.toString().slice(1,-1);
const SHORTCODE_CONTENT = /(.|\n|)*?/.toString().slice(1,-1);
const SHORTCODE_SPACE = /\s*/.toString().slice(1,-1);


class ShortcodeParser {

construct() {
this.shortCodes = {};
}

registerShortCode(key, callback) {
this.shortCodes[key] = callback;
}

typecast(val) {
val = val.trim().replace(/(^['"]|['"]$)/g, '');
if (/^\d+$/.test(val)) {
return parseInt(val, 10);
} else if (/^\d+\.\d+$/.test(val)) {
return parseFloat(val);
} else if (/^(true|false)$/.test(val)) {
return (val === 'true');
} else if (/^undefined$/.test(val)) {
return undefined;
} else if (/^null$/i.test(val)) {
return null;
} else {
return val;
}
}

closeTagString(name) {
return /^[^a-z0-9]/.test(name) ? util.format('[%s]?%s', name[0].replace('$', '\\$'), name.slice(1)) : name;
}


parseShortcode(name, buf, inline) {
var regex, match, data = {}, attr = {};

if (inline) {
regex = new RegExp('^' + util.format(SHORTCODE_OPEN, name)
+ SHORTCODE_ATTRS
+ SHORTCODE_SPACE
+ SHORTCODE_SLASH
+ SHORTCODE_RIGHT_BRACKET, 'i');
} else {
regex = new RegExp('^' + util.format(SHORTCODE_OPEN, name)
+ SHORTCODE_ATTRS
+ SHORTCODE_SPACE
+ SHORTCODE_RIGHT_BRACKET, 'i');
}

while ((match = buf.match(regex)) !== null) {
var key = match[3] || match[2];
var val = match[4] || match[3];
var pattern = match[1];
if (pattern) {
var idx = buf.lastIndexOf(pattern);
attr[key] = (val !== undefined) ? this.typecast(val) : true;
buf = buf.slice(0, idx) + buf.slice(idx + pattern.length);
} else {
break;
}
}

attr = Object.keys(attr).reverse().reduce(function(prev, current) {
prev[current] = attr[current]; return prev;
}, {});

buf = buf.replace(regex, '').replace(new RegExp(util.format(SHORTCODE_CLOSE, this.closeTagString(name))), '');

return {
attr: attr,
content: inline ? buf : buf.replace(/(^\n|\n$)/g, '')
}

}

parse(plainText) {

for (var name in this.shortCodes) {
var regex = {
wrapper: new RegExp(util.format(SHORTCODE_OPEN, name)
+ SHORTCODE_ATTRS
+ SHORTCODE_RIGHT_BRACKET
+ SHORTCODE_CONTENT
+ util.format(SHORTCODE_CLOSE, this.closeTagString(name)), 'gi'),
inline: new RegExp(util.format(SHORTCODE_OPEN, name)
+ SHORTCODE_ATTRS
+ SHORTCODE_SLASH
+ SHORTCODE_RIGHT_BRACKET, 'gi')
}


let matches = plainText.match(regex.wrapper);

if (matches) {
for (let m,data,i=0,len=matches.length; i < len; i++) {
m = matches[i];
data = this.parseShortcode(name, m);
plainText = plainText.replace(m, this.shortCodes[name].call(null, data.content, data.attr));
}
}

matches = plainText.match(regex.inline);
if (matches) {
let m = null;
while((m = matches.shift()) !== undefined) {
let data = this.parseShortcode(name, m, true);
plainText = plainText.replace(m, this.shortCodes[name].call(null, data.content, data.attr));
}

}


}

return plainText;
}

}


ShortcodeParser.prototype.shortCodes = {};

export default ShortcodeParser;
3 changes: 1 addition & 2 deletions client/src/entwine/Markdown_ssmedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,9 @@ jQuery.entwine('ss', ($) => {
}
const data = this.getData();
const extraData = this.getExtraData();

let markdown = '!['
+ (extraData.CaptionText ? extraData.CaptionText : data.title)
+ ']([image_link id=' + data.ID +' width=' + data.width + ' height=' + data.height + '] "'
+ ']([image_link id=' + data.ID +' width=' + data.InsertWidth + ' height=' + data.InsertHeight + ' url=\''+ data.url +'\'] "'
+ data.title
+ '")';

Expand Down
4 changes: 1 addition & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-simplemde-editor": "3.6.11",
"bootstrap": "4.0.0-beta"
"bootstrap": "4.0.0-beta",
"util": "0.10.3"
},
"devDependencies": {
"@silverstripe/webpack-config": "0.3.0",
Expand Down
6 changes: 4 additions & 2 deletions src/db/MarkdownText.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ public function ParseMarkdown($bCache = true, $strValue = '')

// shortcodes
$regexes = [
'/\[image_link*\s[a-z|A-Z|0-9\s\=]*\]/',
'/\[file_link\,[a-z|A-Z|0-9\s\=]*\]/'
'/\[image_link(.+?)\]/',
'/\[file_link(.+?)\]/'
];

foreach ($regexes as $pattern) {
Expand All @@ -79,6 +79,8 @@ public function ParseMarkdown($bCache = true, $strValue = '')
}
}



$parseDown = new GithubMarkdown();
$parsed = $parseDown->parse($parsed);

Expand Down
45 changes: 41 additions & 4 deletions src/forms/MarkdownEditorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Control\Director;
use SilverStripe\Core\Manifest\ModuleResourceLoader;
use SilverStripe\View\SSViewer;
use SilverStripe\View\ThemeResourceLoader;

class MarkdownEditorConfig
{
Expand All @@ -23,9 +27,10 @@ class MarkdownEditorConfig

protected static $configs = [];
protected static $current;
private static $editor_css = [];
private static $default_config = 'default';

protected $settings = [
protected $toolbar = [
[
'name' => 'heading',
'className' => 'fa fa-header',
Expand Down Expand Up @@ -173,20 +178,52 @@ public static function set_config($identifier, MarkdownEditorConfig $config = nu
return $config;
}


/**
* @return array
*/
protected function getEditorCSS()
{
$editor = array();

// Add standard editor.css
$editorCSSFiles = $this->config()->get('editor_css');
if ($editorCSSFiles) {
foreach ($editorCSSFiles as $editorCSS) {
$path = ModuleResourceLoader::singleton()
->resolveURL($editorCSS);
$editor[] = Director::absoluteURL($path);
}
}

// Themed editor.css
$themes = SSViewer::get_themes();
$themedEditor = ThemeResourceLoader::inst()->findThemedCSS('editor', $themes);
if ($themedEditor) {
$editor[] = Director::absoluteURL($themedEditor);
}

return $editor;
}


/**
* @return array
*/
public function getConfig()
{
return $this->settings;
return [
'toolbar' => $this->toolbar,
'editor_css' => $this->getEditorCSS()
];
}

/**
* @return MarkdownEditorConfig
*/
public function addSeparator()
{
array_push($this->settings, '|');
array_push($this->toolbar, '|');
return $this;
}

Expand All @@ -196,7 +233,7 @@ public function addSeparator()
*/
public function addButton($button)
{
array_push($this->settings, $button);
array_push($this->toolbar, $button);
return $this;
}

Expand Down

0 comments on commit 2d98e6b

Please sign in to comment.