plugin.js 14 KB

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