diff --git a/admin/_config.php b/admin/_config.php index 043276de2..deb138026 100644 --- a/admin/_config.php +++ b/admin/_config.php @@ -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'); diff --git a/gulpfile.js b/gulpfile.js index 603864eea..18539db14 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -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']); diff --git a/javascript/dist/TinyMCE_SSPlugin.js b/javascript/dist/TinyMCE_SSPlugin.js new file mode 100644 index 000000000..11f0ab624 --- /dev/null +++ b/javascript/dist/TinyMCE_SSPlugin.js @@ -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('').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('
').append(el).html()); + } + + var shortTagRegex = /\[image(.*?)\]/gi; + while (matches = shortTagRegex.exec(content)) { + var attrs = attrFromStrFn(matches[1]); + var el = jQuery('').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('
').append(el).html()); + } + + o.content = content; + }); + } + }; + + tinymce.PluginManager.add("ssbuttons", function (editor) { + ssbuttons.init(editor); + }); + })(); +}); \ No newline at end of file diff --git a/thirdparty/tinymce_ssbuttons/plugin.js b/javascript/src/TinyMCE_SSPlugin.js similarity index 91% rename from thirdparty/tinymce_ssbuttons/plugin.js rename to javascript/src/TinyMCE_SSPlugin.js index ceb065efa..4246a68cf 100644 --- a/thirdparty/tinymce_ssbuttons/plugin.js +++ b/javascript/src/TinyMCE_SSPlugin.js @@ -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('
').append(el).html())); } diff --git a/thirdparty/tinymce_ssbuttons/plugin.min.js b/thirdparty/tinymce_ssbuttons/plugin.min.js deleted file mode 100644 index bfef76bc4..000000000 --- a/thirdparty/tinymce_ssbuttons/plugin.min.js +++ /dev/null @@ -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").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("
").append(a).html())}for(var r=/\[image(.*?)\]/gi;e=r.exec(n);){var s=i(e[1]),a=jQuery("").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("
").append(a).html())}t.content=n})}};tinymce.PluginManager.add("ssbuttons",function(e){t.init(e)})}()},{}]},{},[1]); \ No newline at end of file