Merge pull request #5213 from open-sausages/pulls/4.0/fix-tinymce-shortcodes

API and BUG fix; Update ssbuttons tinymce plugin for image shortcode parsing
This commit is contained in:
Ingo Schommer 2016-03-21 14:33:37 +13:00
commit 3573236310
5 changed files with 181 additions and 35 deletions

View File

@ -32,7 +32,7 @@ HtmlEditorConfig::get('cms')
->enablePlugins(array(
'contextmenu' => null,
'image' => null,
'ssbuttons' => THIRDPARTY_DIR . '/tinymce_ssbuttons/plugin.min.js'
'ssbuttons' => FRAMEWORK_DIR . '/javascript/dist/TinyMCE_SSPlugin.js'
));
CMSMenu::remove_menu_item('CMSProfileController');

View File

@ -20,7 +20,7 @@ var gulp = require('gulp'),
sprity = require('sprity'),
gulpif = require('gulp-if'),
sourcemaps = require('gulp-sourcemaps');
var isDev = typeof process.env.npm_config_development !== 'undefined';
var PATHS = {
@ -273,27 +273,6 @@ gulp.task('thirdparty', function () {
copyFiles(blueimpTmplConfig);
copyFiles(jquerySizesConfig);
copyFiles(tinymceConfig);
// TODO Remove once all TinyMCE plugins are bundled
var stream = browserify(Object.assign({}, browserifyOptions, {
entries: PATHS.FRAMEWORK_THIRDPARTY + '/tinymce_ssbuttons/plugin.js'
}))
.transform(babelify.configure({
presets: ['es2015'],
comments: false
}))
.bundle()
.on('error', notify.onError({
message: 'Error: <%= error.message %>',
}))
.pipe(source('plugin.min.js'))
.pipe(buffer());
if (!isDev) {
stream.pipe(uglify());
}
return stream.pipe(gulp.dest(PATHS.FRAMEWORK_THIRDPARTY + '/tinymce_ssbuttons/'));
});
gulp.task('umd', ['umd-admin', 'umd-framework']);

173
javascript/dist/TinyMCE_SSPlugin.js vendored Normal file
View File

@ -0,0 +1,173 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define('ss.TinyMCE_SSPlugin', [], factory);
} else if (typeof exports !== "undefined") {
factory();
} else {
var mod = {
exports: {}
};
factory();
global.ssTinyMCE_SSPlugin = mod.exports;
}
})(this, function () {
'use strict';
(function () {
var ssbuttons = {
getInfo: function getInfo() {
return {
longname: 'Special buttons for SilverStripe CMS',
author: 'Sam Minnée',
authorurl: 'http://www.siverstripe.com/',
infourl: 'http://www.silverstripe.com/',
version: "1.0"
};
},
init: function init(ed) {
ed.addButton('sslink', {
icon: 'link',
title: 'Insert Link',
cmd: 'sslink'
});
ed.addMenuItem('sslink', {
icon: 'link',
text: 'Insert Link',
cmd: 'sslink'
});
ed.addButton('ssmedia', {
icon: 'image',
title: 'Insert Media',
cmd: 'ssmedia'
});
ed.addMenuItem('ssmedia', {
icon: 'image',
text: 'Insert Media',
cmd: 'ssmedia'
});
ed.addCommand('sslink', function (ed) {
jQuery('#' + this.id).entwine('ss').openLinkDialog();
});
ed.addCommand('ssmedia', function (ed) {
jQuery('#' + this.id).entwine('ss').openMediaDialog();
});
ed.on('BeforeExecCommand', function (e) {
var cmd = e.command;
var ui = e.ui;
var val = e.value;
if (cmd == 'mceAdvLink' || cmd == 'mceLink') {
e.preventDefault();
ed.execCommand('sslink', ui, val);
} else if (cmd == 'mceAdvImage' || cmd == 'mceImage') {
e.preventDefault();
ed.execCommand('ssmedia', ui, val);
}
});
ed.on('SaveContent', function (o) {
var content = jQuery(o.content);
var attrsFn = function attrsFn(attrs) {
return Object.keys(attrs).map(function (name) {
return attrs[name] ? name + '="' + attrs[name] + '"' : null;
}).filter(function (el) {
return el !== null;
}).join(' ');
};
content.find('.ss-htmleditorfield-file.embed').each(function () {
var el = jQuery(this);
var attrs = {
width: el.attr('width'),
class: el.attr('cssclass'),
thumbnail: el.data('thumbnail')
};
var shortCode = '[embed ' + attrsFn(attrs) + ']' + el.data('url') + '[/embed]';
el.replaceWith(shortCode);
});
content.find('img').each(function () {
var el = jQuery(this);
var attrs = {
src: el.attr('src'),
id: el.data('id'),
width: el.attr('width'),
height: el.attr('height'),
class: el.attr('class'),
title: el.attr('title'),
alt: el.attr('alt')
};
var shortCode = '[image ' + attrsFn(attrs) + ']';
el.replaceWith(shortCode);
});
o.content = '';
content.each(function () {
if (this.outerHTML !== undefined) {
o.content += this.outerHTML;
}
});
});
ed.on('BeforeSetContent', function (o) {
var matches;
var content = o.content;
var attrFromStrFn = function attrFromStrFn(str) {
return str.match(/([^\s\/'"=,]+)\s*=\s*(('([^']+)')|("([^"]+)")|([^\s,\]]+))/g).reduce(function (coll, val) {
var match = val.match(/^([^\s\/'"=,]+)\s*=\s*(?:(?:'([^']+)')|(?:"([^"]+)")|(?:[^\s,\]]+))$/),
key = match[1],
value = match[2] || match[3] || match[4];
coll[key] = value;
return coll;
}, {});
};
var shortTagRegex = /\[embed(.*?)\](.+?)\[\/\s*embed\s*\]/gi;
while (matches = shortTagRegex.exec(content)) {
var attrs = attrFromStrFn(matches[1]);
var el;
el = jQuery('<img/>').attr({
'src': attrs['thumbnail'],
'width': attrs['width'],
'height': attrs['height'],
'class': attrs['class'],
'data-url': matches[2]
}).addClass('ss-htmleditorfield-file embed');
attrs['cssclass'] = attrs['class'];
Object.keys(attrs).forEach(function (key) {
return el.attr('data-' + key, attrs[key]);
});
content = content.replace(matches[0], jQuery('<div/>').append(el).html());
}
var shortTagRegex = /\[image(.*?)\]/gi;
while (matches = shortTagRegex.exec(content)) {
var attrs = attrFromStrFn(matches[1]);
var el = jQuery('<img/>').attr({
'src': attrs['src'],
'width': attrs['width'],
'height': attrs['height'],
'class': attrs['class'],
'alt': attrs['alt'],
'title': attrs['title'],
'data-id': attrs['id']
});
content = content.replace(matches[0], jQuery('<div/>').append(el).html());
}
o.content = content;
});
}
};
tinymce.PluginManager.add("ssbuttons", function (editor) {
ssbuttons.init(editor);
});
})();
});

View File

@ -91,7 +91,6 @@
content.find('img').each(function() {
var el = jQuery(this);
var attrs = {
// TODO Don't store 'src' since its more volatile than 'id'.
// Requires server-side preprocessing of HTML+shortcodes in HTMLValue
src: el.attr('src'),
id: el.data('id'),
@ -121,15 +120,13 @@
var content = o.content;
var attrFromStrFn = (str) => {
return str
// Remove quotation marks and trim.
.replace(/['"]/g, '')
.replace(/(^\s+|\s+$)/g, '')
// Extract the attrs and values into a key-value array,
// or key-key if no value is set.
.split(/\s+/)
// Split on all attributes, quoted or not
.match(/([^\s\/'"=,]+)\s*=\s*(('([^']+)')|("([^"]+)")|([^\s,\]]+))/g)
.reduce((coll, val) => {
var pair = val.split('=');
coll[pair[0]] = (pair.length == 1) ? pair[0] : pair[1];
var match = val.match(/^([^\s\/'"=,]+)\s*=\s*(?:(?:'([^']+)')|(?:"([^"]+)")|(?:[^\s,\]]+))$/),
key = match[1],
value = match[2] || match[3] || match[4]; // single, double, or unquoted match
coll[key] = value;
return coll;
}, {});
};
@ -166,8 +163,6 @@
'title': attrs['title'],
'data-id': attrs['id']
});
Object.keys(attrs).forEach((key) => el.attr('data-' + key, attrs[key]));
content = content.replace(matches[0], (jQuery('<div/>').append(el).html()));
}

View File

@ -1 +0,0 @@
!function t(e,n,i){function r(s,c){if(!n[s]){if(!e[s]){var o="function"==typeof require&&require;if(!c&&o)return o(s,!0);if(a)return a(s,!0);var d=new Error("Cannot find module '"+s+"'");throw d.code="MODULE_NOT_FOUND",d}var u=n[s]={exports:{}};e[s][0].call(u.exports,function(t){var n=e[s][1][t];return r(n?n:t)},u,u.exports,t,e,n,i)}return n[s].exports}for(var a="function"==typeof require&&require,s=0;s<i.length;s++)r(i[s]);return r}({1:[function(t,e,n){"use strict";!function(){var t={getInfo:function(){return{longname:"Special buttons for SilverStripe CMS",author:"Sam Minnée",authorurl:"http://www.siverstripe.com/",infourl:"http://www.silverstripe.com/",version:"1.0"}},init:function(t){t.addButton("sslink",{icon:"link",title:"Insert Link",cmd:"sslink"}),t.addMenuItem("sslink",{icon:"link",text:"Insert Link",cmd:"sslink"}),t.addButton("ssmedia",{icon:"image",title:"Insert Media",cmd:"ssmedia"}),t.addMenuItem("ssmedia",{icon:"image",text:"Insert Media",cmd:"ssmedia"}),t.addCommand("sslink",function(t){jQuery("#"+this.id).entwine("ss").openLinkDialog()}),t.addCommand("ssmedia",function(t){jQuery("#"+this.id).entwine("ss").openMediaDialog()}),t.on("BeforeExecCommand",function(e){var n=e.command,i=e.ui,r=e.value;"mceAdvLink"==n||"mceLink"==n?(e.preventDefault(),t.execCommand("sslink",i,r)):"mceAdvImage"!=n&&"mceImage"!=n||(e.preventDefault(),t.execCommand("ssmedia",i,r))}),t.on("SaveContent",function(t){var e=jQuery(t.content),n=function(t){return Object.keys(t).map(function(e){return t[e]?e+'="'+t[e]+'"':null}).filter(function(t){return null!==t}).join(" ")};e.find(".ss-htmleditorfield-file.embed").each(function(){var t=jQuery(this),e={width:t.attr("width"),"class":t.attr("cssclass"),thumbnail:t.data("thumbnail")},i="[embed "+n(e)+"]"+t.data("url")+"[/embed]";t.replaceWith(i)}),e.find("img").each(function(){var t=jQuery(this),e={src:t.attr("src"),id:t.data("id"),width:t.attr("width"),height:t.attr("height"),"class":t.attr("class"),title:t.attr("title"),alt:t.attr("alt")},i="[image "+n(e)+"]";t.replaceWith(i)}),t.content="",e.each(function(){void 0!==this.outerHTML&&(t.content+=this.outerHTML)})}),t.on("BeforeSetContent",function(t){for(var e,n=t.content,i=function(t){return t.replace(/['"]/g,"").replace(/(^\s+|\s+$)/g,"").split(/\s+/).reduce(function(t,e){var n=e.split("=");return t[n[0]]=1==n.length?n[0]:n[1],t},{})},r=/\[embed(.*?)\](.+?)\[\/\s*embed\s*\]/gi;e=r.exec(n);){var a,s=i(e[1]);a=jQuery("<img/>").attr({src:s.thumbnail,width:s.width,height:s.height,"class":s["class"],"data-url":e[2]}).addClass("ss-htmleditorfield-file embed"),s.cssclass=s["class"],Object.keys(s).forEach(function(t){return a.attr("data-"+t,s[t])}),n=n.replace(e[0],jQuery("<div/>").append(a).html())}for(var r=/\[image(.*?)\]/gi;e=r.exec(n);){var s=i(e[1]),a=jQuery("<img/>").attr({src:s.src,width:s.width,height:s.height,"class":s["class"],alt:s.alt,title:s.title,"data-id":s.id});Object.keys(s).forEach(function(t){return a.attr("data-"+t,s[t])}),n=n.replace(e[0],jQuery("<div/>").append(a).html())}t.content=n})}};tinymce.PluginManager.add("ssbuttons",function(e){t.init(e)})}()},{}]},{},[1]);