prepare core ckeditor for custom config and plugins

This commit is contained in:
Ian 2014-03-15 17:48:29 +01:00
parent 4296b78333
commit 8e26bd9711
7 changed files with 513 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

View File

@ -0,0 +1,62 @@
/*
* Embed Media Dialog based on http://www.fluidbyte.net/embed-youtube-vimeo-etc-into-ckeditor
*
* Plugin name: mediaembed
* Menu button name: MediaEmbed
*
* Youtube Editor Icon
* http://paulrobertlloyd.com/
*
* @author Fabian Vogelsteller [frozeman.de]
* @version 0.5
*/
( function() {
CKEDITOR.plugins.add( 'mediaembed',
{
icons: 'mediaembed', // %REMOVE_LINE_CORE%
hidpi: true, // %REMOVE_LINE_CORE%
init: function( editor )
{
var me = this;
CKEDITOR.dialog.add( 'MediaEmbedDialog', function (instance)
{
return {
title : 'Embed Media',
minWidth : 550,
minHeight : 200,
contents :
[
{
id : 'iframe',
expand : true,
elements :[{
id : 'embedArea',
type : 'textarea',
label : 'Paste Embed Code Here',
'autofocus':'autofocus',
setup: function(element){},
commit: function(element){}
}]
}
],
onOk: function() {
var div = instance.document.createElement('div');
div.setHtml(this.getContentElement('iframe', 'embedArea').getValue());
instance.insertElement(div);
}
};
} );
editor.addCommand( 'MediaEmbed', new CKEDITOR.dialogCommand( 'MediaEmbedDialog',
{ allowedContent: 'iframe[*]' }
) );
editor.ui.addButton( 'MediaEmbed',
{
label: 'Embed Media',
command: 'MediaEmbed',
toolbar: 'mediaembed'
} );
}
} );
} )();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,145 @@
/**
* @fileOverview A Serendipity wysiwyg-placeholder ckeditor plugin: procurator, 2014-03-15, v. 1.3, Ian
*/
(function (pluginName) {
var pluginName = 'procurator';
function createFakeElement(editor, realElement, displayName) {
if (!displayName) var displayName = '';
// API createFakeParserElement(realElement, className, realElementType, isResizable) - realElementType can be flash,iframe,and? (these set their own title attribute),
var fakeElement = editor.createFakeParserElement( realElement, 'cke_procurator', 'procurator', false ), fakeStyle = fakeElement.attributes.style || '';
// set the needed [img object] attributes
//fakeElement.attributes[ 'align' ] = 'right'; // '' is default, uncomment to default
fakeElement.attributes[ 'title' ] = realElement.name + ' tag placeholder';
fakeElement.attributes[ 'alt' ] = realElement.name;
//fakeElement.attributes[ 'contenteditable'] = "false"; // disabled, since CKEDITOR 4.3.2 must have had changed something, that the mediainsert block was replaced by an img procurator/placeholder code
//fakeElement.attributes['src'] = realElement.name+'.png'; // could be used to add an specific element name placeholder, else the /plugins/fakeobjects/images/spacer.gif is used and overruled by addCss
return fakeElement;
}
CKEDITOR.plugins.add( pluginName, {
requires: ['fakeobjects'],
onLoad: function() {
// CHANGES IN CKEDITOR 4
// The "additional CSS" feature provided by CKEDITOR.editor#addCss has moved to a global CKEDITOR.addCss, with specified style rules applies document wide.
// Thus the proper way for a plugin to style it's editable content is to call CKEDITOR.addCss inside of the plugin's onLoad function, rather than it's init function in v3.
// Adds a piece of CSS code to the editor, which will be applied to the WYSIWYG editing document. This CSS would not be added to the output, and is there mainly for editor-specific editing requirements.
// Note: This function should be called before the editor is loaded to take effect.
CKEDITOR.addCss(
'.cke_procurator' +
'{' +
'background-color: #FAFAFA;' +
'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/procurator.png' ) + ');' +
'background-position: center center;' +
'background-repeat: no-repeat;' +
'border: 1px solid #a9a9a9;' +
'border-radius: 0.8em;' +
'width: 220px;' +
'height: 37px;' +
'}'
);
},
init: function (editor) {
// This happens on switch wysiwyg-mode to source view only and is used to remove special wysiwyg changes to the source
editor.on( 'mode', function() {
if ( editor.mode == 'source' ) {
var source = editor.getData();//alert(source);
source = source.replace( /[\t]/g, ' '); // replace ckeditor added tabulators with 4 spaces
source = source.replace( /"/g, '"'); // replace smarty tags quote to " switching wysiwyg to source view
source = source.replace( /'/g, "'"); // replace smarty tags quote to ' switching wysiwyg to source view
//source = source.replace( /\r?\n|\r/gm, ""); // replace all newlines for
// set data back into source mode textarea
editor.setData(source);
}
});
},
afterInit: function (editor) {
// Special script tags work by default, without any changes (commenting out the script to protected replavements) in ckeditor.js file,
// but we can not access (fake and replace with placeholders) these <!--{cke_protected} .* --> comments via the dataFilter rule element.
// This is why we have to fake it by comment dataFilter rule first
// The dataProcessor handles data coming in to the editor and out from the editor.
var dataProcessor = editor.dataProcessor;
// The dataProcessor.dataFilter handles incoming data, like pasting:
// filter applied to the input data when transforming it to HTML to be loaded into the editor ("on input").
// Ckeditor in this state is enabled.
var dataFilter = dataProcessor && dataProcessor.dataFilter;
// The dataProcessor.htmlFilter handles outgoing data, like saving, viewing the source button, or generally calling updateElement on the editor:
// filter applied to the HTML available in the editor when transforming it on the XHTML outputted by the editor ("on output").
// If you have a rule within the htmlFilter that strips out the href attribute, then on save it will be stripped.
// Ckeditor in this state is disabled.
// var htmlFilter = dataProcessor && dataProcessor.htmlFilter;
// var writer = editor.dataProcessor.writer;
// The way to close self closing tags inside add rules, like
// writer.selfClosingEnd = '>';
if (dataFilter) {
// Here we want to add a new filter rule... if the source matches this property, it will be converted
// Note the num at the end, which defines the filter priority. Higher number = higher Priority.
dataFilter.addRules({
// comment reads html comments <!-- (.*) --> and adds fakes to the income-data to be displayed in wysiwyg-mode,
// which was already filtered by ckeditor - see script, noscript (and php?) tags as example
comment : function( elString, elObject ) {
var protectedSourceMarker = '{cke_protected}';
if ( elString.substr( 0, protectedSourceMarker.length ) == protectedSourceMarker ) {
var displayName = 'protected';
var realData = elString.replace(/\{cke_protected\}([\s\S]+?)/g, "$1"); // cuts the snippet from {cke_protected}(.*) to preserve
var tagName = realData.replace( /([%3C|%3C%3F|%7B]?([a-z]+))%+(.*)$/, "$1"); // get the real tag name to set as real tag name title to object fakeElement.attributes
tagName = tagName.replace( '%3C', '' ).replace( '%3F', '' ); // tweak a little more, since this upper regex drives me crazy
if ( tagName.substr( 0, 3) == '%7B' ) tagName = 'smarty'; // []{
if ( !tagName.match("script|mediainsert|audio|smarty") ) tagName = 'unknown';
var fakeWrapper = new CKEDITOR.htmlParser.element( displayName ); // creates the new object
fakeWrapper.name = tagName; // to give this to createFakeElement(), set name value to displayName, else it is the cke_protected script value
var fakeElement = createFakeElement(editor, fakeWrapper); // take the way to editor.createFakeParserElement( fakeWrapper, 'cke_procurator', displayName, false );
// by here I had issues that the realelement data returned twice and falsely encoded,
// or returned to be like "%3Cprotected%3E%3C%2Fprotected%3E" only,
// so we workaround this weird issues and just set the value back to the realData! :)
fakeElement.attributes['data-cke-realelement'] = realData;
//alert(elObject.value);
//alert(fakeElement.attributes['data-cke-realelement']);
if ( fakeElement ) {
delete elObject.value;
return fakeElement;
}
}
return elObject.value;
},
// this are real tag elements only
elements: {
/*
'protected': function (element) {
var fake = createFakeElement(editor, element);
if ( element.attributes.src ) {
fake.attributes.alt = element.attributes.src;
fake.attributes["data-cke-realelement"] = fake.attributes["data-cke-realelement"].replace(/%26amp%3B/gi, '%26'); //fix double encoding on ampersands in src
}
return fake;
},*/
}
}, 16); // This is ckeditor priority [1-10] and ACF [11-14], but set to 1 does not conflict here, since we don't hit internal priority rules
// http://docs.ckeditor.com/?_escaped_fragment_=/api/CKEDITOR.editor-event-toDataFormat#!/api/CKEDITOR.editor-event-toDataFormat
// toHtml( evt )
// This event is fired by the CKEDITOR.htmlDataProcessor when input HTML is to be purified by the CKEDITOR.htmlDataProcessor.toHtml method.
// By adding listeners with different priorities it is possible to process input HTML on different stages:
// 1-4: Data is available in the original string format.
// 5: Data is initially filtered with regexp patterns and parsed to CKEDITOR.htmlParser.fragment CKEDITOR.htmlParser.element.
// 5-9: Data is available in the parsed format, but CKEDITOR.htmlDataProcessor.dataFilter is not applied yet.
// 10: Data is filtered with CKEDITOR.htmlDataProcessor.dataFilter.
// 10-14: Data is available in the parsed format and CKEDITOR.htmlDataProcessor.dataFilter has already been applied.
// 15: Data is written back to an HTML string.
// 15-*: Data is available in an HTML string.
// Available since: 4.1
}
}
});
})('procurator');

View File

@ -0,0 +1,106 @@
/**
* @fileOverview A Serendipity CKEDITOR custom config file: ckeditor_custom_config.js, v. 1.0, 2014-03-14, Ian
*/
/**
* Substitute every config option to CKEDITOR in here
*/
CKEDITOR.editorConfig = function( config ) {
// Advanced Content Filter ACF works in two modes:
// automatic the filter is configured by editor features (like plugins, buttons, and commands) that are enabled with configuration options
// such as CKEDITOR.config.plugins, CKEDITOR.config.extraPlugins, and CKEDITOR.config.toolbar,
// custom the filter is configured by the CKEDITOR.config.allowedContent option and only features that match this setting are activated.
// In both modes it is possible to extend the filter configuration by using the CKEDITOR.config.extraAllowedContent setting.
// If you want to disable Advanced Content Filter, set CKEDITOR.config.allowedContent to true. All available editor features will be activated and input data will not be filtered.
// Allowed content rules. This setting is used when instantiating CKEDITOR.editor.filter.
// The following values are accepted:
// CKEDITOR.filter.allowedContentRules defined rules will be added to the CKEDITOR.editor.filter.
// true will disable the filter (data will not be filtered, all features will be activated).
// default the filter will be configured by loaded features (toolbar items, commands, etc.).
// In all cases filter configuration may be extended by extraAllowedContent. This option may be especially useful when you want to use the default allowedContent value along with some additional rules.
//
// config.allowedContent = CONFIG_ACF_OFF;
// List of regular expressions to be executed on ***input HTML***, indicating HTML source code that, when matched, must not be available in the WYSIWYG mode for editing.
// allow <script> tags
config.protectedSource.push( /<(script)[^>]*>.*<\/script>/ig ); // set default in ckeditor.js [/<script[\s\S]*?<\/script>/gi,/<noscript[\s\S]*?<\/noscript>/gi]
// allow imageselectorplus mediainsert tag code
config.protectedSource.push( /<(mediainsert)[^>]*>[\s\S]*?<\/mediainsert>/img );
// allow a Smarty like {} tag syntax without starting whitespace, which would be some other code part.
config.protectedSource.push( /\{[a-zA-Z\$].*?\}/gi );
// Set placeholder tag cases - elements [attributes]{styles}(classes)
// Allowed mediainsert, gallery, media tags (imageselectorplus galleries) - which tells ACF to not touch the code!
// Allowed div is a need for Media Library inserts - which tells ACF to not touch the code!
// img[height] is even needed to avoid ACF OFF removement of height attributes
config.extraAllowedContent = 'mediainsert[*]{*}(*);gallery[*]{*}(*);media[*]{*}(*);script[*]{*}(*);audio[*]{*}(*);div[*]{*}(*);img[height,width];';
// CKEDITOR.protectedSource patterns used regex Escape sequences
// \s any whitespace character;
// \S any character that is not a whitespace character
// \t tab (hex 09);
// \r carriage return (hex 0D);
// \n newline (hex 0A);
// Pattern Modifiers
// /i caseless, match both upper and lower case letters
// /m treat as multiline
// /g be greedy
// Prevent filler nodes in all empty blocks. - case switching source and wysiwyg mode multiple times
//config.fillEmptyBlocks = false; // default (true) - switches <p>&nbsp;</p> to <p></p>
//config.ignoreEmptyParagraph = false; // default(true) - Whether the editor must output an empty value ('') if it's contents is made by an empty paragraph only. (extends to config.fillEmptyBlocks)
// It will still generate an empty <p></p> though.
config.autoParagraph = false; // but this one definitely prevents adding multiple empty paragraphs when switching source mode!!!
// The configuration setting that controls the ENTER mode is "config.enterMode" and it offers three options:
// (1) The default creates a paragraph element each time the "enter" key is pressed:
//config.enterMode = CKEDITOR.ENTER_P; // inserts <p></p>
// (2) You can choose to create a "div" element instead of a paragraph:
//config.enterMode = CKEDITOR.ENTER_DIV; // inserts <div></div>
// (3) If you prefer to not wrap the text in anything, you can choose to insert a line break tag:
//config.enterMode = CKEDITOR.ENTER_BR; // inserts <br />
// You can always use SHIFT+ENTER to set a br in the P-mode default option or change the SHIFT-mode to something else
//config.shiftEnterMode = CKEDITOR.ENTER_BR;
//config.entities = false;
//config.htmlEncodeOutput = false;
// ui configurations - just some examples
//config.uiColor = 'transparent'; // standard, but better disable config.uiColor all
//config.uiColor = '#CFD1CF'; // standard grey
//config.uiColor = '#f5f5f5'; // standard light grey
//config.uiColor = '#E6EDF3'; // extreme light blue
//config.uiColor = '#DFE8F6'; // very light blue
//config.uiColor = '#9AB8F3'; // light blue/violet
//config.uiColor = '#AADC6E'; // light green
//config.uiColor = '#FFDC6E'; // light gold
//config.uiColor = '#FF8040'; // mango
//config.uiColor = '#FF2400'; // scarlet red
//config.uiColor = '#14B8C4'; // light turquoise
config['skin'] = 'moono';
config['height'] = 400;
// remove custom toolbar buttons and plugins
//config.removePlugins = 'flash,iframe';
config.removeButtons = 'Styles';
// set the custom toolbar group
// Note: indent is disabled, mediaembed plugin is set here and procurator placeholders for "protected Source" is buttonless
config.toolbarGroups = [
{ name: 'styles' },
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', /*'indent', */'blocks', 'align', 'bidi' ] },
{ name: 'links' },
{ name: 'insert' },
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'others' },
{ name: 'mediaembed' },
{ name: 'tools' },
{ name: 'about' }
];
};

