diff --git a/plugin.js b/plugin.js index 07580a8..0f49182 100644 --- a/plugin.js +++ b/plugin.js @@ -7,424 +7,396 @@ * @fileOverview Rich code snippets for CKEditor. */ -'use strict'; - -( function() { - CKEDITOR.plugins.add( 'codesnippet', { - requires: 'widget,dialog', - // jscs:disable maximumLineLength - lang: 'ar,az,bg,ca,cs,da,de,de-ch,el,en,en-au,en-gb,eo,es,es-mx,et,eu,fa,fi,fr,fr-ca,gl,he,hr,hu,id,it,ja,km,ko,ku,lt,lv,nb,nl,no,oc,pl,pt,pt-br,ro,ru,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE% - // jscs:enable maximumLineLength - icons: 'codesnippet', // %REMOVE_LINE_CORE% - hidpi: true, // %REMOVE_LINE_CORE% - - isSupportedEnvironment: function() { - return !CKEDITOR.env.ie || CKEDITOR.env.version > 8; - }, - - beforeInit: function( editor ) { - editor._.codesnippet = {}; - - /** - * Sets the custom syntax highlighter. See {@link CKEDITOR.plugins.codesnippet.highlighter} - * to learn how to register a custom highlighter. - * - * **Note**: - * - * * This method can only be called while initialising plugins (in one of - * the three callbacks). - * * This method is accessible through the `editor.plugins.codesnippet` namespace only. - * - * @since 4.4.0 - * @member CKEDITOR.plugins.codesnippet - * @param {CKEDITOR.plugins.codesnippet.highlighter} highlighter - */ - this.setHighlighter = function( highlighter ) { - editor._.codesnippet.highlighter = highlighter; - - var langs = editor._.codesnippet.langs = - editor.config.codeSnippet_languages || highlighter.languages; - - // We might escape special regex chars below, but we expect that there - // should be no crazy values used as lang keys. - editor._.codesnippet.langsRegex = new RegExp( '(?:^|\\s)language-(' + - CKEDITOR.tools.object.keys( langs ).join( '|' ) + ')(?:\\s|$)' ); - }; - - editor.once( 'pluginsLoaded', function() { - // Remove the method once it cannot be used, because it leaks the editor reference (#589). - this.setHighlighter = null; - }, this ); - }, - - onLoad: function() { - CKEDITOR.dialog.add( 'codeSnippet', this.path + 'dialogs/codesnippet.js' ); - }, - - init: function( editor ) { - editor.ui.addButton && editor.ui.addButton( 'CodeSnippet', { - label: editor.lang.codesnippet.button, - command: 'codeSnippet', - toolbar: 'insert,10' - } ); - }, - - afterInit: function( editor ) { - var path = this.path; - - registerWidget( editor ); - - // At the very end, if no custom highlighter was set so far (by plugin#setHighlighter) - // we will set default one. - if ( !editor._.codesnippet.highlighter ) { - var hljsHighlighter = new CKEDITOR.plugins.codesnippet.highlighter( { - languages: { - apache: 'Apache', - bash: 'Bash', - coffeescript: 'CoffeeScript', - cpp: 'C++', - cs: 'C#', - css: 'CSS', - diff: 'Diff', - html: 'HTML', - http: 'HTTP', - ini: 'INI', - java: 'Java', - javascript: 'JavaScript', - json: 'JSON', - makefile: 'Makefile', - markdown: 'Markdown', - nginx: 'Nginx', - objectivec: 'Objective-C', - perl: 'Perl', - php: 'PHP', - python: 'Python', - ruby: 'Ruby', - sql: 'SQL', - vbscript: 'VBScript', - xhtml: 'XHTML', - xml: 'XML' - }, - - init: function( callback ) { - var that = this; - - if ( editor.plugins.codesnippet.isSupportedEnvironment() ) { - CKEDITOR.scriptLoader.load( path + 'lib/highlight/highlight.pack.js', function() { - that.hljs = window.hljs; - callback(); - } ); - } - - // Method is available only if wysiwygarea exists. - if ( editor.addContentsCss ) { - editor.addContentsCss( path + 'lib/highlight/styles/' + editor.config.codeSnippet_theme + '.css' ); - } - }, - - highlighter: function( code, language, callback ) { - var highlighted = this.hljs.highlightAuto( code, - this.hljs.getLanguage( language ) ? [ language ] : undefined ); - - if ( highlighted ) - callback( highlighted.value ); - } - } ); - - this.setHighlighter( hljsHighlighter ); - } - } - } ); - - /** - * Global helpers and classes of the Code Snippet plugin. - * - * For more information see the {@glink features/codesnippet Code Snippet Guide}. - * - * @class - * @singleton - */ - CKEDITOR.plugins.codesnippet = { - highlighter: Highlighter - }; - - /** - * A Code Snippet highlighter. It can be set as a default highlighter - * using {@link CKEDITOR.plugins.codesnippet#setHighlighter}, for example: - * - * // Create a new plugin which registers a custom code highlighter - * // based on customEngine in order to replace the one that comes - * // with the Code Snippet plugin. - * CKEDITOR.plugins.add( 'myCustomHighlighter', { - * afterInit: function( editor ) { - * // Create a new instance of the highlighter. - * var myHighlighter = new CKEDITOR.plugins.codesnippet.highlighter( { - * init: function( ready ) { - * // Asynchronous code to load resources and libraries for customEngine. - * customEngine.loadResources( function() { - * // Let the editor know that everything is ready. - * ready(); - * } ); - * }, - * highlighter: function( code, language, callback ) { - * // Let the customEngine highlight the code. - * customEngine.highlight( code, language, function() { - * callback( highlightedCode ); - * } ); - * } - * } ); - * - * // Check how it performs. - * myHighlighter.highlight( 'foo()', 'javascript', function( highlightedCode ) { - * console.log( highlightedCode ); // -> foo() - * } ); - * - * // From now on, myHighlighter will be used as a Code Snippet - * // highlighter, overwriting the default engine. - * editor.plugins.codesnippet.setHighlighter( myHighlighter ); - * } - * } ); - * - * @since 4.4.0 - * @class CKEDITOR.plugins.codesnippet.highlighter - * @extends CKEDITOR.plugins.codesnippet - * @param {Object} def Highlighter definition. See {@link #highlighter}, {@link #init} and {@link #languages}. - */ - function Highlighter( def ) { - CKEDITOR.tools.extend( this, def ); - - /** - * A queue of {@link #highlight} jobs to be - * done once the highlighter is {@link #ready}. - * - * @readonly - * @property {Array} [=[]] - */ - this.queue = []; - - // Async init – execute jobs when ready. - if ( this.init ) { - this.init( CKEDITOR.tools.bind( function() { - // Execute pending jobs. - var job; - - while ( ( job = this.queue.pop() ) ) - job.call( this ); - - this.ready = true; - }, this ) ); - } else { - this.ready = true; - } - - /** - * If specified, this function should asynchronously load highlighter-specific - * resources and execute `ready` when the highlighter is ready. - * - * @property {Function} [init] - * @param {Function} ready The function to be called once - * the highlighter is {@link #ready}. - */ - - /** - * A function which highlights given plain text `code` in a given `language` and, once done, - * calls the `callback` function with highlighted markup as an argument. - * - * @property {Function} [highlighter] - * @param {String} code Code to be formatted. - * @param {String} lang Language to be used ({@link CKEDITOR.config#codeSnippet_languages}). - * @param {Function} callback Function which accepts highlighted String as an argument. - */ - - /** - * Defines languages supported by the highlighter. - * They can be restricted with the {@link CKEDITOR.config#codeSnippet_languages} configuration option. - * - * **Note**: If {@link CKEDITOR.config#codeSnippet_languages} is set, **it will - * overwrite** the languages listed in `languages`. - * - * languages: { - * coffeescript: 'CoffeeScript', - * cpp: 'C++', - * cs: 'C#', - * css: 'CSS' - * } - * - * More information on how to change the list of languages is available - * in the {@glink features/codesnippet#changing-supported-languages Code Snippet documentation}. - * - * @property {Object} languages - */ - - /** - * A flag which indicates whether the highlighter is ready to do jobs - * from the {@link #queue}. - * - * @readonly - * @property {Boolean} ready - */ - } - - /** - * Executes the {@link #highlighter}. If the highlighter is not ready, it defers the job ({@link #queue}) - * and executes it when the highlighter is {@link #ready}. - * - * @param {String} code Code to be formatted. - * @param {String} lang Language to be used ({@link CKEDITOR.config#codeSnippet_languages}). - * @param {Function} callback Function which accepts highlighted String as an argument. - */ - Highlighter.prototype.highlight = function() { - var arg = arguments; - - // Highlighter is ready – do it now. - if ( this.ready ) - this.highlighter.apply( this, arg ); - // Queue the job. It will be done once ready. - else { - this.queue.push( function() { - this.highlighter.apply( this, arg ); - } ); - } - }; - - // Encapsulates snippet widget registration code. - // @param {CKEDITOR.editor} editor - function registerWidget( editor ) { - var codeClass = editor.config.codeSnippet_codeClass, - newLineRegex = /\r?\n/g, - textarea = new CKEDITOR.dom.element( 'textarea' ), - lang = editor.lang.codesnippet; - - editor.widgets.add( 'codeSnippet', { - allowedContent: 'pre; code(language-*)', - // Actually we need both - pre and code, but ACF does not make it possible - // to defire required content with "and" operator. - requiredContent: 'pre', - styleableElements: 'pre', - template: '
',
- dialog: 'codeSnippet',
- pathName: lang.pathName,
- mask: true,
-
- parts: {
- pre: 'pre',
- code: 'code'
- },
-
- highlight: function() {
- var that = this,
- widgetData = this.data,
- callback = function( formatted ) {
- // IE8 (not supported browser) have issue with new line chars, when using innerHTML.
- // It will simply strip it.
- that.parts.code.setHtml( editor.plugins.codesnippet.isSupportedEnvironment() ?
- formatted : formatted.replace( newLineRegex, '...
- upcast: function( el, data ) {
- if ( el.name != 'pre' )
- return;
-
- var childrenArray = getNonEmptyChildren( el ),
- code;
-
- if ( childrenArray.length != 1 || ( code = childrenArray[ 0 ] ).name != 'code' )
- return;
-
- // Upcast with text only: https://dev.ckeditor.com/ticket/11926#comment:4
- if ( code.children.length != 1 || code.children[ 0 ].type != CKEDITOR.NODE_TEXT )
- return;
-
- // Read language-* from class attribute.
- var matchResult = editor._.codesnippet.langsRegex.exec( code.attributes[ 'class' ] );
-
- if ( matchResult )
- data.lang = matchResult[ 1 ];
-
- // Use textarea to decode HTML entities (https://dev.ckeditor.com/ticket/11926).
- textarea.setHtml( code.getHtml() );
- data.code = textarea.getValue();
-
- code.addClass( codeClass );
-
- return el;
- },
-
- // Downcasts to ...
- downcast: function( el ) {
- var code = el.getFirst( 'code' );
-
- // Remove pretty formatting from ....
- code.children.length = 0;
-
- // Remove config#codeSnippet_codeClass.
- code.removeClass( codeClass );
-
- // Set raw text inside ....
- code.add( new CKEDITOR.htmlParser.text( CKEDITOR.tools.htmlEncode( this.data.code ) ) );
-
- return el;
- }
- } );
-
- // Returns an **array** of child elements, with whitespace-only text nodes
- // filtered out.
- // @param {CKEDITOR.htmlParser.element} parentElement
- // @return Array - array of CKEDITOR.htmlParser.node
- var whitespaceOnlyRegex = /^[\s\n\r]*$/;
-
- function getNonEmptyChildren( parentElement ) {
- var ret = [],
- preChildrenList = parentElement.children,
- curNode;
-
- // Filter out empty text nodes.
- for ( var i = preChildrenList.length - 1; i >= 0; i-- ) {
- curNode = preChildrenList[ i ];
-
- if ( curNode.type != CKEDITOR.NODE_TEXT || !curNode.value.match( whitespaceOnlyRegex ) )
- ret.push( curNode );
- }
-
- return ret;
- }
- }
-} )();
+"use strict";
+
+(function () {
+ CKEDITOR.plugins.add("codesnippet", {
+ requires: "widget,dialog",
+ // jscs:disable maximumLineLength
+ lang: "ar,az,bg,ca,cs,da,de,de-ch,el,en,en-au,en-gb,eo,es,es-mx,et,eu,fa,fi,fr,fr-ca,gl,he,hr,hu,id,it,ja,km,ko,ku,lt,lv,nb,nl,no,oc,pl,pt,pt-br,ro,ru,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn", // %REMOVE_LINE_CORE%
+ // jscs:enable maximumLineLength
+ icons: "codesnippet", // %REMOVE_LINE_CORE%
+ hidpi: true, // %REMOVE_LINE_CORE%
+ defaultLanguages: {
+ apache: "Apache",
+ bash: "Bash",
+ coffeescript: "CoffeeScript",
+ cpp: "C++",
+ cs: "C#",
+ css: "CSS",
+ diff: "Diff",
+ html: "HTML",
+ http: "HTTP",
+ ini: "INI",
+ java: "Java",
+ javascript: "JavaScript",
+ json: "JSON",
+ makefile: "Makefile",
+ markdown: "Markdown",
+ nginx: "Nginx",
+ objectivec: "Objective-C",
+ perl: "Perl",
+ php: "PHP",
+ python: "Python",
+ ruby: "Ruby",
+ sql: "SQL",
+ vbscript: "VBScript",
+ xhtml: "XHTML",
+ xml: "XML",
+ },
+ highlighterType: 'highlight', // highlight or prism
+ configs: {
+ highlight: {
+ globalName: 'hljs',
+ jsPath: 'lib/highlight/highlight.pack.js',
+ cssPath: 'lib/highlight/styles/',
+ codeClass: 'hljs',
+ },
+ prism: {
+ globalName: 'Prism',
+ codeClass: 'prism',
+ }
+ },
+ isSupportedEnvironment() {
+ return !CKEDITOR.env.ie || CKEDITOR.env.version > 8;
+ },
+
+ beforeInit(editor) {
+ editor._.codesnippet = {};
+ const configHighlighter = editor.config.codeSnippet_highlighter.toLowerCase();
+ if (configHighlighter.indexOf('prism') > -1) this.highlighterType = 'prism';
+ if (configHighlighter.indexOf('highlight') > -1) this.highlighterType = 'highlight';
+
+ /**
+ * Sets the custom syntax highlighter. See {@link CKEDITOR.plugins.codesnippet.highlighter}
+ * to learn how to register a custom highlighter.
+ *
+ * **Note**:
+ *
+ * * This method can only be called while initialising plugins (in one of
+ * the three callbacks).
+ * * This method is accessible through the `editor.plugins.codesnippet` namespace only.
+ *
+ * @since 4.4.0
+ * @member CKEDITOR.plugins.codesnippet
+ * @param {CKEDITOR.plugins.codesnippet.highlighter} highlighter
+ */
+ this.setHighlighter = function setHighlighter(highlighter) {
+ editor._.codesnippet.highlighter = highlighter;
+
+ var langs = (editor._.codesnippet.langs =
+ editor.config.codeSnippet_languages || highlighter.languages);
+
+ // We might escape special regex chars below, but we expect that there
+ // should be no crazy values used as lang keys.
+ // this should optionally support lang-, language-, and
+ editor._.codesnippet.langsRegex = new RegExp(
+ "(?:^|\\s)(lang(uage)?-)?(" +
+ CKEDITOR.tools.object.keys(langs).join("|") +
+ ")(?:\\s|$)"
+ );
+ };
+
+ editor.once(
+ "pluginsLoaded",
+ function () {
+ // Remove the method once it cannot be used, because it leaks the editor reference (#589).
+ // note: determine if this gets added back in the afterInit
+ this.setHighlighter = null;
+ },
+ this
+ );
+ },
+
+ onLoad() {
+ CKEDITOR.dialog.add("codeSnippet", this.path + "dialogs/codesnippet.js");
+ },
+
+ init(editor) {
+ if (editor.ui.addButton) {
+ editor.ui.addButton("CodeSnippet", {
+ label: editor.lang.codesnippet.button,
+ command: "codeSnippet",
+ toolbar: "insert,10",
+ });
+ }
+ },
+ /**
+ * @typedef CodeHighlighter
+ * @property {Object} languages A map of language names and their aliases.
+ * @property {Function} init A function that initializes the highlighter.
+ * @property {Function} loadJs A function that loads the highlighter's JS file.
+ * @property {Function} loadCss A function that loads the highlighter's CSS file.
+ * @property {Function} highlight A function that highlights the code snippet.
+ */
+ /**
+ *
+ * @param {string} highlighterType [prism, higlight]
+ * @param {Object} editor The editor instance
+ * @returns {CodeHighlighter}
+ */
+ getHighlighter(highlighterType, editor) {
+ const { path, defaultLanguages } = this;
+ let jsPath;
+ let cssPath;
+ let globalName = this.configs[highlighterType].globalName;
+ // note: consider moving this outside this function. Right now it's here just b/c...
+ // ...it's useful to keep it in the same scope as editor. But that isn't really necessary.
+ // TODO: Make this a class?
+ function CodeHighlighter(editor, nameOnWindow, pluginPath, cssPath) {
+ return {
+ languages: defaultLanguages,
+ init(callback) {
+ if (editor.plugins.codesnippet.isSupportedEnvironment()) {
+ this.loadJs(pluginPath, nameOnWindow, callback);
+ this.loadCss(cssPath);
+ }
+ },
+ loadJs(pluginPath, globalname, callback) {
+ try {
+ if (pluginPath) {
+ CKEDITOR.scriptLoader.load(pluginPath, () => {
+ this[nameOnWindow] = window[nameOnWindow];
+ callback();
+ });
+ } else {
+ this[nameOnWindow] = window[nameOnWindow];
+ callback();
+ }
+ } catch (loadJsError) {
+ console.error(loadJsError);
+ }
+ },
+ loadCss(cssPath) {
+ if (!editor.addContentsCss || !cssPath) return;
+ editor.addContentsCss(cssPath);
+ },
+ highlighter(code, language, callback) {
+ const highlighted = this[nameOnWindow].highlightAuto(
+ code,
+ this[nameOnWindow].getLanguage(language) ? [language] : undefined
+ );
+
+ if (highlighted) callback(highlighted.value);
+ },
+ };
+ }
+
+ if (highlighterType === "highlight") {
+ // highlight depends on the js that comes with the library, and needs CSS added in
+ jsPath = path + this.configs.highlight.jsPath;
+ cssPath =
+ path +
+ this.configs.highlight.cssPath +
+ editor.config.codeSnippet_theme +
+ ".css";
+ }
+
+ const codeHighlighter = new CodeHighlighter(
+ editor,
+ globalName,
+ jsPath,
+ cssPath
+ );
+
+ if (highlighterType === "prism") {
+ // Prism's highlight function takes different arguments that highlight's. It needs to be sent the grammar and language name
+ // Would be more useful to make the highlighter function more globally useful, but this is a quick fix
+ codeHighlighter.highlighter = function highlighter(
+ code,
+ language,
+ callback
+ ) {
+ const highlighted = this[globalName].highlight(
+ code,
+ this[globalName].languages[language],
+ language
+ );
+
+ if (highlighted) callback(highlighted);
+ };
+ }
+
+ return codeHighlighter;
+ },
+ afterInit(editor) {
+ const {highlighterType} = this;
+ registerWidget(editor);
+ // At the very end, if no custom highlighter was set so far (by plugin#setHighlighter)
+ // we will set default one.
+ // we get a highlighter just to keep functions a bit smaller.
+ if (!editor._.codesnippet.highlighter) {
+ const codesnippetHighlighter = new CKEDITOR.plugins.codesnippet.highlighter(
+ this.getHighlighter(highlighterType, editor)
+ );
+ this.setHighlighter(codesnippetHighlighter);
+ }
+ },
+ });
+
+ /**
+ * Global helpers and classes of the Code Snippet plugin.
+ *
+ * For more information see the {@glink features/codesnippet Code Snippet Guide}.
+ *
+ * @class
+ * @singleton
+ */
+ CKEDITOR.plugins.codesnippet = {
+ highlighter: Highlighter,
+ };
+
+ /**
+ * A Code Snippet highlighter. It can be set as a default highlighter
+ * using {@link CKEDITOR.plugins.codesnippet#setHighlighter}, for example:
+ *
+ * // Create a new plugin which registers a custom code highlighter
+ * // based on customEngine in order to replace the one that comes
+ * // with the Code Snippet plugin.
+ * CKEDITOR.plugins.add( 'myCustomHighlighter', {
+ * afterInit: function( editor ) {
+ * // Create a new instance of the highlighter.
+ * var myHighlighter = new CKEDITOR.plugins.codesnippet.highlighter( {
+ * init: function( ready ) {
+ * // Asynchronous code to load resources and libraries for customEngine.
+ * customEngine.loadResources( function() {
+ * // Let the editor know that everything is ready.
+ * ready();
+ * } );
+ * },
+ * highlighter: function( code, language, callback ) {
+ * // Let the customEngine highlight the code.
+ * customEngine.highlight( code, language, function() {
+ * callback( highlightedCode );
+ * } );
+ * }
+ * } );
+ *
+ * // Check how it performs.
+ * myHighlighter.highlight( 'foo()', 'javascript', function( highlightedCode ) {
+ * console.log( highlightedCode ); // -> foo()
+ * } );
+ *
+ * // From now on, myHighlighter will be used as a Code Snippet
+ * // highlighter, overwriting the default engine.
+ * editor.plugins.codesnippet.setHighlighter( myHighlighter );
+ * }
+ * } );
+ *
+ * @since 4.4.0
+ * @class CKEDITOR.plugins.codesnippet.highlighter
+ * @extends CKEDITOR.plugins.codesnippet
+ * @param {Object} def Highlighter definition. See {@link #highlighter}, {@link #init} and {@link #languages}.
+ */
+ function Highlighter(def) {
+ CKEDITOR.tools.extend(this, def);
+
+ /**
+ * A queue of {@link #highlight} jobs to be
+ * done once the highlighter is {@link #ready}.
+ *
+ * @readonly
+ * @property {Array} [=[]]
+ */
+ this.queue = [];
+
+ // Async init – execute jobs when ready.
+ if (this.init) {
+ this.init(
+ CKEDITOR.tools.bind(function () {
+ // Execute pending jobs.
+ var job;
+
+ while ((job = this.queue.pop())) job.call(this);
+
+ this.ready = true;
+ }, this)
+ );
+ } else {
+ this.ready = true;
+ }
+
+ /**
+ * If specified, this function should asynchronously load highlighter-specific
+ * resources and execute `ready` when the highlighter is ready.
+ *
+ * @property {Function} [init]
+ * @param {Function} ready The function to be called once
+ * the highlighter is {@link #ready}.
+ */
+
+ /**
+ * A function which highlights given plain text `code` in a given `language` and, once done,
+ * calls the `callback` function with highlighted markup as an argument.
+ *
+ * @property {Function} [highlighter]
+ * @param {String} code Code to be formatted.
+ * @param {String} lang Language to be used ({@link CKEDITOR.config#codeSnippet_languages}).
+ * @param {Function} callback Function which accepts highlighted String as an argument.
+ */
+
+ /**
+ * Defines languages supported by the highlighter.
+ * They can be restricted with the {@link CKEDITOR.config#codeSnippet_languages} configuration option.
+ *
+ * **Note**: If {@link CKEDITOR.config#codeSnippet_languages} is set, **it will
+ * overwrite** the languages listed in `languages`.
+ *
+ * languages: {
+ * coffeescript: 'CoffeeScript',
+ * cpp: 'C++',
+ * cs: 'C#',
+ * css: 'CSS'
+ * }
+ *
+ * More information on how to change the list of languages is available
+ * in the {@glink features/codesnippet#changing-supported-languages Code Snippet documentation}.
+ *
+ * @property {Object} languages
+ */
+
+ /**
+ * A flag which indicates whether the highlighter is ready to do jobs
+ * from the {@link #queue}.
+ *
+ * @readonly
+ * @property {Boolean} ready
+ */
+ }
+
+ /**
+ * Executes the {@link #highlighter}. If the highlighter is not ready, it defers the job ({@link #queue})
+ * and executes it when the highlighter is {@link #ready}.
+ *
+ * @param {String} code Code to be formatted.
+ * @param {String} lang Language to be used ({@link CKEDITOR.config#codeSnippet_languages}).
+ * @param {Function} callback Function which accepts highlighted String as an argument.
+ */
+ Highlighter.prototype.highlight = function () {
+ var arg = arguments;
+
+ // Highlighter is ready – do it now.
+ if (this.ready) this.highlighter.apply(this, arg);
+ // Queue the job. It will be done once ready.
+ else {
+ this.queue.push(function () {
+ this.highlighter.apply(this, arg);
+ });
+ }
+ };
+ /**
+ * Encapsulates snippet widget registration code.
+ * @param {CKEDITOR.editor} editor
+ */
+ function registerWidget(editor) {
+ var textarea = new CKEDITOR.dom.element("textarea");
+ const codeSnippetWidget = editor.config.getCodeSnippetWidget(
+ editor,
+ textarea
+ );
+ editor.widgets.add("codeSnippet", codeSnippetWidget);
+ }
+})();
/**
* A CSS class of the `` element used internally for styling
@@ -432,8 +404,8 @@
* {@link CKEDITOR.config#codeSnippet_theme config.codeSnippet_theme}),
* which means that it is **not present** in the editor output data.
*
- * // Changes the class to "myCustomClass".
- * config.codeSnippet_codeClass = 'myCustomClass';
+ * // Changes the class to "myCustomClass".
+ * config.codeSnippet_codeClass = 'myCustomClass';
*
* **Note**: The class might need to be changed when you are using a custom
* highlighter (the default is [highlight.js](https://highlightjs.org)).
@@ -442,12 +414,195 @@
* Read more in the {@glink features/codesnippet documentation}
* and see the {@glink examples/codesnippet example}.
*
- * @since 4.4.0
+ * @since 4.4.1
* @cfg {String} [codeSnippet_codeClass='hljs']
* @member CKEDITOR.config
*/
CKEDITOR.config.codeSnippet_codeClass = 'hljs';
+/* NOTES
+* 1. The codeSnippet_codeClass should be variably set based on CKEDITOR.config.codeSnippet_highlighter
+* But this didn't work. So don't rely on it. logically set it in the codeSnippetWidget function
+* 2. The getCodeSnippetWidget function has an object mapping highlighterType to codeClass.
+* I tried to set that on CKEDITOR.config, but that didn't work (never saw the property display).
+ But that object should be moved _somewhere_ more visible.
+* 3. The object that codeSnippetWidget returns should really be a class.
+*/
+
+/**
+ * This gets the widget definition for the code snippet widget
+ * @param {Object} editor the ckeditor instance
+ * @param {Node} textarea ckeditor.dom element
+ * @returns {Object} a complex widget definition that includes highlight functionality
+ */
+CKEDITOR.config.getCodeSnippetWidget = function getCodeSnippetWidget(
+ editor,
+ textarea
+) {
+ const { lang, config } = editor;
+ const codesnippet = lang.codesnippet;
+ const codeClasses = { // @TODO: find out why this can't be set on CKEDITOR.config
+ 'hljs': 'hljs',
+ 'highlightjs': 'hljs',
+ 'prism': 'prism',
+ 'prismjs': 'prism',
+ };
+ const highlighterType = config.codeSnippet_highlighter;
+ const codeClass = codeClasses[highlighterType];
+ const whitespaceOnlyRegex = /^[\s\n\r]*$/;
+ const newLineRegex = /\r?\n/g;
+ /**
+ * Returns an **array** of child elements, with whitespace-only text nodes
+ * @TODO consider moving this out of here? maybe to a utils or something?
+ * @param {CKEDITOR.htmlParser.element} parentElement
+ * @returns {Array} array of CKEDITOR.htmlParser.node
+ */
+
+ function getNonEmptyChildren(parentElement) {
+ var ret = [],
+ preChildrenList = parentElement.children,
+ curNode;
+
+ // Filter out empty text nodes.
+ for (var i = preChildrenList.length - 1; i >= 0; i--) {
+ curNode = preChildrenList[i];
+
+ if (
+ curNode.type != CKEDITOR.NODE_TEXT ||
+ !curNode.value.match(whitespaceOnlyRegex)
+ )
+ ret.push(curNode);
+ }
+
+ return ret;
+ }
+ // @TODO make this a class?
+ return {
+ allowedContent: "pre; code(language-*)",
+ // Actually we need both - pre and code, but ACF does not make it possible
+ // to defire required content with "and" operator.
+ requiredContent: "pre",
+ styleableElements: "pre",
+ template: `
`,
+ dialog: "codeSnippet",
+ pathName: codesnippet.pathName,
+ mask: true,
+ parts: {
+ pre: "pre",
+ code: "code",
+ },
+ highlight() {
+ const that = this;
+ let widgetData = this.data;
+ const callback = function (formatted) {
+ // @TODO: do we still care about IE8?
+ // IE8 (not supported browser) have issue with new line chars, when using innerHTML.
+ // It will simply strip it.
+ that.parts.code.setHtml(
+ editor.plugins.codesnippet.isSupportedEnvironment()
+ ? formatted
+ : formatted.replace(newLineRegex, "
")
+ );
+ };
+
+ // Set plain code first, so even if custom handler will not call it the code will be there.
+ callback(CKEDITOR.tools.htmlEncode(widgetData.code));
+
+ // Call higlighter to apply its custom highlighting.
+ // wrap in a try-catch because if we don't, and it fails, then the editor stops working, too.
+ try {
+ editor._.codesnippet.highlighter.highlight(
+ widgetData.code,
+ widgetData.lang,
+ function (formatted) {
+ editor.fire("lockSnapshot");
+ callback(formatted);
+ editor.fire("unlockSnapshot");
+ }
+ );
+ } catch (highlightErr) {
+ console.error(highlightErr, widgetData);
+ }
+ },
+ data() {
+ const newData = this.data;
+ const { oldData } = this;
+
+ if (newData.code)
+ this.parts.code.setHtml(CKEDITOR.tools.htmlEncode(newData.code));
+
+ // @TODO: what about lang- classes or instances where just the language is the class? find a solution for those.
+ // Remove old .language-* class.
+ if (oldData && newData.lang != oldData.lang)
+ this.parts.code.removeClass("language-" + oldData.lang);
+
+ // Lang needs to be specified in order to apply formatting.
+ if (newData.lang) {
+ // Apply new .language-* class.
+ this.parts.code.addClass("language-" + newData.lang);
+
+ this.highlight();
+ }
+
+ // Save oldData.
+ this.oldData = CKEDITOR.tools.copy(newData);
+ },
+ // Upcasts ...
+ upcast(el, data) {
+ if (el.name != "pre") return;
+
+ const childrenArray = getNonEmptyChildren(el);
+ let code;
+
+ if (
+ childrenArray.length != 1 ||
+ (code = childrenArray[0]).name != "code"
+ ) {
+ return;
+ }
+
+ // Upcast with text only: https://dev.ckeditor.com/ticket/11926#comment:4
+ if (
+ code.children.length != 1 ||
+ code.children[0].type != CKEDITOR.NODE_TEXT
+ ) {
+ return;
+ }
+
+ // Read language-* from class attribute.
+ var matchResult = editor._.codesnippet.langsRegex.exec(
+ code.attributes["class"]
+ );
+
+ // because the regex can match lang- or language-, or , we get multiple results. so get the last one.
+ if (matchResult) data.lang = matchResult[matchResult.length - 1];
+ // Use textarea to decode HTML entities (https://dev.ckeditor.com/ticket/11926).
+ textarea.setHtml(code.getHtml());
+ data.code = textarea.getValue();
+
+ code.addClass(codeClass);
+
+ return el;
+ },
+ // Downcasts to ...
+ downcast(el) {
+ var code = el.getFirst("code");
+
+ // Remove pretty formatting from ....
+ code.children.length = 0;
+
+ // Remove config#codeSnippet_codeClass.
+ code.removeClass(codeClass);
+
+ // Set raw text inside ....
+ code.add(
+ new CKEDITOR.htmlParser.text(CKEDITOR.tools.htmlEncode(this.data.code))
+ );
+
+ return el;
+ },
+ };
+};
/**
* Restricts languages available in the "Code Snippet" dialog window.
* An empty value is always added to the list.
@@ -458,11 +613,11 @@ CKEDITOR.config.codeSnippet_codeClass = 'hljs';
* Read more in the {@glink features/codesnippet#changing-supported-languages documentation}
* and see the {@glink examples/codesnippet example}.
*
- * // Restricts languages to JavaScript and PHP.
- * config.codeSnippet_languages = {
- * javascript: 'JavaScript',
- * php: 'PHP'
- * };
+ * // Restricts languages to JavaScript and PHP.
+ * config.codeSnippet_languages = {
+ * javascript: 'JavaScript',
+ * php: 'PHP'
+ * };
*
* @since 4.4.0
* @cfg {Object} [codeSnippet_languages=null]
@@ -478,11 +633,11 @@ CKEDITOR.config.codeSnippet_codeClass = 'hljs';
* Read more in the {@glink features/codesnippet#changing-highlighter-theme documentation}
* and see the {@glink examples/codesnippet example}.
*
- * // Changes the theme to "pojoaque".
- * config.codeSnippet_theme = 'pojoaque';
+ * // Changes the theme to "pojoaque".
+ * config.codeSnippet_theme = 'pojoaque';
*
* @since 4.4.0
* @cfg {String} [codeSnippet_theme='default']
* @member CKEDITOR.config
*/
-CKEDITOR.config.codeSnippet_theme = 'default';
+CKEDITOR.config.codeSnippet_theme = "default";