Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

plugin.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. /**
  6. * @fileOverview A plugin created to handle ticket #11064. While the issue is caused by native WebKit/Blink behaviour,
  7. * this plugin can be easily detached or modified when the issue is fixed in the browsers without changing the core.
  8. * When Ctrl/Cmd + A is pressed to select all content it does not work due to a bug in
  9. * Webkit/Blink if a non-editable element is at the beginning or the end of the content.
  10. */
  11. ( function() {
  12. 'use strict';
  13. CKEDITOR.plugins.add( 'widgetselection', {
  14. init: function( editor ) {
  15. if ( CKEDITOR.env.webkit ) {
  16. var widgetselection = CKEDITOR.plugins.widgetselection;
  17. editor.on( 'contentDom', function( evt ) {
  18. var editor = evt.editor,
  19. doc = editor.document,
  20. editable = editor.editable();
  21. editable.attachListener( doc, 'keydown', function( evt ) {
  22. var data = evt.data.$;
  23. // Ctrl/Cmd + A
  24. if ( evt.data.getKey() == 65 && ( CKEDITOR.env.mac && data.metaKey || !CKEDITOR.env.mac && data.ctrlKey ) ) {
  25. // Defer the call so the selection is already changed by the pressed keys.
  26. CKEDITOR.tools.setTimeout( function() {
  27. // Manage filler elements on keydown. If there is no need
  28. // to add fillers, we need to check and clean previously used once.
  29. if ( !widgetselection.addFillers( editable ) ) {
  30. widgetselection.removeFillers( editable );
  31. }
  32. }, 0 );
  33. }
  34. }, null, null, -1 );
  35. // Check and clean previously used fillers.
  36. editor.on( 'selectionCheck', function( evt ) {
  37. widgetselection.removeFillers( evt.editor.editable() );
  38. } );
  39. // Remove fillers on paste before data gets inserted into editor.
  40. editor.on( 'paste', function( evt ) {
  41. evt.data.dataValue = widgetselection.cleanPasteData( evt.data.dataValue );
  42. } );
  43. if ( 'selectall' in editor.plugins ) {
  44. widgetselection.addSelectAllIntegration( editor );
  45. }
  46. } );
  47. }
  48. }
  49. } );
  50. /**
  51. * A set of helper methods for the Widget Selection plugin.
  52. *
  53. * @property widgetselection
  54. * @member CKEDITOR.plugins
  55. * @since 4.6.1
  56. */
  57. CKEDITOR.plugins.widgetselection = {
  58. /**
  59. * The start filler element reference.
  60. *
  61. * @property {CKEDITOR.dom.element}
  62. * @member CKEDITOR.plugins.widgetselection
  63. * @private
  64. */
  65. startFiller: null,
  66. /**
  67. * The end filler element reference.
  68. *
  69. * @property {CKEDITOR.dom.element}
  70. * @member CKEDITOR.plugins.widgetselection
  71. * @private
  72. */
  73. endFiller: null,
  74. /**
  75. * An attribute which identifies the filler element.
  76. *
  77. * @property {String}
  78. * @member CKEDITOR.plugins.widgetselection
  79. * @private
  80. */
  81. fillerAttribute: 'data-cke-filler-webkit',
  82. /**
  83. * The default content of the filler element. Note: The filler needs to have `visible` content.
  84. * Unprintable elements or empty content do not help as a workaround.
  85. *
  86. * @property {String}
  87. * @member CKEDITOR.plugins.widgetselection
  88. * @private
  89. */
  90. fillerContent: ' ',
  91. /**
  92. * Tag name which is used to create fillers.
  93. *
  94. * @property {String}
  95. * @member CKEDITOR.plugins.widgetselection
  96. * @private
  97. */
  98. fillerTagName: 'div',
  99. /**
  100. * Adds a filler before or after a non-editable element at the beginning or the end of the `editable`.
  101. *
  102. * @param {CKEDITOR.editable} editable
  103. * @returns {Boolean}
  104. * @member CKEDITOR.plugins.widgetselection
  105. */
  106. addFillers: function( editable ) {
  107. var editor = editable.editor;
  108. // Whole content should be selected, if not fix the selection manually.
  109. if ( !this.isWholeContentSelected( editable ) && editable.getChildCount() > 0 ) {
  110. var firstChild = editable.getFirst( filterTempElements ),
  111. lastChild = editable.getLast( filterTempElements );
  112. // Check if first element is editable. If not prepend with filler.
  113. if ( firstChild && firstChild.type == CKEDITOR.NODE_ELEMENT && !firstChild.isEditable() ) {
  114. this.startFiller = this.createFiller();
  115. editable.append( this.startFiller, 1 );
  116. }
  117. // Check if last element is editable. If not append filler.
  118. if ( lastChild && lastChild.type == CKEDITOR.NODE_ELEMENT && !lastChild.isEditable() ) {
  119. this.endFiller = this.createFiller( true );
  120. editable.append( this.endFiller, 0 );
  121. }
  122. // Reselect whole content after any filler was added.
  123. if ( this.hasFiller( editable ) ) {
  124. var rangeAll = editor.createRange();
  125. rangeAll.selectNodeContents( editable );
  126. rangeAll.select();
  127. return true;
  128. }
  129. }
  130. return false;
  131. },
  132. /**
  133. * Removes filler elements or updates their references.
  134. *
  135. * It will **not remove** filler elements if the whole content is selected, as it would break the
  136. * selection.
  137. *
  138. * @param {CKEDITOR.editable} editable
  139. * @member CKEDITOR.plugins.widgetselection
  140. */
  141. removeFillers: function( editable ) {
  142. // If startFiller or endFiller exists and not entire content is selected it means the selection
  143. // just changed from selected all. We need to remove fillers and set proper selection/content.
  144. if ( this.hasFiller( editable ) && !this.isWholeContentSelected( editable ) ) {
  145. var startFillerContent = editable.findOne( this.fillerTagName + '[' + this.fillerAttribute + '=start]' ),
  146. endFillerContent = editable.findOne( this.fillerTagName + '[' + this.fillerAttribute + '=end]' );
  147. if ( this.startFiller && startFillerContent && this.startFiller.equals( startFillerContent ) ) {
  148. this.removeFiller( this.startFiller, editable );
  149. } else {
  150. // The start filler is still present but it is a different element than previous one. It means the
  151. // undo recreating entirely selected content was performed. We need to update filler reference.
  152. this.startFiller = startFillerContent;
  153. }
  154. if ( this.endFiller && endFillerContent && this.endFiller.equals( endFillerContent ) ) {
  155. this.removeFiller( this.endFiller, editable );
  156. } else {
  157. // Same as with start filler.
  158. this.endFiller = endFillerContent;
  159. }
  160. }
  161. },
  162. /**
  163. * Removes fillers from the paste data.
  164. *
  165. * @param {String} data
  166. * @returns {String}
  167. * @member CKEDITOR.plugins.widgetselection
  168. * @private
  169. */
  170. cleanPasteData: function( data ) {
  171. if ( data && data.length ) {
  172. data = data
  173. .replace( this.createFillerRegex(), '' )
  174. .replace( this.createFillerRegex( true ), '' );
  175. }
  176. return data;
  177. },
  178. /**
  179. * Checks if the entire content of the given editable is selected.
  180. *
  181. * @param {CKEDITOR.editable} editable
  182. * @returns {Boolean}
  183. * @member CKEDITOR.plugins.widgetselection
  184. * @private
  185. */
  186. isWholeContentSelected: function( editable ) {
  187. var range = editable.editor.getSelection().getRanges()[ 0 ];
  188. if ( range ) {
  189. if ( range && range.collapsed ) {
  190. return false;
  191. } else {
  192. var rangeClone = range.clone();
  193. rangeClone.enlarge( CKEDITOR.ENLARGE_ELEMENT );
  194. return !!( rangeClone && editable && rangeClone.startContainer && rangeClone.endContainer &&
  195. rangeClone.startOffset === 0 && rangeClone.endOffset === editable.getChildCount() &&
  196. rangeClone.startContainer.equals( editable ) && rangeClone.endContainer.equals( editable ) );
  197. }
  198. }
  199. return false;
  200. },
  201. /**
  202. * Checks if there is any filler element in the given editable.
  203. *
  204. * @param {CKEDITOR.editable} editable
  205. * @returns {Boolean}
  206. * @member CKEDITOR.plugins.widgetselection
  207. * @private
  208. */
  209. hasFiller: function( editable ) {
  210. return editable.find( this.fillerTagName + '[' + this.fillerAttribute + ']' ).count() > 0;
  211. },
  212. /**
  213. * Creates a filler element.
  214. *
  215. * @param {Boolean} [onEnd] If filler will be placed on end or beginning of the content.
  216. * @returns {CKEDITOR.dom.element}
  217. * @member CKEDITOR.plugins.widgetselection
  218. * @private
  219. */
  220. createFiller: function( onEnd ) {
  221. var filler = new CKEDITOR.dom.element( this.fillerTagName );
  222. filler.setHtml( this.fillerContent );
  223. filler.setAttribute( this.fillerAttribute, onEnd ? 'end' : 'start' );
  224. filler.setAttribute( 'data-cke-temp', 1 );
  225. filler.setStyles( {
  226. display: 'block',
  227. width: 0,
  228. height: 0,
  229. padding: 0,
  230. border: 0,
  231. margin: 0,
  232. position: 'absolute',
  233. top: 0,
  234. left: '-9999px',
  235. opacity: 0,
  236. overflow: 'hidden'
  237. } );
  238. return filler;
  239. },
  240. /**
  241. * Removes the specific filler element from the given editable. If the filler contains any content (typed or pasted),
  242. * it replaces the current editable content. If not, the caret is placed before the first or after the last editable
  243. * element (depends if the filler was at the beginning or the end).
  244. *
  245. * @param {CKEDITOR.dom.element} filler
  246. * @param {CKEDITOR.editable} editable
  247. * @member CKEDITOR.plugins.widgetselection
  248. * @private
  249. */
  250. removeFiller: function( filler, editable ) {
  251. if ( filler ) {
  252. var editor = editable.editor,
  253. currentRange = editable.editor.getSelection().getRanges()[ 0 ],
  254. currentPath = currentRange.startPath(),
  255. range = editor.createRange(),
  256. insertedHtml,
  257. fillerOnStart,
  258. manuallyHandleCaret;
  259. if ( currentPath.contains( filler ) ) {
  260. insertedHtml = filler.getHtml();
  261. manuallyHandleCaret = true;
  262. }
  263. fillerOnStart = filler.getAttribute( this.fillerAttribute ) == 'start';
  264. filler.remove();
  265. filler = null;
  266. if ( insertedHtml && insertedHtml.length > 0 && insertedHtml != this.fillerContent ) {
  267. editable.insertHtmlIntoRange( insertedHtml, editor.getSelection().getRanges()[ 0 ] );
  268. range.setStartAt( editable.getChild( editable.getChildCount() - 1 ), CKEDITOR.POSITION_BEFORE_END );
  269. editor.getSelection().selectRanges( [ range ] );
  270. } else if ( manuallyHandleCaret ) {
  271. if ( fillerOnStart ) {
  272. range.setStartAt( editable.getFirst().getNext(), CKEDITOR.POSITION_AFTER_START );
  273. } else {
  274. range.setEndAt( editable.getLast().getPrevious(), CKEDITOR.POSITION_BEFORE_END );
  275. }
  276. editable.editor.getSelection().selectRanges( [ range ] );
  277. }
  278. }
  279. },
  280. /**
  281. * Creates a regular expression which will match the filler HTML in the text.
  282. *
  283. * @param {Boolean} [onEnd] Whether a regular expression should be created for the filler at the beginning or
  284. * the end of the content.
  285. * @returns {RegExp}
  286. * @member CKEDITOR.plugins.widgetselection
  287. * @private
  288. */
  289. createFillerRegex: function( onEnd ) {
  290. var matcher = this.createFiller( onEnd ).getOuterHtml()
  291. .replace( /style="[^"]*"/gi, 'style="[^"]*"' )
  292. .replace( />[^<]*</gi, '>[^<]*<' );
  293. return new RegExp( ( !onEnd ? '^' : '' ) + matcher + ( onEnd ? '$' : '' ) );
  294. },
  295. /**
  296. * Adds an integration for the [Select All](http://ckeditor.com/addon/selectall) plugin to the given `editor`.
  297. *
  298. * @private
  299. * @param {CKEDITOR.editor} editor
  300. * @member CKEDITOR.plugins.widgetselection
  301. */
  302. addSelectAllIntegration: function( editor ) {
  303. var widgetselection = this;
  304. editor.editable().attachListener( editor, 'beforeCommandExec', function( evt ) {
  305. var editable = editor.editable();
  306. if ( evt.data.name == 'selectAll' && editable ) {
  307. widgetselection.addFillers( editable );
  308. }
  309. }, null, null, 9999 );
  310. }
  311. };
  312. function filterTempElements( el ) {
  313. return el.getName && !el.hasAttribute( 'data-cke-temp' );
  314. }
  315. } )();