plugin.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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. ( function() {
  6. CKEDITOR.plugins.add( 'panel', {
  7. beforeInit: function( editor ) {
  8. editor.ui.addHandler( CKEDITOR.UI_PANEL, CKEDITOR.ui.panel.handler );
  9. }
  10. } );
  11. /**
  12. * Panel UI element.
  13. *
  14. * @readonly
  15. * @property {String} [='panel']
  16. * @member CKEDITOR
  17. */
  18. CKEDITOR.UI_PANEL = 'panel';
  19. /**
  20. * @class
  21. * @constructor Creates a panel class instance.
  22. * @param {CKEDITOR.dom.document} document
  23. * @param {Object} definition
  24. */
  25. CKEDITOR.ui.panel = function( document, definition ) {
  26. // Copy all definition properties to this object.
  27. if ( definition )
  28. CKEDITOR.tools.extend( this, definition );
  29. // Set defaults.
  30. CKEDITOR.tools.extend( this, {
  31. className: '',
  32. css: []
  33. } );
  34. this.id = CKEDITOR.tools.getNextId();
  35. this.document = document;
  36. this.isFramed = this.forceIFrame || this.css.length;
  37. this._ = {
  38. blocks: {}
  39. };
  40. };
  41. /**
  42. * Represents panel handler object.
  43. *
  44. * @class
  45. * @singleton
  46. * @extends CKEDITOR.ui.handlerDefinition
  47. */
  48. CKEDITOR.ui.panel.handler = {
  49. /**
  50. * Transforms a panel definition in a {@link CKEDITOR.ui.panel} instance.
  51. *
  52. * @param {Object} definition
  53. * @returns {CKEDITOR.ui.panel}
  54. */
  55. create: function( definition ) {
  56. return new CKEDITOR.ui.panel( definition );
  57. }
  58. };
  59. var panelTpl = CKEDITOR.addTemplate( 'panel', '<div lang="{langCode}" id="{id}" dir={dir}' +
  60. ' class="cke cke_reset_all {editorId} cke_panel cke_panel {cls} cke_{dir}"' +
  61. ' style="z-index:{z-index}" role="presentation">' +
  62. '{frame}' +
  63. '</div>' );
  64. var frameTpl = CKEDITOR.addTemplate( 'panel-frame', '<iframe id="{id}" class="cke_panel_frame" role="presentation" frameborder="0" src="{src}"></iframe>' );
  65. var frameDocTpl = CKEDITOR.addTemplate( 'panel-frame-inner', '<!DOCTYPE html>' +
  66. '<html class="cke_panel_container {env}" dir="{dir}" lang="{langCode}">' +
  67. '<head>{css}</head>' +
  68. '<body class="cke_{dir}"' +
  69. ' style="margin:0;padding:0" onload="{onload}"></body>' +
  70. '<\/html>' );
  71. /** @class CKEDITOR.ui.panel */
  72. CKEDITOR.ui.panel.prototype = {
  73. /**
  74. * Renders the combo.
  75. *
  76. * @param {CKEDITOR.editor} editor The editor instance which this button is
  77. * to be used by.
  78. * @param {Array} [output] The output array to which append the HTML relative
  79. * to this button.
  80. */
  81. render: function( editor, output ) {
  82. this.getHolderElement = function() {
  83. var holder = this._.holder;
  84. if ( !holder ) {
  85. if ( this.isFramed ) {
  86. var iframe = this.document.getById( this.id + '_frame' ),
  87. parentDiv = iframe.getParent(),
  88. doc = iframe.getFrameDocument();
  89. // Make it scrollable on iOS. (#8308)
  90. CKEDITOR.env.iOS && parentDiv.setStyles( {
  91. 'overflow': 'scroll',
  92. '-webkit-overflow-scrolling': 'touch'
  93. } );
  94. var onLoad = CKEDITOR.tools.addFunction( CKEDITOR.tools.bind( function() {
  95. this.isLoaded = true;
  96. if ( this.onLoad )
  97. this.onLoad();
  98. }, this ) );
  99. doc.write( frameDocTpl.output( CKEDITOR.tools.extend( {
  100. css: CKEDITOR.tools.buildStyleHtml( this.css ),
  101. onload: 'window.parent.CKEDITOR.tools.callFunction(' + onLoad + ');'
  102. }, data ) ) );
  103. var win = doc.getWindow();
  104. // Register the CKEDITOR global.
  105. win.$.CKEDITOR = CKEDITOR;
  106. // Arrow keys for scrolling is only preventable with 'keypress' event in Opera (#4534).
  107. doc.on( 'keydown', function( evt ) {
  108. var keystroke = evt.data.getKeystroke(),
  109. dir = this.document.getById( this.id ).getAttribute( 'dir' );
  110. // Delegate key processing to block.
  111. if ( this._.onKeyDown && this._.onKeyDown( keystroke ) === false ) {
  112. evt.data.preventDefault();
  113. return;
  114. }
  115. // ESC/ARROW-LEFT(ltr) OR ARROW-RIGHT(rtl)
  116. if ( keystroke == 27 || keystroke == ( dir == 'rtl' ? 39 : 37 ) ) {
  117. if ( this.onEscape && this.onEscape( keystroke ) === false )
  118. evt.data.preventDefault();
  119. }
  120. }, this );
  121. holder = doc.getBody();
  122. holder.unselectable();
  123. CKEDITOR.env.air && CKEDITOR.tools.callFunction( onLoad );
  124. } else {
  125. holder = this.document.getById( this.id );
  126. }
  127. this._.holder = holder;
  128. }
  129. return holder;
  130. };
  131. var data = {
  132. editorId: editor.id,
  133. id: this.id,
  134. langCode: editor.langCode,
  135. dir: editor.lang.dir,
  136. cls: this.className,
  137. frame: '',
  138. env: CKEDITOR.env.cssClass,
  139. 'z-index': editor.config.baseFloatZIndex + 1
  140. };
  141. if ( this.isFramed ) {
  142. // With IE, the custom domain has to be taken care at first,
  143. // for other browers, the 'src' attribute should be left empty to
  144. // trigger iframe's 'load' event.
  145. var src =
  146. CKEDITOR.env.air ? 'javascript:void(0)' : // jshint ignore:line
  147. CKEDITOR.env.ie ? 'javascript:void(function(){' + encodeURIComponent( // jshint ignore:line
  148. 'document.open();' +
  149. // In IE, the document domain must be set any time we call document.open().
  150. '(' + CKEDITOR.tools.fixDomain + ')();' +
  151. 'document.close();'
  152. ) + '}())' :
  153. '';
  154. data.frame = frameTpl.output( {
  155. id: this.id + '_frame',
  156. src: src
  157. } );
  158. }
  159. var html = panelTpl.output( data );
  160. if ( output )
  161. output.push( html );
  162. return html;
  163. },
  164. /**
  165. * @todo
  166. */
  167. addBlock: function( name, block ) {
  168. block = this._.blocks[ name ] = block instanceof CKEDITOR.ui.panel.block ? block : new CKEDITOR.ui.panel.block( this.getHolderElement(), block );
  169. if ( !this._.currentBlock )
  170. this.showBlock( name );
  171. return block;
  172. },
  173. /**
  174. * @todo
  175. */
  176. getBlock: function( name ) {
  177. return this._.blocks[ name ];
  178. },
  179. /**
  180. * @todo
  181. */
  182. showBlock: function( name ) {
  183. var blocks = this._.blocks,
  184. block = blocks[ name ],
  185. current = this._.currentBlock;
  186. // ARIA role works better in IE on the body element, while on the iframe
  187. // for FF. (#8864)
  188. var holder = !this.forceIFrame || CKEDITOR.env.ie ? this._.holder : this.document.getById( this.id + '_frame' );
  189. if ( current )
  190. current.hide();
  191. this._.currentBlock = block;
  192. CKEDITOR.fire( 'ariaWidget', holder );
  193. // Reset the focus index, so it will always go into the first one.
  194. block._.focusIndex = -1;
  195. this._.onKeyDown = block.onKeyDown && CKEDITOR.tools.bind( block.onKeyDown, block );
  196. block.show();
  197. return block;
  198. },
  199. /**
  200. * @todo
  201. */
  202. destroy: function() {
  203. this.element && this.element.remove();
  204. }
  205. };
  206. /**
  207. * @class
  208. *
  209. * @todo class and all methods
  210. */
  211. CKEDITOR.ui.panel.block = CKEDITOR.tools.createClass( {
  212. /**
  213. * Creates a block class instances.
  214. *
  215. * @constructor
  216. * @todo
  217. */
  218. $: function( blockHolder, blockDefinition ) {
  219. this.element = blockHolder.append( blockHolder.getDocument().createElement( 'div', {
  220. attributes: {
  221. 'tabindex': -1,
  222. 'class': 'cke_panel_block'
  223. },
  224. styles: {
  225. display: 'none'
  226. }
  227. } ) );
  228. // Copy all definition properties to this object.
  229. if ( blockDefinition )
  230. CKEDITOR.tools.extend( this, blockDefinition );
  231. // Set the a11y attributes of this element ...
  232. this.element.setAttributes( {
  233. 'role': this.attributes.role || 'presentation',
  234. 'aria-label': this.attributes[ 'aria-label' ],
  235. 'title': this.attributes.title || this.attributes[ 'aria-label' ]
  236. } );
  237. this.keys = {};
  238. this._.focusIndex = -1;
  239. // Disable context menu for panels.
  240. this.element.disableContextMenu();
  241. },
  242. _: {
  243. /**
  244. * Mark the item specified by the index as current activated.
  245. */
  246. markItem: function( index ) {
  247. if ( index == -1 )
  248. return;
  249. var links = this.element.getElementsByTag( 'a' );
  250. var item = links.getItem( this._.focusIndex = index );
  251. // Safari need focus on the iframe window first(#3389), but we need
  252. // lock the blur to avoid hiding the panel.
  253. if ( CKEDITOR.env.webkit )
  254. item.getDocument().getWindow().focus();
  255. item.focus();
  256. this.onMark && this.onMark( item );
  257. }
  258. },
  259. proto: {
  260. show: function() {
  261. this.element.setStyle( 'display', '' );
  262. },
  263. hide: function() {
  264. if ( !this.onHide || this.onHide.call( this ) !== true )
  265. this.element.setStyle( 'display', 'none' );
  266. },
  267. onKeyDown: function( keystroke, noCycle ) {
  268. var keyAction = this.keys[ keystroke ];
  269. switch ( keyAction ) {
  270. // Move forward.
  271. case 'next':
  272. var index = this._.focusIndex,
  273. links = this.element.getElementsByTag( 'a' ),
  274. link;
  275. while ( ( link = links.getItem( ++index ) ) ) {
  276. // Move the focus only if the element is marked with
  277. // the _cke_focus and it it's visible (check if it has
  278. // width).
  279. if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth ) {
  280. this._.focusIndex = index;
  281. link.focus();
  282. break;
  283. }
  284. }
  285. // If no link was found, cycle and restart from the top. (#11125)
  286. if ( !link && !noCycle ) {
  287. this._.focusIndex = -1;
  288. return this.onKeyDown( keystroke, 1 );
  289. }
  290. return false;
  291. // Move backward.
  292. case 'prev':
  293. index = this._.focusIndex;
  294. links = this.element.getElementsByTag( 'a' );
  295. while ( index > 0 && ( link = links.getItem( --index ) ) ) {
  296. // Move the focus only if the element is marked with
  297. // the _cke_focus and it it's visible (check if it has
  298. // width).
  299. if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth ) {
  300. this._.focusIndex = index;
  301. link.focus();
  302. break;
  303. }
  304. // Make sure link is null when the loop ends and nothing was
  305. // found (#11125).
  306. link = null;
  307. }
  308. // If no link was found, cycle and restart from the bottom. (#11125)
  309. if ( !link && !noCycle ) {
  310. this._.focusIndex = links.count();
  311. return this.onKeyDown( keystroke, 1 );
  312. }
  313. return false;
  314. case 'click':
  315. case 'mouseup':
  316. index = this._.focusIndex;
  317. link = index >= 0 && this.element.getElementsByTag( 'a' ).getItem( index );
  318. if ( link )
  319. link.$[ keyAction ] ? link.$[ keyAction ]() : link.$[ 'on' + keyAction ]();
  320. return false;
  321. }
  322. return true;
  323. }
  324. }
  325. } );
  326. } )();
  327. /**
  328. * Fired when a panel is added to the document.
  329. *
  330. * @event ariaWidget
  331. * @member CKEDITOR
  332. * @param {Object} data The element wrapping the panel.
  333. */