View File

@ -0,0 +1,200 @@
/**
* @fileOverview A Serendipity custom CKEDITOR additional plugin creator file: ckeditor_custom_plugin.js, v. 1.0, 2014-03-15, Ian
*/
/**
* Get the instance ready event and set global instance var
* This is read by serendipity_editor.js(.tpl) and in case of serendipity_html_nugget_plugin by below serendipity_imageSelector_addToBody()
*/
CKEDITOR.on( 'instanceReady', function( event ) {
event.editor.on( 'focus', function() {
isinstance = this;
});
});
/**
* Create a prototyp foreach array function
* This is faster than using ckeditor.js internal forEach() implementation or even using plain for()
*/
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (fn, scope) {
'use strict';
var i, len;
for (i = 0, len = this.length; i < len; ++i) {
if (i in this) {
fn.call(scope, this[i], i, this);
}
}
};
}
/**
* ExecutionCommand string replacement function
* Used in CKEDITOR.plugins.add(), to get ready to execute (eval) the button command passed by Serendipity plugins
* @param string string
* @return string string
*/
function ecfit(str) {
str = str.replace('function() { ', '');
str = str.replace(' }', '');
str = str.replace(CKEDITOR_S9Y_BASEURL, '');
return str;
}
/**
* This was previously a nugget only area, spawned by head! (textareas of staticpage nuggets, html nugget plugins, etc.)
* called via Spawnnugget(), set by real plugins like staticpages and cores functions_plugins_admin.inc in case of $ev['skip_nuggets'] === false
*
* NOW it is used by all textareas!
*
* @param string $eventData['item']
* @param string $eventData['jsname']
* @param array/object jsEventData/json_encode($eventData['buttons'])
*/
function Spawnnuggets(item, addEP, jsED) {
if (!item) var item = null;
if (!addEP) var addEP = null;
if (!jsED) var jsED = null;
var textarea_instance = !isNaN(item) ? 'nuggets' + item : item;
var name_extraPlugins = (addEP !== null) ? addEP : textarea_instance;
var jsEventData = (jsED !== null) ? jsED : window.jsEventData; // global set by 'backend_wysiwyg_finish' hook
var extraPluginList = name_extraPlugins+',mediaembed,procurator'; // no spaces allowed!
if (document.getElementById(textarea_instance)) {
CKEDITOR.replace(textarea_instance, {
// Load our specific configuration file.
customConfig : CKEDITOR_CCPATHFILE+'ckeditor_custom_config.js',
// or do and set
// Reset toolbar Groups settings
// toolbarGroups: null
// or any other configuration option here
// uiColor : '#AADC6E', // light green example
// language: 'fr', // default lang set example for french
// set all plugins at once - no spaces allowed!
extraPlugins: extraPluginList,
// Set the startup mode view [OK]
// startupMode: 'source',
// listen on load - do I need this still? YES!!!
on: {
loaded: function( evt ) {
var editor = evt.editor,
rules = {
elements: {
mediainsert: function( element ) {
// XHTML output instead of HTML - but this does not react on trailing slash eg <media "blah" />
// editor.dataProcessor.writer.selfClosingEnd = ' />';
//avoid line breaks with special block elements
var tags = ['mediainsert', 'gallery', 'media'];
for (var key in tags) {
editor.dataProcessor.writer.setRules(tags[key],
{
// Indicates that this tag causes indentation on line breaks inside of it.
indent : true,
// Inserts a line break before the element opening tag.
breakBeforeOpen : true,
// Inserts a line break after the element opening tag.
breakAfterOpen : false,
// Inserts a line break before the element closing tag.
breakBeforeClose : true,
// Inserts a line break after the element closing tag.
breakAfterClose : false
});
}
},
// Output dimensions of w/h images, since we either need an unchanged MediaLibrary image code for responsive templates or tweak some replacements!
img: function( element ) {
var style = element.attributes.style;
if ( style )
{
// Get the height from the style.
var match = /(?:^|\s)height\s*:\s*(\d+)px/i.exec( style );
var height = match && match[1];
if ( height )
{
element.attributes.style = element.attributes.style.replace( /(?:^|\s)height\s*:\s*(\d+)px;?/i , '' );
//element.attributes.height = height;
// Do not add to element attribute height, since then the height will be automatically (re-) added to style again by ckeditor or image js
// The current result is now: img alt class src style{width}. That is the only working state to get arround this issue in a relative simple way!
// Remember: Turning ACF OFF, will leave code alone, but still removes the height="" attribute! (workaround in extraAllowedContent added img[height]!)
}
}
}
}
};
// It's good to set both filters - dataFilter is used when loading data and htmlFilter when retrieving.
editor.dataProcessor.htmlFilter.addRules( rules );
editor.dataProcessor.dataFilter.addRules( rules );
}
}
});
CKEDITOR.plugins.add(name_extraPlugins, {
init: function(editor) {
if(typeof jsEventData !== 'undefined') {
jsEventData.forEach( function(k, i) {
var execcom = ecfit(jsEventData[i].javascript);
editor.addCommand( jsEventData[i].id, {
exec: function( editor ) {
eval(execcom); // [OK] only way this code is executable
}
});
editor.ui.addButton(jsEventData[i].id, {
label: jsEventData[i].name,
title: jsEventData[i].name+' Plugin',
icon: CKEDITOR_S9YPLUGPATH+jsEventData[i].img_path,
iconName: jsEventData[i].id+'_icon',
command: jsEventData[i].id
});
});
}
editor.addCommand( 'openML', {
exec : function( editor ) {
serendipity.openPopup('serendipity_admin.php?serendipity[adminModule]=media&serendipity[noBanner]=true&serendipity[noSidebar]=true&serendipity[noFooter]=true&serendipity[showMediaToolbar]=false&serendipity[showUpload]=true&serendipity[textarea]={$item}');
//window.open('serendipity_admin_image_selector.php', 'ImageSel', 'width=800,height=600,toolbar=no,scrollbars=1,scrollbars,resize=1,resizable=1');
}
});
editor.ui.addButton('openML', {
label: 'S9yMedia',
title: 'Serendipity Media Library',
icon: CKEDITOR_CCIMGPATH,
iconName: 'openML_icon',
command: 'openML'
});
}
});
}
}
/**
* Clone an old serendipity_editor.js function, to avoid a
* TypeError: (parent.)self.opener.serendipity_imageSelector_addToBody is not a function
* in case of serendipity_html_nugget_plugin textarea (nuggets3) usage
* and other popup function using plugins, like linktrimmer and amazonchooser
*/
function serendipity_imageSelector_addToBody (str, textarea) {
var oEditor = isinstance;
if (oEditor.mode == "wysiwyg") {
oEditor.insertHtml(str);
}
}
/*
var serendipity = {}; // define the namespace
serendipity.serendipity_imageSelector_addToBody = function(str, textarea) {
var oEditor = isinstance;
if (oEditor.mode == "wysiwyg") {
oEditor.insertHtml(str);
}
return;
}
*/