]>
Commit | Line | Data |
---|---|---|
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 | |
7 | * @fileOverview The "Notification" plugin.\r | |
8 | *\r | |
9 | */\r | |
10 | \r | |
11 | 'use strict';\r | |
12 | \r | |
13 | CKEDITOR.plugins.add( 'notification', {\r | |
14 | lang: 'az,ca,cs,da,de,de-ch,en,eo,es,es-mx,eu,fr,gl,hr,hu,id,it,ja,km,ko,ku,nb,nl,oc,pl,pt,pt-br,ru,sk,sv,tr,ug,uk,zh,zh-cn', // %REMOVE_LINE_CORE%\r | |
15 | \r | |
16 | init: function( editor ) {\r | |
17 | editor._.notificationArea = new Area( editor );\r | |
18 | \r | |
19 | // Overwrites default `editor.showNotification`.\r | |
20 | editor.showNotification = function( message, type, progressOrDuration ) {\r | |
21 | var progress, duration;\r | |
22 | \r | |
23 | if ( type == 'progress' ) {\r | |
24 | progress = progressOrDuration;\r | |
25 | } else {\r | |
26 | duration = progressOrDuration;\r | |
27 | }\r | |
28 | \r | |
29 | var notification = new CKEDITOR.plugins.notification( editor, {\r | |
30 | message: message,\r | |
31 | type: type,\r | |
32 | progress: progress,\r | |
33 | duration: duration\r | |
34 | } );\r | |
35 | \r | |
36 | notification.show();\r | |
37 | \r | |
38 | return notification;\r | |
39 | };\r | |
40 | \r | |
41 | // Close the last notification on ESC.\r | |
42 | editor.on( 'key', function( evt ) {\r | |
43 | if ( evt.data.keyCode == 27 ) { /* ESC */\r | |
44 | var notifications = editor._.notificationArea.notifications;\r | |
45 | \r | |
46 | if ( !notifications.length ) {\r | |
47 | return;\r | |
48 | }\r | |
49 | \r | |
50 | // As long as this is not a common practice to inform screen-reader users about actions, in this case\r | |
51 | // this is the best solution (unfortunately there is no standard for accessibility for notifications).\r | |
52 | // Notification has an `alert` aria role what means that it does not get a focus nor is needed to be\r | |
53 | // closed (unlike `alertdialog`). However notification will capture ESC key so we need to inform user\r | |
54 | // why it does not do other actions.\r | |
55 | say( editor.lang.notification.closed );\r | |
56 | \r | |
57 | // Hide last.\r | |
58 | notifications[ notifications.length - 1 ].hide();\r | |
59 | \r | |
60 | evt.cancel();\r | |
61 | }\r | |
62 | } );\r | |
63 | \r | |
64 | // Send the message to the screen readers.\r | |
65 | function say( text ) {\r | |
66 | var message = new CKEDITOR.dom.element( 'div' );\r | |
67 | message.setStyles( {\r | |
68 | position: 'fixed',\r | |
69 | 'margin-left': '-9999px'\r | |
70 | } );\r | |
71 | message.setAttributes( {\r | |
72 | 'aria-live': 'assertive',\r | |
73 | 'aria-atomic': 'true'\r | |
74 | } );\r | |
75 | message.setText( text );\r | |
76 | \r | |
77 | CKEDITOR.document.getBody().append( message );\r | |
78 | \r | |
79 | setTimeout( function() {\r | |
80 | message.remove();\r | |
81 | }, 100 );\r | |
82 | }\r | |
83 | }\r | |
84 | } );\r | |
85 | \r | |
86 | /**\r | |
87 | * Notification class. Notifications are used to display short messages to the user. They might be used to show the result of\r | |
88 | * asynchronous actions or information about changes in the editor content. It is recommended to use them instead of\r | |
89 | * alert dialogs. They should **not** be used if a user response is required nor with dialog windows (e.g. in dialog validation).\r | |
90 | *\r | |
91 | * There are four types of notifications available, see the {@link #type} property.\r | |
92 | *\r | |
93 | * Note that the notification constructor only creates a notification instance. To show it, use the {@link #show} method:\r | |
94 | *\r | |
95 | * var notification = new CKEDITOR.plugins.notification( editor, { message: 'Foo' } );\r | |
96 | * notification.show();\r | |
97 | *\r | |
98 | * You can also use the {@link CKEDITOR.editor#showNotification} method:\r | |
99 | *\r | |
100 | * editor.showNotification( 'Foo' );\r | |
101 | *\r | |
102 | * All of the notification actions: ({@link #show}, {@link #update} and {@link #hide}) fire cancelable events\r | |
103 | * on the related {@link CKEDITOR.editor} instance so you can integrate editor notifications with your website notifications.\r | |
104 | *\r | |
105 | * Refer to the [Notifications](http://docs.ckeditor.com/#!/guide/dev_notifications) article for more information about this feature.\r | |
106 | *\r | |
107 | * @since 4.5\r | |
108 | * @class CKEDITOR.plugins.notification\r | |
109 | * @constructor Create a notification object. Call {@link #show} to show the created notification.\r | |
110 | * @param {CKEDITOR.editor} editor The editor instance.\r | |
111 | * @param {Object} options\r | |
112 | * @param {String} options.message The message displayed in the notification.\r | |
113 | * @param {String} [options.type='info'] Notification type, see {@link #type}.\r | |
114 | * @param {Number} [options.progress=0] If the type is `progress` this may be a progress from 0 to 1.\r | |
115 | * @param {Number} [options.duration] How long the notification will be visible, see {@link #duration}.\r | |
116 | */\r | |
117 | function Notification( editor, options ) {\r | |
118 | CKEDITOR.tools.extend( this, options, {\r | |
119 | editor: editor,\r | |
120 | id: 'cke-' + CKEDITOR.tools.getUniqueId(),\r | |
121 | area: editor._.notificationArea\r | |
122 | } );\r | |
123 | \r | |
124 | if ( !options.type ) {\r | |
125 | this.type = 'info';\r | |
126 | }\r | |
127 | \r | |
128 | this.element = this._createElement();\r | |
129 | \r | |
130 | // Don't allow dragging on notification (http://dev.ckeditor.com/ticket/13184).\r | |
131 | editor.plugins.clipboard && CKEDITOR.plugins.clipboard.preventDefaultDropOnElement( this.element );\r | |
132 | }\r | |
133 | \r | |
134 | /**\r | |
135 | * The editor instance.\r | |
136 | *\r | |
137 | * @readonly\r | |
138 | * @property {CKEDITOR.editor} editor\r | |
139 | */\r | |
140 | \r | |
141 | /**\r | |
142 | * Message displayed in the notification.\r | |
143 | *\r | |
144 | * @readonly\r | |
145 | * @property {String} message\r | |
146 | */\r | |
147 | \r | |
148 | /**\r | |
149 | * Notification type. There are four types available:\r | |
150 | *\r | |
151 | * * `info` (default) – Information for the user (e.g. "File is uploading.", "ACF modified content."),\r | |
152 | * * `warning` – Warning or error message (e.g. "This type of file is not supported.",\r | |
153 | * "You cannot paste the script."),\r | |
154 | * * `success` – Information that an operation finished successfully (e.g. "File uploaded.", "Data imported.").\r | |
155 | * * `progress` – Information about the progress of an operation. When the operation is done, the notification\r | |
156 | * type should be changed to `success`.\r | |
157 | *\r | |
158 | * @readonly\r | |
159 | * @property {String} type\r | |
160 | */\r | |
161 | \r | |
162 | /**\r | |
163 | * If the notification {@link #type} is `'progress'`, this is the progress from `0` to `1`.\r | |
164 | *\r | |
165 | * @readonly\r | |
166 | * @property {Number} progress\r | |
167 | */\r | |
168 | \r | |
169 | /**\r | |
170 | * Notification duration. Determines after how many milliseconds the notification should close automatically.\r | |
171 | * `0` means that the notification will not close automatically and that the user needs to close it manually.\r | |
172 | * The default value for `warning` and `progress` notifications is `0`. For `info` and `success` the value can\r | |
173 | * either be set through the {@link CKEDITOR.config#notification_duration} configuration option or equals `5000`\r | |
174 | * if the configuration option is not set.\r | |
175 | *\r | |
176 | * @readonly\r | |
177 | * @property {Number} duration\r | |
178 | */\r | |
179 | \r | |
180 | /**\r | |
181 | * Unique notification ID.\r | |
182 | *\r | |
183 | * @readonly\r | |
184 | * @property {Number} id\r | |
185 | */\r | |
186 | \r | |
187 | /**\r | |
188 | * Notification DOM element. There is one element per notification. It is created when the notification is created,\r | |
189 | * even if it is not shown. If the notification is hidden, the element is detached from the document but not deleted.\r | |
190 | * It will be reused if the notification is shown again.\r | |
191 | *\r | |
192 | * @readonly\r | |
193 | * @property {CKEDITOR.dom.element} element\r | |
194 | */\r | |
195 | \r | |
196 | /**\r | |
197 | * {@link CKEDITOR.plugins.notification.area Notification area} reference.\r | |
198 | *\r | |
199 | * @readonly\r | |
200 | * @property {CKEDITOR.plugins.notification.area} area\r | |
201 | */\r | |
202 | \r | |
203 | Notification.prototype = {\r | |
204 | /**\r | |
205 | * Adds the notification element to the notification area. The notification will be hidden automatically if\r | |
206 | * {@link #duration} is set.\r | |
207 | *\r | |
208 | * Fires the {@link CKEDITOR.editor#notificationShow} event.\r | |
209 | */\r | |
210 | show: function() {\r | |
211 | if ( this.editor.fire( 'notificationShow', { notification: this } ) === false ) {\r | |
212 | return;\r | |
213 | }\r | |
214 | \r | |
215 | this.area.add( this );\r | |
216 | \r | |
217 | this._hideAfterTimeout();\r | |
218 | },\r | |
219 | \r | |
220 | /**\r | |
221 | * Updates the notification object and element.\r | |
222 | *\r | |
223 | * Fires the {@link CKEDITOR.editor#notificationUpdate} event.\r | |
224 | *\r | |
225 | * @param {Object} options\r | |
226 | * @param {String} [options.message] {@link #message}\r | |
227 | * @param {String} [options.type] {@link #type}\r | |
228 | * @param {Number} [options.progress] {@link #progress}\r | |
229 | * @param {Number} [options.duration] {@link #duration}\r | |
230 | * @param {Boolean} [options.important=false] If the update is important, the notification will be shown\r | |
231 | * if it was hidden and read by screen readers.\r | |
232 | */\r | |
233 | update: function( options ) {\r | |
234 | var show = true;\r | |
235 | \r | |
236 | if ( this.editor.fire( 'notificationUpdate', { notification: this, options: options } ) === false ) {\r | |
237 | // The idea of cancelable event is to let user create his own way of displaying notification, so if\r | |
238 | // `notificationUpdate` event will be canceled there will be no interaction with notification area, but on\r | |
239 | // the other hand the logic should work anyway so object will be updated (including `element` property).\r | |
240 | // Note: we can safely update the element's attributes below, because this element is created inside\r | |
241 | // the constructor. If the notificatinShow event was canceled as well, the element is detached from DOM.\r | |
242 | show = false;\r | |
243 | }\r | |
244 | \r | |
245 | var element = this.element,\r | |
246 | messageElement = element.findOne( '.cke_notification_message' ),\r | |
247 | progressElement = element.findOne( '.cke_notification_progress' ),\r | |
248 | type = options.type;\r | |
249 | \r | |
250 | element.removeAttribute( 'role' );\r | |
251 | \r | |
252 | // Change type to progress if `options.progress` is set.\r | |
253 | if ( options.progress && this.type != 'progress' ) {\r | |
254 | type = 'progress';\r | |
255 | }\r | |
256 | \r | |
257 | if ( type ) {\r | |
258 | element.removeClass( this._getClass() );\r | |
259 | element.removeAttribute( 'aria-label' );\r | |
260 | \r | |
261 | this.type = type;\r | |
262 | \r | |
263 | element.addClass( this._getClass() );\r | |
264 | element.setAttribute( 'aria-label', this.type );\r | |
265 | \r | |
266 | if ( this.type == 'progress' && !progressElement ) {\r | |
267 | progressElement = this._createProgressElement();\r | |
268 | progressElement.insertBefore( messageElement );\r | |
269 | } else if ( this.type != 'progress' && progressElement ) {\r | |
270 | progressElement.remove();\r | |
271 | }\r | |
272 | }\r | |
273 | \r | |
274 | if ( options.message !== undefined ) {\r | |
275 | this.message = options.message;\r | |
276 | messageElement.setHtml( this.message );\r | |
277 | }\r | |
278 | \r | |
279 | if ( options.progress !== undefined ) {\r | |
280 | this.progress = options.progress;\r | |
281 | \r | |
282 | if ( progressElement ) {\r | |
283 | progressElement.setStyle( 'width', this._getPercentageProgress() );\r | |
284 | }\r | |
285 | }\r | |
286 | \r | |
287 | if ( show && options.important ) {\r | |
288 | element.setAttribute( 'role', 'alert' );\r | |
289 | \r | |
290 | if ( !this.isVisible() ) {\r | |
291 | this.area.add( this );\r | |
292 | }\r | |
293 | }\r | |
294 | \r | |
295 | // Overwrite even if it is undefined.\r | |
296 | this.duration = options.duration;\r | |
297 | \r | |
298 | this._hideAfterTimeout();\r | |
299 | },\r | |
300 | \r | |
301 | /**\r | |
302 | * Removes the notification element from the notification area.\r | |
303 | *\r | |
304 | * Fires the {@link CKEDITOR.editor#notificationHide} event.\r | |
305 | */\r | |
306 | hide: function() {\r | |
307 | if ( this.editor.fire( 'notificationHide', { notification: this } ) === false ) {\r | |
308 | return;\r | |
309 | }\r | |
310 | \r | |
311 | this.area.remove( this );\r | |
312 | },\r | |
313 | \r | |
314 | /**\r | |
315 | * Returns `true` if the notification is in the notification area.\r | |
316 | *\r | |
317 | * @returns {Boolean} `true` if the notification is in the notification area.\r | |
318 | */\r | |
319 | isVisible: function() {\r | |
320 | return CKEDITOR.tools.indexOf( this.area.notifications, this ) >= 0;\r | |
321 | },\r | |
322 | \r | |
323 | /**\r | |
324 | * Creates the notification DOM element.\r | |
325 | *\r | |
326 | * @private\r | |
327 | * @returns {CKEDITOR.dom.element} Notification DOM element.\r | |
328 | */\r | |
329 | _createElement: function() {\r | |
330 | var notification = this,\r | |
331 | notificationElement, notificationMessageElement, notificationCloseElement,\r | |
332 | close = this.editor.lang.common.close;\r | |
333 | \r | |
334 | notificationElement = new CKEDITOR.dom.element( 'div' );\r | |
335 | notificationElement.addClass( 'cke_notification' );\r | |
336 | notificationElement.addClass( this._getClass() );\r | |
337 | notificationElement.setAttributes( {\r | |
338 | id: this.id,\r | |
339 | role: 'alert',\r | |
340 | 'aria-label': this.type\r | |
341 | } );\r | |
342 | \r | |
343 | if ( this.type == 'progress' )\r | |
344 | notificationElement.append( this._createProgressElement() );\r | |
345 | \r | |
346 | notificationMessageElement = new CKEDITOR.dom.element( 'p' );\r | |
347 | notificationMessageElement.addClass( 'cke_notification_message' );\r | |
348 | notificationMessageElement.setHtml( this.message );\r | |
349 | notificationElement.append( notificationMessageElement );\r | |
350 | \r | |
351 | notificationCloseElement = CKEDITOR.dom.element.createFromHtml(\r | |
352 | '<a class="cke_notification_close" href="javascript:void(0)" title="' + close + '" role="button" tabindex="-1">' +\r | |
353 | '<span class="cke_label">X</span>' +\r | |
354 | '</a>' );\r | |
355 | notificationElement.append( notificationCloseElement );\r | |
356 | \r | |
357 | notificationCloseElement.on( 'click', function() {\r | |
358 | // Focus editor on close (http://dev.ckeditor.com/ticket/12865)\r | |
359 | notification.editor.focus();\r | |
360 | \r | |
361 | notification.hide();\r | |
362 | } );\r | |
363 | \r | |
364 | return notificationElement;\r | |
365 | },\r | |
366 | \r | |
367 | /**\r | |
368 | * Gets the notification CSS class.\r | |
369 | *\r | |
370 | * @private\r | |
371 | * @returns {String} Notification CSS class.\r | |
372 | */\r | |
373 | _getClass: function() {\r | |
374 | return ( this.type == 'progress' ) ?\r | |
375 | 'cke_notification_info' :\r | |
376 | ( 'cke_notification_' + this.type );\r | |
377 | },\r | |
378 | \r | |
379 | /**\r | |
380 | * Creates a progress element for the notification element.\r | |
381 | *\r | |
382 | * @private\r | |
383 | * @returns {CKEDITOR.dom.element} Progress element for the notification element.\r | |
384 | */\r | |
385 | _createProgressElement: function() {\r | |
386 | var element = new CKEDITOR.dom.element( 'span' );\r | |
387 | element.addClass( 'cke_notification_progress' );\r | |
388 | element.setStyle( 'width', this._getPercentageProgress() );\r | |
389 | return element;\r | |
390 | },\r | |
391 | \r | |
392 | /**\r | |
393 | * Gets the progress as a percentage (ex. `0.3` -> `30%`).\r | |
394 | *\r | |
395 | * @private\r | |
396 | * @returns {String} Progress as a percentage.\r | |
397 | */\r | |
398 | _getPercentageProgress: function() {\r | |
399 | return Math.round( ( this.progress || 0 ) * 100 ) + '%';\r | |
400 | },\r | |
401 | \r | |
402 | /**\r | |
403 | * Hides the notification after a timeout.\r | |
404 | *\r | |
405 | * @private\r | |
406 | */\r | |
407 | _hideAfterTimeout: function() {\r | |
408 | var notification = this,\r | |
409 | duration;\r | |
410 | \r | |
411 | if ( this._hideTimeoutId ) {\r | |
412 | clearTimeout( this._hideTimeoutId );\r | |
413 | }\r | |
414 | \r | |
415 | if ( typeof this.duration == 'number' ) {\r | |
416 | duration = this.duration;\r | |
417 | } else if ( this.type == 'info' || this.type == 'success' ) {\r | |
418 | duration = ( typeof this.editor.config.notification_duration == 'number' ) ?\r | |
419 | this.editor.config.notification_duration :\r | |
420 | 5000;\r | |
421 | }\r | |
422 | \r | |
423 | if ( duration ) {\r | |
424 | notification._hideTimeoutId = setTimeout( function() {\r | |
425 | notification.hide();\r | |
426 | }, duration );\r | |
427 | }\r | |
428 | }\r | |
429 | };\r | |
430 | \r | |
431 | /**\r | |
432 | * Notification area is an area where all notifications are put. The area is laid out dynamically.\r | |
433 | * When the first notification is added, the area is shown and all listeners are added.\r | |
434 | * When the last notification is removed, the area is hidden and all listeners are removed.\r | |
435 | *\r | |
436 | * @since 4.5\r | |
437 | * @private\r | |
438 | * @class CKEDITOR.plugins.notification.area\r | |
439 | * @constructor\r | |
440 | * @param {CKEDITOR.editor} editor The editor instance.\r | |
441 | */\r | |
442 | function Area( editor ) {\r | |
443 | var that = this;\r | |
444 | \r | |
445 | this.editor = editor;\r | |
446 | this.notifications = [];\r | |
447 | this.element = this._createElement();\r | |
448 | this._uiBuffer = CKEDITOR.tools.eventsBuffer( 10, this._layout, this );\r | |
449 | this._changeBuffer = CKEDITOR.tools.eventsBuffer( 500, this._layout, this );\r | |
450 | \r | |
451 | editor.on( 'destroy', function() {\r | |
452 | that._removeListeners();\r | |
453 | that.element.remove();\r | |
454 | } );\r | |
455 | }\r | |
456 | \r | |
457 | /**\r | |
458 | * The editor instance.\r | |
459 | *\r | |
460 | * @readonly\r | |
461 | * @property {CKEDITOR.editor} editor\r | |
462 | */\r | |
463 | \r | |
464 | /**\r | |
465 | * The array of added notifications.\r | |
466 | *\r | |
467 | * @readonly\r | |
468 | * @property {Array} notifications\r | |
469 | */\r | |
470 | \r | |
471 | /**\r | |
472 | * Notification area DOM element. This element is created when the area object is created. It will be attached to the document\r | |
473 | * when the first notification is added and removed when the last notification is removed.\r | |
474 | *\r | |
475 | * @readonly\r | |
476 | * @property {CKEDITOR.dom.element} element\r | |
477 | */\r | |
478 | \r | |
479 | /**\r | |
480 | * Notification width. Cached for performance reasons.\r | |
481 | *\r | |
482 | * @private\r | |
483 | * @property {CKEDITOR.dom.element} _notificationWidth\r | |
484 | */\r | |
485 | \r | |
486 | /**\r | |
487 | * Notification margin. Cached for performance reasons.\r | |
488 | *\r | |
489 | * @private\r | |
490 | * @property {CKEDITOR.dom.element} _notificationMargin\r | |
491 | */\r | |
492 | \r | |
493 | /**\r | |
494 | * Event buffer object for UI events to optimize performance.\r | |
495 | *\r | |
496 | * @private\r | |
497 | * @property {Object} _uiBuffer\r | |
498 | */\r | |
499 | \r | |
500 | /**\r | |
501 | * Event buffer object for editor change events to optimize performance.\r | |
502 | *\r | |
503 | * @private\r | |
504 | * @property {Object} _changeBuffer\r | |
505 | */\r | |
506 | \r | |
507 | Area.prototype = {\r | |
508 | /**\r | |
509 | * Adds the notification to the notification area. If it is the first notification, the area will also be attached to\r | |
510 | * the document and listeners will be attached.\r | |
511 | *\r | |
512 | * Note that the proper way to show a notification is to call the {@link CKEDITOR.plugins.notification#show} method.\r | |
513 | *\r | |
514 | * @param {CKEDITOR.plugins.notification} notification Notification to add.\r | |
515 | */\r | |
516 | add: function( notification ) {\r | |
517 | this.notifications.push( notification );\r | |
518 | \r | |
519 | this.element.append( notification.element );\r | |
520 | \r | |
521 | if ( this.element.getChildCount() == 1 ) {\r | |
522 | CKEDITOR.document.getBody().append( this.element );\r | |
523 | this._attachListeners();\r | |
524 | }\r | |
525 | \r | |
526 | this._layout();\r | |
527 | },\r | |
528 | \r | |
529 | /**\r | |
530 | * Removes the notification from the notification area. If it is the last notification, the area will also be\r | |
531 | * detached from the document and listeners will be detached.\r | |
532 | *\r | |
533 | * Note that the proper way to hide a notification is to call the {@link CKEDITOR.plugins.notification#hide} method.\r | |
534 | *\r | |
535 | * @param {CKEDITOR.plugins.notification} notification Notification to remove.\r | |
536 | */\r | |
537 | remove: function( notification ) {\r | |
538 | var i = CKEDITOR.tools.indexOf( this.notifications, notification );\r | |
539 | \r | |
540 | if ( i < 0 ) {\r | |
541 | return;\r | |
542 | }\r | |
543 | \r | |
544 | this.notifications.splice( i, 1 );\r | |
545 | \r | |
546 | notification.element.remove();\r | |
547 | \r | |
548 | if ( !this.element.getChildCount() ) {\r | |
549 | this._removeListeners();\r | |
550 | this.element.remove();\r | |
551 | }\r | |
552 | },\r | |
553 | \r | |
554 | /**\r | |
555 | * Creates the notification area element.\r | |
556 | *\r | |
557 | * @private\r | |
558 | * @returns {CKEDITOR.dom.element} Notification area element.\r | |
559 | */\r | |
560 | _createElement: function() {\r | |
561 | var editor = this.editor,\r | |
562 | config = editor.config,\r | |
563 | notificationArea = new CKEDITOR.dom.element( 'div' );\r | |
564 | \r | |
565 | notificationArea.addClass( 'cke_notifications_area' );\r | |
566 | notificationArea.setAttribute( 'id', 'cke_notifications_area_' + editor.name );\r | |
567 | notificationArea.setStyle( 'z-index', config.baseFloatZIndex - 2 );\r | |
568 | \r | |
569 | return notificationArea;\r | |
570 | },\r | |
571 | \r | |
572 | /**\r | |
573 | * Attaches listeners to the notification area.\r | |
574 | *\r | |
575 | * @private\r | |
576 | */\r | |
577 | _attachListeners: function() {\r | |
578 | var win = CKEDITOR.document.getWindow(),\r | |
579 | editor = this.editor;\r | |
580 | \r | |
581 | win.on( 'scroll', this._uiBuffer.input );\r | |
582 | win.on( 'resize', this._uiBuffer.input );\r | |
583 | editor.on( 'change', this._changeBuffer.input );\r | |
584 | editor.on( 'floatingSpaceLayout', this._layout, this, null, 20 );\r | |
585 | editor.on( 'blur', this._layout, this, null, 20 );\r | |
586 | },\r | |
587 | \r | |
588 | /**\r | |
589 | * Detaches listeners from the notification area.\r | |
590 | *\r | |
591 | * @private\r | |
592 | */\r | |
593 | _removeListeners: function() {\r | |
594 | var win = CKEDITOR.document.getWindow(),\r | |
595 | editor = this.editor;\r | |
596 | \r | |
597 | win.removeListener( 'scroll', this._uiBuffer.input );\r | |
598 | win.removeListener( 'resize', this._uiBuffer.input );\r | |
599 | editor.removeListener( 'change', this._changeBuffer.input );\r | |
600 | editor.removeListener( 'floatingSpaceLayout', this._layout );\r | |
601 | editor.removeListener( 'blur', this._layout );\r | |
602 | },\r | |
603 | \r | |
604 | /**\r | |
605 | * Sets the position of the notification area based on the editor content, toolbar as well as\r | |
606 | * viewport position and dimensions.\r | |
607 | *\r | |
608 | * @private\r | |
609 | */\r | |
610 | _layout: function() {\r | |
611 | var area = this.element,\r | |
612 | editor = this.editor,\r | |
613 | contentsRect = editor.ui.contentsElement.getClientRect(),\r | |
614 | contentsPos = editor.ui.contentsElement.getDocumentPosition(),\r | |
615 | top,\r | |
616 | topRect,\r | |
617 | areaRect = area.getClientRect(),\r | |
618 | notification,\r | |
619 | notificationWidth = this._notificationWidth,\r | |
620 | notificationMargin = this._notificationMargin,\r | |
621 | win = CKEDITOR.document.getWindow(),\r | |
622 | scrollPos = win.getScrollPosition(),\r | |
623 | viewRect = win.getViewPaneSize(),\r | |
624 | body = CKEDITOR.document.getBody(),\r | |
625 | bodyPos = body.getDocumentPosition(),\r | |
626 | cssLength = CKEDITOR.tools.cssLength;\r | |
627 | \r | |
628 | // Cache for optimization\r | |
629 | if ( !notificationWidth || !notificationMargin ) {\r | |
630 | notification = this.element.getChild( 0 );\r | |
631 | notificationWidth = this._notificationWidth = notification.getClientRect().width;\r | |
632 | notificationMargin = this._notificationMargin =\r | |
633 | parseInt( notification.getComputedStyle( 'margin-left' ), 10 ) +\r | |
634 | parseInt( notification.getComputedStyle( 'margin-right' ), 10 );\r | |
635 | }\r | |
636 | \r | |
637 | // Check if toolbar exist and if so, then assign values to it (#491).\r | |
638 | if ( editor.toolbar ) {\r | |
639 | top = editor.ui.space( 'top' );\r | |
640 | topRect = top.getClientRect();\r | |
641 | }\r | |
642 | \r | |
643 | \r | |
644 | // --------------------------------------- Horizontal layout ----------------------------------------\r | |
645 | \r | |
646 | // +---Viewport-------------------------------+ +---Viewport-------------------------------+\r | |
647 | // | | | |\r | |
648 | // | +---Toolbar----------------------------+ | | +---Content----------------------------+ |\r | |
649 | // | | | | | | | |\r | |
650 | // | +---Content----------------------------+ | | | | |\r | |
651 | // | | | | | +---Toolbar----------------------+ | |\r | |
652 | // | | +------Notification------+ | | | | | | |\r | |
653 | // | | | | OR | +--------------------------------+ | |\r | |
654 | // | | | | | | | |\r | |
655 | // | | | | | | +------Notification------+ | |\r | |
656 | // | | | | | | | |\r | |
657 | // | | | | | | | |\r | |
658 | // | +--------------------------------------+ | | +--------------------------------------+ |\r | |
659 | // +------------------------------------------+ +------------------------------------------+\r | |
660 | if ( top && top.isVisible() &&\r | |
661 | topRect.bottom > contentsRect.top &&\r | |
662 | topRect.bottom < contentsRect.bottom - areaRect.height ) {\r | |
663 | setBelowToolbar();\r | |
664 | \r | |
665 | // +---Viewport-------------------------------+\r | |
666 | // | |\r | |
667 | // | +---Content----------------------------+ |\r | |
668 | // | | | |\r | |
669 | // | | +------Notification------+ | |\r | |
670 | // | | | |\r | |
671 | // | | | |\r | |
672 | // | | | |\r | |
673 | // | +--------------------------------------+ |\r | |
674 | // | |\r | |
675 | // +------------------------------------------+\r | |
676 | } else if ( contentsRect.top > 0 ) {\r | |
677 | setTopStandard();\r | |
678 | \r | |
679 | // +---Content----------------------------+\r | |
680 | // | |\r | |
681 | // +---Viewport-------------------------------+\r | |
682 | // | | | |\r | |
683 | // | | +------Notification------+ | |\r | |
684 | // | | | |\r | |
685 | // | | | |\r | |
686 | // | | | |\r | |
687 | // | +--------------------------------------+ |\r | |
688 | // | |\r | |
689 | // +------------------------------------------+\r | |
690 | } else if ( contentsPos.y + contentsRect.height - areaRect.height > scrollPos.y ) {\r | |
691 | setTopFixed();\r | |
692 | \r | |
693 | // +---Content----------------------------+ +---Content----------------------------+\r | |
694 | // | | | |\r | |
695 | // | | | |\r | |
696 | // | | | +------Notification------+ |\r | |
697 | // | | | |\r | |
698 | // | | OR +--------------------------------------+\r | |
699 | // +---Viewport-------------------------------+\r | |
700 | // | | +------Notification------+ | | +---Viewport-------------------------------+\r | |
701 | // | | | | | |\r | |
702 | // | +--------------------------------------+ | | |\r | |
703 | // | | | |\r | |
704 | // +------------------------------------------+ +------------------------------------------+\r | |
705 | } else {\r | |
706 | setBottom();\r | |
707 | }\r | |
708 | \r | |
709 | function setTopStandard() {\r | |
710 | area.setStyles( {\r | |
711 | position: 'absolute',\r | |
712 | top: cssLength( contentsPos.y )\r | |
713 | } );\r | |
714 | }\r | |
715 | \r | |
716 | function setBelowToolbar() {\r | |
717 | area.setStyles( {\r | |
718 | position: 'fixed',\r | |
719 | top: cssLength( topRect.bottom )\r | |
720 | } );\r | |
721 | }\r | |
722 | \r | |
723 | function setTopFixed() {\r | |
724 | area.setStyles( {\r | |
725 | position: 'fixed',\r | |
726 | top: 0\r | |
727 | } );\r | |
728 | }\r | |
729 | \r | |
730 | function setBottom() {\r | |
731 | area.setStyles( {\r | |
732 | position: 'absolute',\r | |
733 | top: cssLength( contentsPos.y + contentsRect.height - areaRect.height )\r | |
734 | } );\r | |
735 | }\r | |
736 | \r | |
737 | // ---------------------------------------- Vertical layout -----------------------------------------\r | |
738 | \r | |
739 | var leftBase = area.getStyle( 'position' ) == 'fixed' ?\r | |
740 | contentsRect.left :\r | |
741 | body.getComputedStyle( 'position' ) != 'static' ? contentsPos.x - bodyPos.x : contentsPos.x;\r | |
742 | \r | |
743 | // Content is narrower than notification\r | |
744 | if ( contentsRect.width < notificationWidth + notificationMargin ) {\r | |
745 | \r | |
746 | // +---Viewport-------------------------------+\r | |
747 | // | |\r | |
748 | // | +---Content------------+ |\r | |
749 | // | | | |\r | |
750 | // | +------Notification------+ | |\r | |
751 | // | | | |\r | |
752 | // | +----------------------+ |\r | |
753 | // | |\r | |
754 | // +------------------------------------------+\r | |
755 | if ( contentsPos.x + notificationWidth + notificationMargin > scrollPos.x + viewRect.width ) {\r | |
756 | setRight();\r | |
757 | \r | |
758 | // +---Viewport-------------------------------+ +---Viewport--------------------------+\r | |
759 | // | | | |\r | |
760 | // | +---Content------------+ | +---Content------------+ |\r | |
761 | // | | | | | | | |\r | |
762 | // | | +------Notification------+ | OR | +------Notification------+ |\r | |
763 | // | | | | | | | |\r | |
764 | // | +----------------------+ | +----------------------+ |\r | |
765 | // | | | |\r | |
766 | // +------------------------------------------+ +-------------------------------------+\r | |
767 | } else {\r | |
768 | setLeft();\r | |
769 | }\r | |
770 | \r | |
771 | // Content is wider than notification.\r | |
772 | } else {\r | |
773 | \r | |
774 | // +--+Viewport+------------------------+\r | |
775 | // | |\r | |
776 | // | +---Content-----------------------------------------+\r | |
777 | // | | | |\r | |
778 | // | | +-----+Notification+-----+ |\r | |
779 | // | | | |\r | |
780 | // | | | |\r | |
781 | // | | | |\r | |
782 | // | +---------------------------------------------------+\r | |
783 | // | |\r | |
784 | // +------------------------------------+\r | |
785 | if ( contentsPos.x + notificationWidth + notificationMargin > scrollPos.x + viewRect.width ) {\r | |
786 | setLeft();\r | |
787 | \r | |
788 | // +---Viewport-------------------------+\r | |
789 | // | |\r | |
790 | // | +---Content----------------------------------------------+\r | |
791 | // | | | |\r | |
792 | // | | +------Notification------+ | |\r | |
793 | // | | | |\r | |
794 | // | | | |\r | |
795 | // | +--------------------------------------------------------+\r | |
796 | // | |\r | |
797 | // +------------------------------------+\r | |
798 | } else if ( contentsPos.x + contentsRect.width / 2 +\r | |
799 | notificationWidth / 2 + notificationMargin > scrollPos.x + viewRect.width ) {\r | |
800 | setRightFixed();\r | |
801 | \r | |
802 | // +---Viewport-------------------------+\r | |
803 | // | |\r | |
804 | // +---Content----------------------------+ |\r | |
805 | // | | | |\r | |
806 | // | +------Notification------+ | |\r | |
807 | // | | | |\r | |
808 | // | | | |\r | |
809 | // +--------------------------------------+ |\r | |
810 | // | |\r | |
811 | // +------------------------------------+\r | |
812 | } else if ( contentsRect.left + contentsRect.width - notificationWidth - notificationMargin < 0 ) {\r | |
813 | setRight();\r | |
814 | \r | |
815 | // +---Viewport-------------------------+\r | |
816 | // | |\r | |
817 | // +---Content---------------------------------------------+ |\r | |
818 | // | | | |\r | |
819 | // | | +------Notification------+ | |\r | |
820 | // | | | |\r | |
821 | // | | | |\r | |
822 | // +-------------------------------------------------------+ |\r | |
823 | // | |\r | |
824 | // +------------------------------------+\r | |
825 | } else if ( contentsRect.left + contentsRect.width / 2 - notificationWidth / 2 < 0 ) {\r | |
826 | setLeftFixed();\r | |
827 | \r | |
828 | // +---Viewport-------------------------+\r | |
829 | // | |\r | |
830 | // | +---Content----------------------+ |\r | |
831 | // | | | |\r | |
832 | // | | +-----Notification-----+ | |\r | |
833 | // | | | |\r | |
834 | // | | | |\r | |
835 | // | +--------------------------------+ |\r | |
836 | // | |\r | |
837 | // +------------------------------------+\r | |
838 | } else {\r | |
839 | setCenter();\r | |
840 | }\r | |
841 | }\r | |
842 | \r | |
843 | function setLeft() {\r | |
844 | area.setStyle( 'left', cssLength( leftBase ) );\r | |
845 | }\r | |
846 | \r | |
847 | function setLeftFixed() {\r | |
848 | area.setStyle( 'left', cssLength( leftBase - contentsPos.x + scrollPos.x ) );\r | |
849 | }\r | |
850 | \r | |
851 | function setCenter() {\r | |
852 | area.setStyle( 'left', cssLength( leftBase + contentsRect.width / 2 - notificationWidth / 2 - notificationMargin / 2 ) );\r | |
853 | }\r | |
854 | \r | |
855 | function setRight() {\r | |
856 | area.setStyle( 'left', cssLength( leftBase + contentsRect.width - notificationWidth - notificationMargin ) );\r | |
857 | }\r | |
858 | \r | |
859 | function setRightFixed() {\r | |
860 | area.setStyle( 'left', cssLength( leftBase - contentsPos.x + scrollPos.x + viewRect.width -\r | |
861 | notificationWidth - notificationMargin ) );\r | |
862 | }\r | |
863 | }\r | |
864 | };\r | |
865 | \r | |
866 | CKEDITOR.plugins.notification = Notification;\r | |
867 | \r | |
868 | /**\r | |
869 | * After how many milliseconds the notification of the `info` and `success`\r | |
870 | * {@link CKEDITOR.plugins.notification#type type} should close automatically.\r | |
871 | * `0` means that notifications will not close automatically.\r | |
872 | * Note that `warning` and `progress` notifications will never close automatically.\r | |
873 | *\r | |
874 | * Refer to the [Notifications](http://docs.ckeditor.com/#!/guide/dev_notifications) article\r | |
875 | * for more information about this feature.\r | |
876 | *\r | |
877 | * @since 4.5\r | |
878 | * @cfg {Number} [notification_duration=5000]\r | |
879 | * @member CKEDITOR.config\r | |
880 | */\r | |
881 | \r | |
882 | /**\r | |
883 | * Event fired when the {@link CKEDITOR.plugins.notification#show} method is called, before the\r | |
884 | * notification is shown. If this event is canceled, the notification will not be shown.\r | |
885 | *\r | |
886 | * Using this event allows you to fully customize how a notification will be shown. It may be used to integrate\r | |
887 | * the CKEditor notification system with your web page notifications.\r | |
888 | *\r | |
889 | * @since 4.5\r | |
890 | * @event notificationShow\r | |
891 | * @member CKEDITOR.editor\r | |
892 | * @param data\r | |
893 | * @param {CKEDITOR.plugins.notification} data.notification Notification which will be shown.\r | |
894 | * @param {CKEDITOR.editor} editor The editor instance.\r | |
895 | */\r | |
896 | \r | |
897 | /**\r | |
898 | * Event fired when the {@link CKEDITOR.plugins.notification#update} method is called, before the\r | |
899 | * notification is updated. If this event is canceled, the notification will not be shown even if the update was important,\r | |
900 | * but the object will be updated anyway. Note that canceling this event does not prevent updating {@link #element}\r | |
901 | * attributes, but if {@link #notificationShow} was canceled as well, this element is detached from the DOM.\r | |
902 | *\r | |
903 | * Using this event allows you to fully customize how a notification will be updated. It may be used to integrate\r | |
904 | * the CKEditor notification system with your web page notifications.\r | |
905 | *\r | |
906 | * @since 4.5\r | |
907 | * @event notificationUpdate\r | |
908 | * @member CKEDITOR.editor\r | |
909 | * @param data\r | |
910 | * @param {CKEDITOR.plugins.notification} data.notification Notification which will be updated.\r | |
911 | * Note that it contains the data that has not been updated yet.\r | |
912 | * @param {Object} data.options Update options, see {@link CKEDITOR.plugins.notification#update}.\r | |
913 | * @param {CKEDITOR.editor} editor The editor instance.\r | |
914 | */\r | |
915 | \r | |
916 | /**\r | |
917 | * Event fired when the {@link CKEDITOR.plugins.notification#hide} method is called, before the\r | |
918 | * notification is hidden. If this event is canceled, the notification will not be hidden.\r | |
919 | *\r | |
920 | * Using this event allows you to fully customize how a notification will be hidden. It may be used to integrate\r | |
921 | * the CKEditor notification system with your web page notifications.\r | |
922 | *\r | |
923 | * @since 4.5\r | |
924 | * @event notificationHide\r | |
925 | * @member CKEDITOR.editor\r | |
926 | * @param data\r | |
927 | * @param {CKEDITOR.plugins.notification} data.notification Notification which will be hidden.\r | |
928 | * @param {CKEDITOR.editor} editor The editor instance.\r | |
929 | */\r |