plugin.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. /**
  6. * @fileOverview [Mathematical Formulas](http://ckeditor.com/addon/mathjax) plugin.
  7. */
  8. 'use strict';
  9. ( function() {
  10. var cdn = 'http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML';
  11. CKEDITOR.plugins.add( 'mathjax', {
  12. lang: 'af,ar,ca,cs,cy,da,de,el,en,en-gb,eo,es,fa,fi,fr,gl,he,hr,hu,it,ja,km,ku,lt,nb,nl,no,pl,pt,pt-br,ro,ru,sk,sl,sq,sv,tr,tt,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
  13. requires: 'widget,dialog',
  14. icons: 'mathjax',
  15. hidpi: true, // %REMOVE_LINE_CORE%
  16. init: function( editor ) {
  17. var cls = editor.config.mathJaxClass || 'math-tex';
  18. editor.widgets.add( 'mathjax', {
  19. inline: true,
  20. dialog: 'mathjax',
  21. button: editor.lang.mathjax.button,
  22. mask: true,
  23. allowedContent: 'span(!' + cls + ')',
  24. // Allow style classes only on spans having mathjax class.
  25. styleToAllowedContentRules: function( style ) {
  26. var classes = style.getClassesArray();
  27. if ( !classes )
  28. return null;
  29. classes.push( '!' + cls );
  30. return 'span(' + classes.join( ',' ) + ')';
  31. },
  32. pathName: editor.lang.mathjax.pathName,
  33. template: '<span class="' + cls + '" style="display:inline-block" data-cke-survive=1></span>',
  34. parts: {
  35. span: 'span'
  36. },
  37. defaults: {
  38. math: '\\(x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}\\)'
  39. },
  40. init: function() {
  41. var iframe = this.parts.span.getChild( 0 );
  42. // Check if span contains iframe and create it otherwise.
  43. if ( !iframe || iframe.type != CKEDITOR.NODE_ELEMENT || !iframe.is( 'iframe' ) ) {
  44. iframe = new CKEDITOR.dom.element( 'iframe' );
  45. iframe.setAttributes( {
  46. style: 'border:0;width:0;height:0',
  47. scrolling: 'no',
  48. frameborder: 0,
  49. allowTransparency: true,
  50. src: CKEDITOR.plugins.mathjax.fixSrc
  51. } );
  52. this.parts.span.append( iframe );
  53. }
  54. // Wait for ready because on some browsers iFrame will not
  55. // have document element until it is put into document.
  56. // This is a problem when you crate widget using dialog.
  57. this.once( 'ready', function() {
  58. // Src attribute must be recreated to fix custom domain error after undo
  59. // (see iFrame.removeAttribute( 'src' ) in frameWrapper.load).
  60. if ( CKEDITOR.env.ie )
  61. iframe.setAttribute( 'src', CKEDITOR.plugins.mathjax.fixSrc );
  62. this.frameWrapper = new CKEDITOR.plugins.mathjax.frameWrapper( iframe, editor );
  63. this.frameWrapper.setValue( this.data.math );
  64. } );
  65. },
  66. data: function() {
  67. if ( this.frameWrapper )
  68. this.frameWrapper.setValue( this.data.math );
  69. },
  70. upcast: function( el, data ) {
  71. if ( !( el.name == 'span' && el.hasClass( cls ) ) )
  72. return;
  73. if ( el.children.length > 1 || el.children[ 0 ].type != CKEDITOR.NODE_TEXT )
  74. return;
  75. data.math = CKEDITOR.tools.htmlDecode( el.children[ 0 ].value );
  76. // Add style display:inline-block to have proper height of widget wrapper and mask.
  77. var attrs = el.attributes;
  78. if ( attrs.style )
  79. attrs.style += ';display:inline-block';
  80. else
  81. attrs.style = 'display:inline-block';
  82. // Add attribute to prevent deleting empty span in data processing.
  83. attrs[ 'data-cke-survive' ] = 1;
  84. el.children[ 0 ].remove();
  85. return el;
  86. },
  87. downcast: function( el ) {
  88. el.children[ 0 ].replaceWith( new CKEDITOR.htmlParser.text( CKEDITOR.tools.htmlEncode( this.data.math ) ) );
  89. // Remove style display:inline-block.
  90. var attrs = el.attributes;
  91. attrs.style = attrs.style.replace( /display:\s?inline-block;?\s?/, '' );
  92. if ( attrs.style === '' )
  93. delete attrs.style;
  94. return el;
  95. }
  96. } );
  97. // Add dialog.
  98. CKEDITOR.dialog.add( 'mathjax', this.path + 'dialogs/mathjax.js' );
  99. // Add MathJax script to page preview.
  100. editor.on( 'contentPreview', function( evt ) {
  101. evt.data.dataValue = evt.data.dataValue.replace( /<\/head>/,
  102. '<script src="' + ( editor.config.mathJaxLib ? CKEDITOR.getUrl( editor.config.mathJaxLib ) : cdn ) + '"><\/script><\/head>' );
  103. } );
  104. editor.on( 'paste', function( evt ) {
  105. // Firefox does remove iFrame elements from pasted content so this event do the same on other browsers.
  106. // Also iFrame in paste content is reason of "Unspecified error" in IE9 (#10857).
  107. var regex = new RegExp( '<span[^>]*?' + cls + '.*?<\/span>', 'ig' );
  108. evt.data.dataValue = evt.data.dataValue.replace( regex, function( match ) {
  109. return match.replace( /(<iframe.*?\/iframe>)/i, '' );
  110. } );
  111. } );
  112. }
  113. } );
  114. /**
  115. * @private
  116. * @singleton
  117. * @class CKEDITOR.plugins.mathjax
  118. */
  119. CKEDITOR.plugins.mathjax = {};
  120. /**
  121. * A variable to fix problems with `iframe`. This variable is global
  122. * because it is used in both the widget and the dialog window.
  123. *
  124. * @private
  125. * @property {String} fixSrc
  126. */
  127. CKEDITOR.plugins.mathjax.fixSrc =
  128. // In Firefox src must exist and be different than about:blank to emit load event.
  129. CKEDITOR.env.gecko ? 'javascript:true' : // jshint ignore:line
  130. // Support for custom document.domain in IE.
  131. CKEDITOR.env.ie ? 'javascript:' + // jshint ignore:line
  132. 'void((function(){' + encodeURIComponent(
  133. 'document.open();' +
  134. '(' + CKEDITOR.tools.fixDomain + ')();' +
  135. 'document.close();'
  136. ) + '})())' :
  137. // In Chrome src must be undefined to emit load event.
  138. 'javascript:void(0)'; // jshint ignore:line
  139. /**
  140. * Loading indicator image generated by http://preloaders.net.
  141. *
  142. * @private
  143. * @property {String} loadingIcon
  144. */
  145. CKEDITOR.plugins.mathjax.loadingIcon = CKEDITOR.plugins.get( 'mathjax' ).path + 'images/loader.gif';
  146. /**
  147. * Computes predefined styles and copies them to another element.
  148. *
  149. * @private
  150. * @param {CKEDITOR.dom.element} from Copy source.
  151. * @param {CKEDITOR.dom.element} to Copy target.
  152. */
  153. CKEDITOR.plugins.mathjax.copyStyles = function( from, to ) {
  154. var stylesToCopy = [ 'color', 'font-family', 'font-style', 'font-weight', 'font-variant', 'font-size' ];
  155. for ( var i = 0; i < stylesToCopy.length; i++ ) {
  156. var key = stylesToCopy[ i ],
  157. val = from.getComputedStyle( key );
  158. if ( val )
  159. to.setStyle( key, val );
  160. }
  161. };
  162. /**
  163. * Trims MathJax value from '\(1+1=2\)' to '1+1=2'.
  164. *
  165. * @private
  166. * @param {String} value String to trim.
  167. * @returns {String} Trimed string.
  168. */
  169. CKEDITOR.plugins.mathjax.trim = function( value ) {
  170. var begin = value.indexOf( '\\(' ) + 2,
  171. end = value.lastIndexOf( '\\)' );
  172. return value.substring( begin, end );
  173. };
  174. /**
  175. * FrameWrapper is responsible for communication between the MathJax library
  176. * and the `iframe` element that is used for rendering mathematical formulas
  177. * inside the editor.
  178. * It lets you create visual mathematics by using the
  179. * {@link CKEDITOR.plugins.mathjax.frameWrapper#setValue setValue} method.
  180. *
  181. * @private
  182. * @class CKEDITOR.plugins.mathjax.frameWrapper
  183. * @constructor Creates a class instance.
  184. * @param {CKEDITOR.dom.element} iFrame The `iframe` element to be wrapped.
  185. * @param {CKEDITOR.editor} editor The editor instance.
  186. */
  187. if ( !( CKEDITOR.env.ie && CKEDITOR.env.version == 8 ) ) {
  188. CKEDITOR.plugins.mathjax.frameWrapper = function( iFrame, editor ) {
  189. var buffer, preview, value, newValue,
  190. doc = iFrame.getFrameDocument(),
  191. // Is MathJax loaded and ready to work.
  192. isInit = false,
  193. // Is MathJax parsing Tex.
  194. isRunning = false,
  195. // Function called when MathJax is loaded.
  196. loadedHandler = CKEDITOR.tools.addFunction( function() {
  197. preview = doc.getById( 'preview' );
  198. buffer = doc.getById( 'buffer' );
  199. isInit = true;
  200. if ( newValue )
  201. update();
  202. // Private! For test usage only.
  203. CKEDITOR.fire( 'mathJaxLoaded', iFrame );
  204. } ),
  205. // Function called when MathJax finish his job.
  206. updateDoneHandler = CKEDITOR.tools.addFunction( function() {
  207. CKEDITOR.plugins.mathjax.copyStyles( iFrame, preview );
  208. preview.setHtml( buffer.getHtml() );
  209. editor.fire( 'lockSnapshot' );
  210. iFrame.setStyles( {
  211. height: 0,
  212. width: 0
  213. } );
  214. // Set iFrame dimensions.
  215. var height = Math.max( doc.$.body.offsetHeight, doc.$.documentElement.offsetHeight ),
  216. width = Math.max( preview.$.offsetWidth, doc.$.body.scrollWidth );
  217. iFrame.setStyles( {
  218. height: height + 'px',
  219. width: width + 'px'
  220. } );
  221. editor.fire( 'unlockSnapshot' );
  222. // Private! For test usage only.
  223. CKEDITOR.fire( 'mathJaxUpdateDone', iFrame );
  224. // If value changed in the meantime update it again.
  225. if ( value != newValue )
  226. update();
  227. else
  228. isRunning = false;
  229. } );
  230. iFrame.on( 'load', load );
  231. load();
  232. function load() {
  233. doc = iFrame.getFrameDocument();
  234. if ( doc.getById( 'preview' ) )
  235. return;
  236. // Because of IE9 bug in a src attribute can not be javascript
  237. // when you undo (#10930). If you have iFrame with javascript in src
  238. // and call insertBefore on such element then IE9 will see crash.
  239. if ( CKEDITOR.env.ie )
  240. iFrame.removeAttribute( 'src' );
  241. doc.write( '<!DOCTYPE html>' +
  242. '<html>' +
  243. '<head>' +
  244. '<meta charset="utf-8">' +
  245. '<script type="text/x-mathjax-config">' +
  246. // MathJax configuration, disable messages.
  247. 'MathJax.Hub.Config( {' +
  248. 'showMathMenu: false,' +
  249. 'messageStyle: "none"' +
  250. '} );' +
  251. // Get main CKEDITOR form parent.
  252. 'function getCKE() {' +
  253. 'if ( typeof window.parent.CKEDITOR == \'object\' ) {' +
  254. 'return window.parent.CKEDITOR;' +
  255. '} else {' +
  256. 'return window.parent.parent.CKEDITOR;' +
  257. '}' +
  258. '}' +
  259. // Run MathJax.Hub with its actual parser and call callback function after that.
  260. // Because MathJax.Hub is asynchronous create MathJax.Hub.Queue to wait with callback.
  261. 'function update() {' +
  262. 'MathJax.Hub.Queue(' +
  263. '[ \'Typeset\', MathJax.Hub, this.buffer ],' +
  264. 'function() {' +
  265. 'getCKE().tools.callFunction( ' + updateDoneHandler + ' );' +
  266. '}' +
  267. ');' +
  268. '}' +
  269. // Run MathJax for the first time, when the script is loaded.
  270. // Callback function will be called then it's done.
  271. 'MathJax.Hub.Queue( function() {' +
  272. 'getCKE().tools.callFunction(' + loadedHandler + ');' +
  273. '} );' +
  274. '</script>' +
  275. // Load MathJax lib.
  276. '<script src="' + ( editor.config.mathJaxLib || cdn ) + '"></script>' +
  277. '</head>' +
  278. '<body style="padding:0;margin:0;background:transparent;overflow:hidden">' +
  279. '<span id="preview"></span>' +
  280. // Render everything here and after that copy it to the preview.
  281. '<span id="buffer" style="display:none"></span>' +
  282. '</body>' +
  283. '</html>' );
  284. }
  285. // Run MathJax parsing Tex.
  286. function update() {
  287. isRunning = true;
  288. value = newValue;
  289. editor.fire( 'lockSnapshot' );
  290. buffer.setHtml( value );
  291. // Set loading indicator.
  292. preview.setHtml( '<img src=' + CKEDITOR.plugins.mathjax.loadingIcon + ' alt=' + editor.lang.mathjax.loading + '>' );
  293. iFrame.setStyles( {
  294. height: '16px',
  295. width: '16px',
  296. display: 'inline',
  297. 'vertical-align': 'middle'
  298. } );
  299. editor.fire( 'unlockSnapshot' );
  300. // Run MathJax.
  301. doc.getWindow().$.update( value );
  302. }
  303. return {
  304. /**
  305. * Sets the TeX value to be displayed in the `iframe` element inside
  306. * the editor. This function will activate the MathJax
  307. * library which interprets TeX expressions and converts them into
  308. * their representation that is displayed in the editor.
  309. *
  310. * @param {String} value TeX string.
  311. */
  312. setValue: function( value ) {
  313. newValue = CKEDITOR.tools.htmlEncode( value );
  314. if ( isInit && !isRunning )
  315. update();
  316. }
  317. };
  318. };
  319. } else {
  320. // In IE8 MathJax does not work stable so instead of using standard
  321. // frame wrapper it is replaced by placeholder to show pure TeX in iframe.
  322. CKEDITOR.plugins.mathjax.frameWrapper = function( iFrame, editor ) {
  323. iFrame.getFrameDocument().write( '<!DOCTYPE html>' +
  324. '<html>' +
  325. '<head>' +
  326. '<meta charset="utf-8">' +
  327. '</head>' +
  328. '<body style="padding:0;margin:0;background:transparent;overflow:hidden">' +
  329. '<span style="white-space:nowrap;" id="tex"></span>' +
  330. '</body>' +
  331. '</html>' );
  332. return {
  333. setValue: function( value ) {
  334. var doc = iFrame.getFrameDocument(),
  335. tex = doc.getById( 'tex' );
  336. tex.setHtml( CKEDITOR.plugins.mathjax.trim( CKEDITOR.tools.htmlEncode( value ) ) );
  337. CKEDITOR.plugins.mathjax.copyStyles( iFrame, tex );
  338. editor.fire( 'lockSnapshot' );
  339. iFrame.setStyles( {
  340. width: Math.min( 250, tex.$.offsetWidth ) + 'px',
  341. height: doc.$.body.offsetHeight + 'px',
  342. display: 'inline',
  343. 'vertical-align': 'middle'
  344. } );
  345. editor.fire( 'unlockSnapshot' );
  346. }
  347. };
  348. };
  349. }
  350. } )();
  351. /**
  352. * Sets the path to the MathJax library. It can be both a local
  353. * resource and a location different than the default CDN.
  354. *
  355. * Please note that this must be a full or absolute path.
  356. *
  357. * config.mathJaxLib = 'http:\/\/example.com\/libs\/MathJax.js';
  358. *
  359. * @cfg {String} [mathJaxLib='http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML']
  360. * @member CKEDITOR.config
  361. */
  362. /**
  363. * Sets the default class for `span` elements that will be
  364. * converted into [Mathematical Formulas](http://ckeditor.com/addon/mathjax)
  365. * widgets.
  366. *
  367. * If you set it to the following:
  368. *
  369. * config.mathJaxClass = 'my-math';
  370. *
  371. * The code below will be recognized as a Mathematical Formulas widget.
  372. *
  373. * <span class="my-math">\( \sqrt{4} = 2 \)</span>
  374. *
  375. * @cfg {String} [mathJaxClass='math-tex']
  376. * @member CKEDITOR.config
  377. */