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