aboutsummaryrefslogtreecommitdiff
path: root/sources/plugins/widget
diff options
context:
space:
mode:
Diffstat (limited to 'sources/plugins/widget')
-rw-r--r--sources/plugins/widget/dev/assets/contents.css23
-rw-r--r--sources/plugins/widget/dev/assets/sample.jpgbin0 -> 17932 bytes
-rw-r--r--sources/plugins/widget/dev/assets/simplebox/contents.css36
-rw-r--r--sources/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js51
-rw-r--r--sources/plugins/widget/dev/assets/simplebox/icons/simplebox.pngbin0 -> 286 bytes
-rw-r--r--sources/plugins/widget/dev/assets/simplebox/plugin.js114
-rw-r--r--sources/plugins/widget/dev/console.js131
-rw-r--r--sources/plugins/widget/dev/nestedwidgets.html134
-rw-r--r--sources/plugins/widget/dev/widgetstyles.html144
-rw-r--r--sources/plugins/widget/images/handle.pngbin0 -> 220 bytes
-rw-r--r--sources/plugins/widget/lang/af.js8
-rw-r--r--sources/plugins/widget/lang/ar.js8
-rw-r--r--sources/plugins/widget/lang/az.js8
-rw-r--r--sources/plugins/widget/lang/bg.js8
-rw-r--r--sources/plugins/widget/lang/ca.js8
-rw-r--r--sources/plugins/widget/lang/cs.js8
-rw-r--r--sources/plugins/widget/lang/cy.js8
-rw-r--r--sources/plugins/widget/lang/da.js8
-rw-r--r--sources/plugins/widget/lang/de-ch.js8
-rw-r--r--sources/plugins/widget/lang/de.js8
-rw-r--r--sources/plugins/widget/lang/el.js8
-rw-r--r--sources/plugins/widget/lang/en-gb.js8
-rw-r--r--sources/plugins/widget/lang/en.js8
-rw-r--r--sources/plugins/widget/lang/eo.js8
-rw-r--r--sources/plugins/widget/lang/es-mx.js8
-rw-r--r--sources/plugins/widget/lang/es.js8
-rw-r--r--sources/plugins/widget/lang/eu.js8
-rw-r--r--sources/plugins/widget/lang/fa.js8
-rw-r--r--sources/plugins/widget/lang/fi.js8
-rw-r--r--sources/plugins/widget/lang/fr.js8
-rw-r--r--sources/plugins/widget/lang/gl.js8
-rw-r--r--sources/plugins/widget/lang/he.js8
-rw-r--r--sources/plugins/widget/lang/hr.js8
-rw-r--r--sources/plugins/widget/lang/hu.js8
-rw-r--r--sources/plugins/widget/lang/id.js8
-rw-r--r--sources/plugins/widget/lang/it.js8
-rw-r--r--sources/plugins/widget/lang/ja.js8
-rw-r--r--sources/plugins/widget/lang/km.js8
-rw-r--r--sources/plugins/widget/lang/ko.js8
-rw-r--r--sources/plugins/widget/lang/ku.js8
-rw-r--r--sources/plugins/widget/lang/lv.js8
-rw-r--r--sources/plugins/widget/lang/nb.js8
-rw-r--r--sources/plugins/widget/lang/nl.js8
-rw-r--r--sources/plugins/widget/lang/no.js8
-rw-r--r--sources/plugins/widget/lang/oc.js8
-rw-r--r--sources/plugins/widget/lang/pl.js8
-rw-r--r--sources/plugins/widget/lang/pt-br.js8
-rw-r--r--sources/plugins/widget/lang/pt.js8
-rw-r--r--sources/plugins/widget/lang/ru.js8
-rw-r--r--sources/plugins/widget/lang/sk.js8
-rw-r--r--sources/plugins/widget/lang/sl.js8
-rw-r--r--sources/plugins/widget/lang/sq.js8
-rw-r--r--sources/plugins/widget/lang/sv.js8
-rw-r--r--sources/plugins/widget/lang/tr.js8
-rw-r--r--sources/plugins/widget/lang/tt.js8
-rw-r--r--sources/plugins/widget/lang/ug.js8
-rw-r--r--sources/plugins/widget/lang/uk.js8
-rw-r--r--sources/plugins/widget/lang/vi.js8
-rw-r--r--sources/plugins/widget/lang/zh-cn.js8
-rw-r--r--sources/plugins/widget/lang/zh.js8
-rw-r--r--sources/plugins/widget/plugin.js4147
61 files changed, 5180 insertions, 0 deletions
diff --git a/sources/plugins/widget/dev/assets/contents.css b/sources/plugins/widget/dev/assets/contents.css
new file mode 100644
index 0000000..2cff316
--- /dev/null
+++ b/sources/plugins/widget/dev/assets/contents.css
@@ -0,0 +1,23 @@
1.mediumBorder {
2 border-width: 2px;
3}
4.thickBorder {
5 border-width: 5px;
6}
7img.thickBorder, img.mediumBorder {
8 border-style: solid;
9 border-color: #CCC;
10}
11.important.soMuch {
12 margin: 25px;
13 padding: 25px;
14 background: red;
15 border: none;
16}
17
18span.redMarker {
19 background-color: red;
20}
21.invisible {
22 opacity: 0.1;
23}
diff --git a/sources/plugins/widget/dev/assets/sample.jpg b/sources/plugins/widget/dev/assets/sample.jpg
new file mode 100644
index 0000000..a4a77fa
--- /dev/null
+++ b/sources/plugins/widget/dev/assets/sample.jpg
Binary files differ
diff --git a/sources/plugins/widget/dev/assets/simplebox/contents.css b/sources/plugins/widget/dev/assets/simplebox/contents.css
new file mode 100644
index 0000000..ddf3675
--- /dev/null
+++ b/sources/plugins/widget/dev/assets/simplebox/contents.css
@@ -0,0 +1,36 @@
1.simplebox {
2 padding: 8px;
3 margin: 10px;
4 background: #eee;
5 border-radius: 8px;
6 border: 1px solid #ddd;
7 box-shadow: 0 1px 1px #fff inset, 0 -1px 0px #ccc inset;
8}
9.simplebox-title, .simplebox-content {
10 box-shadow: 0 1px 1px #ddd inset;
11 border: 1px solid #cccccc;
12 border-radius: 5px;
13 background: #fff;
14}
15.simplebox-title {
16 margin: 0 0 8px;
17 padding: 5px 8px;
18}
19.simplebox-content {
20 padding: 0 8px;
21}
22.simplebox-content::after {
23 content: '';
24 display: block;
25 clear: both;
26}
27.simplebox.align-right {
28 float: right;
29}
30.simplebox.align-left {
31 float: left;
32}
33.simplebox.align-center {
34 margin-left: auto;
35 margin-right: auto;
36}
diff --git a/sources/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js b/sources/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js
new file mode 100644
index 0000000..45a150c
--- /dev/null
+++ b/sources/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js
@@ -0,0 +1,51 @@
1// Note: This automatic widget to dialog window binding (the fact that every field is set up from the widget
2// and is committed to the widget) is only possible when the dialog is opened by the Widgets System
3// (i.e. the widgetDef.dialog property is set).
4// When you are opening the dialog window by yourself, you need to take care of this by yourself too.
5
6CKEDITOR.dialog.add( 'simplebox', function( editor ) {
7 return {
8 title: 'Edit Simple Box',
9 minWidth: 200,
10 minHeight: 100,
11 contents: [
12 {
13 id: 'info',
14 elements: [
15 {
16 id: 'align',
17 type: 'select',
18 label: 'Align',
19 items: [
20 [ editor.lang.common.notSet, '' ],
21 [ editor.lang.common.alignLeft, 'left' ],
22 [ editor.lang.common.alignRight, 'right' ],
23 [ editor.lang.common.alignCenter, 'center' ]
24 ],
25 // When setting up this field, set its value to the "align" value from widget data.
26 // Note: Align values used in the widget need to be the same as those defined in the "items" array above.
27 setup: function( widget ) {
28 this.setValue( widget.data.align );
29 },
30 // When committing (saving) this field, set its value to the widget data.
31 commit: function( widget ) {
32 widget.setData( 'align', this.getValue() );
33 }
34 },
35 {
36 id: 'width',
37 type: 'text',
38 label: 'Width',
39 width: '50px',
40 setup: function( widget ) {
41 this.setValue( widget.data.width );
42 },
43 commit: function( widget ) {
44 widget.setData( 'width', this.getValue() );
45 }
46 }
47 ]
48 }
49 ]
50 };
51} );
diff --git a/sources/plugins/widget/dev/assets/simplebox/icons/simplebox.png b/sources/plugins/widget/dev/assets/simplebox/icons/simplebox.png
new file mode 100644
index 0000000..6a5e313
--- /dev/null
+++ b/sources/plugins/widget/dev/assets/simplebox/icons/simplebox.png
Binary files differ
diff --git a/sources/plugins/widget/dev/assets/simplebox/plugin.js b/sources/plugins/widget/dev/assets/simplebox/plugin.js
new file mode 100644
index 0000000..4c22d0d
--- /dev/null
+++ b/sources/plugins/widget/dev/assets/simplebox/plugin.js
@@ -0,0 +1,114 @@
1'use strict';
2
3// Register the plugin within the editor.
4CKEDITOR.plugins.add( 'simplebox', {
5 // This plugin requires the Widgets System defined in the 'widget' plugin.
6 requires: 'widget,dialog',
7
8 // Register the icon used for the toolbar button. It must be the same
9 // as the name of the widget.
10 icons: 'simplebox',
11
12 // The plugin initialization logic goes inside this method.
13 init: function( editor ) {
14 // Register the editing dialog.
15 CKEDITOR.dialog.add( 'simplebox', this.path + 'dialogs/simplebox.js' );
16
17 // Register the simplebox widget.
18 editor.widgets.add( 'simplebox', {
19 // Allow all HTML elements, classes, and styles that this widget requires.
20 // Read more about the Advanced Content Filter here:
21 // * http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter
22 // * http://docs.ckeditor.com/#!/guide/plugin_sdk_integration_with_acf
23 allowedContent:
24 'div(!simplebox,align-left,align-right,align-center){width};' +
25 'div(!simplebox-content); h2(!simplebox-title)',
26
27 // Minimum HTML which is required by this widget to work.
28 requiredContent: 'div(simplebox)',
29
30 // Define two nested editable areas.
31 editables: {
32 title: {
33 // Define CSS selector used for finding the element inside widget element.
34 selector: '.simplebox-title',
35 // Define content allowed in this nested editable. Its content will be
36 // filtered accordingly and the toolbar will be adjusted when this editable
37 // is focused.
38 allowedContent: 'br strong em'
39 },
40 content: {
41 selector: '.simplebox-content'
42 }
43 },
44
45 // Define the template of a new Simple Box widget.
46 // The template will be used when creating new instances of the Simple Box widget.
47 template:
48 '<div class="simplebox">' +
49 '<h2 class="simplebox-title">Title</h2>' +
50 '<div class="simplebox-content"><p>Content...</p></div>' +
51 '</div>',
52
53 // Define the label for a widget toolbar button which will be automatically
54 // created by the Widgets System. This button will insert a new widget instance
55 // created from the template defined above, or will edit selected widget
56 // (see second part of this tutorial to learn about editing widgets).
57 //
58 // Note: In order to be able to translate your widget you should use the
59 // editor.lang.simplebox.* property. A string was used directly here to simplify this tutorial.
60 button: 'Create a simple box',
61
62 // Set the widget dialog window name. This enables the automatic widget-dialog binding.
63 // This dialog window will be opened when creating a new widget or editing an existing one.
64 dialog: 'simplebox',
65
66 // Check the elements that need to be converted to widgets.
67 //
68 // Note: The "element" argument is an instance of http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.element
69 // so it is not a real DOM element yet. This is caused by the fact that upcasting is performed
70 // during data processing which is done on DOM represented by JavaScript objects.
71 upcast: function( element ) {
72 // Return "true" (that element needs to converted to a Simple Box widget)
73 // for all <div> elements with a "simplebox" class.
74 return element.name == 'div' && element.hasClass( 'simplebox' );
75 },
76
77 // When a widget is being initialized, we need to read the data ("align" and "width")
78 // from DOM and set it by using the widget.setData() method.
79 // More code which needs to be executed when DOM is available may go here.
80 init: function() {
81 var width = this.element.getStyle( 'width' );
82 if ( width )
83 this.setData( 'width', width );
84
85 if ( this.element.hasClass( 'align-left' ) )
86 this.setData( 'align', 'left' );
87 if ( this.element.hasClass( 'align-right' ) )
88 this.setData( 'align', 'right' );
89 if ( this.element.hasClass( 'align-center' ) )
90 this.setData( 'align', 'center' );
91 },
92
93 // Listen on the widget#data event which is fired every time the widget data changes
94 // and updates the widget's view.
95 // Data may be changed by using the widget.setData() method, which we use in the
96 // Simple Box dialog window.
97 data: function() {
98 // Check whether "width" widget data is set and remove or set "width" CSS style.
99 // The style is set on widget main element (div.simplebox).
100 if ( !this.data.width )
101 this.element.removeStyle( 'width' );
102 else
103 this.element.setStyle( 'width', this.data.width );
104
105 // Brutally remove all align classes and set a new one if "align" widget data is set.
106 this.element.removeClass( 'align-left' );
107 this.element.removeClass( 'align-right' );
108 this.element.removeClass( 'align-center' );
109 if ( this.data.align )
110 this.element.addClass( 'align-' + this.data.align );
111 }
112 } );
113 }
114} );
diff --git a/sources/plugins/widget/dev/console.js b/sources/plugins/widget/dev/console.js
new file mode 100644
index 0000000..78db0d0
--- /dev/null
+++ b/sources/plugins/widget/dev/console.js
@@ -0,0 +1,131 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/* global CKCONSOLE */
7
8'use strict';
9
10( function() {
11
12 CKCONSOLE.add( 'widget', {
13 panels: [
14 {
15 type: 'box',
16 content: '<ul class="ckconsole_list ckconsole_value" data-value="instances"></ul>',
17
18 refresh: function( editor ) {
19 var instances = obj2Array( editor.widgets.instances );
20
21 return {
22 header: 'Instances (' + instances.length + ')',
23 instances: generateInstancesList( instances )
24 };
25 },
26
27 refreshOn: function( editor, refresh ) {
28 editor.widgets.on( 'instanceCreated', function( evt ) {
29 refresh();
30
31 evt.data.on( 'data', refresh );
32 } );
33
34 editor.widgets.on( 'instanceDestroyed', refresh );
35 }
36 },
37
38 {
39 type: 'box',
40 content:
41 '<ul class="ckconsole_list">' +
42 '<li>focused: <span class="ckconsole_value" data-value="focused"></span></li>' +
43 '<li>selected: <span class="ckconsole_value" data-value="selected"></span></li>' +
44 '</ul>',
45
46 refresh: function( editor ) {
47 var focused = editor.widgets.focused,
48 selected = editor.widgets.selected,
49 selectedIds = [];
50
51 for ( var i = 0; i < selected.length; ++i )
52 selectedIds.push( selected[ i ].id );
53
54 return {
55 header: 'Focus &amp; selection',
56 focused: focused ? 'id: ' + focused.id : '-',
57 selected: selectedIds.length ? 'id: ' + selectedIds.join( ', id: ' ) : '-'
58 };
59 },
60
61 refreshOn: function( editor, refresh ) {
62 editor.on( 'selectionCheck', refresh, null, null, 999 );
63 }
64 },
65
66 {
67 type: 'log',
68
69 on: function( editor, log, logFn ) {
70 // Add all listeners with high priorities to log
71 // messages in the correct order when one event depends on another.
72 // E.g. selectionChange triggers widget selection - if this listener
73 // for selectionChange will be executed later than that one, then order
74 // will be incorrect.
75
76 editor.on( 'selectionChange', function( evt ) {
77 var msg = 'selection change',
78 sel = evt.data.selection,
79 el = sel.getSelectedElement(),
80 widget;
81
82 if ( el && ( widget = editor.widgets.getByElement( el, true ) ) )
83 msg += ' (id: ' + widget.id + ')';
84
85 log( msg );
86 }, null, null, 1 );
87
88 editor.widgets.on( 'instanceDestroyed', function( evt ) {
89 log( 'instance destroyed (id: ' + evt.data.id + ')' );
90 }, null, null, 1 );
91
92 editor.widgets.on( 'instanceCreated', function( evt ) {
93 log( 'instance created (id: ' + evt.data.id + ')' );
94 }, null, null, 1 );
95
96 editor.widgets.on( 'widgetFocused', function( evt ) {
97 log( 'widget focused (id: ' + evt.data.widget.id + ')' );
98 }, null, null, 1 );
99
100 editor.widgets.on( 'widgetBlurred', function( evt ) {
101 log( 'widget blurred (id: ' + evt.data.widget.id + ')' );
102 }, null, null, 1 );
103
104 editor.widgets.on( 'checkWidgets', logFn( 'checking widgets' ), null, null, 1 );
105 editor.widgets.on( 'checkSelection', logFn( 'checking selection' ), null, null, 1 );
106 }
107 }
108 ]
109 } );
110
111 function generateInstancesList( instances ) {
112 var html = '',
113 instance;
114
115 for ( var i = 0; i < instances.length; ++i ) {
116 instance = instances[ i ];
117 html += itemTpl.output( { id: instance.id, name: instance.name, data: JSON.stringify( instance.data ) } );
118 }
119 return html;
120 }
121
122 function obj2Array( obj ) {
123 var arr = [];
124 for ( var id in obj )
125 arr.push( obj[ id ] );
126
127 return arr;
128 }
129
130 var itemTpl = new CKEDITOR.template( '<li>id: <code>{id}</code>, name: <code>{name}</code>, data: <code>{data}</code></li>' );
131} )();
diff --git a/sources/plugins/widget/dev/nestedwidgets.html b/sources/plugins/widget/dev/nestedwidgets.html
new file mode 100644
index 0000000..0686d2c
--- /dev/null
+++ b/sources/plugins/widget/dev/nestedwidgets.html
@@ -0,0 +1,134 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>Nested widgets &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <script src="../../../dev/console/console.js"></script>
12 <script src="../../../dev/console/focusconsole.js"></script>
13 <script src="console.js"></script>
14 <link rel="stylesheet" href="../../../samples/old/sample.css">
15 <link rel="stylesheet" href="../../../contents.css">
16 <link rel="stylesheet" href="assets/simplebox/contents.css">
17</head>
18<body>
19 <h1 class="samples">Nested widgets</h1>
20
21 <h2>Classic (iframe-based) Sample</h2>
22 <textarea cols="80" id="editor1" name="editor1" rows="10">
23 <h1>Simple Box Sample</h1>
24
25 <div class="simplebox align-right" style="width: 60%">
26 <h2 class="simplebox-title">Title</h2>
27 <div class="simplebox-content">
28 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
29
30 <figure class="image" style="float: right">
31 <img alt="The Eagle" src="assets/sample.jpg" width="150" />
32 <figcaption>The Eagle in lunar orbit</figcaption>
33 </figure>
34
35 <ul>
36 <li>Foo!</li>
37 <li>Bar!</li>
38 </ul>
39
40 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
41 </div>
42 </div>
43
44 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
45
46 <p>Pellentesque vitae eleifend nisl, non accumsan tellus. Maecenas nec libero non tellus tincidunt mollis porttitor sed arcu. Donec ultricies nulla vitae eros lacinia, vel congue sem auctor. Vivamus convallis, urna ac tincidunt malesuada, lectus erat convallis metus, a hendrerit massa augue accumsan magna. Nulla mattis tellus elit, nec congue magna scelerisque eget. Aliquam posuere nisi augue, posuere sodales nisi iaculis eu. Donec fermentum urna id nibh sagittis fermentum sit amet sed enim. Aliquam neque elit, pretium elementum nunc a, faucibus accumsan lorem. Etiam pulvinar odio et hendrerit tincidunt. Suspendisse tempus eros lacus, in convallis velit mollis ut. Aenean congue, justo eleifend ultricies malesuada, nunc nunc molestie mauris, eget placerat libero eros vel nisi. Quisque diam arcu, mollis ac laoreet vitae, varius et sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis in vehicula sapien. Nunc feugiat porta elit nec volutpat.</p>
47
48 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
49
50 <div class="simplebox align-center" style="width: 750px">
51 <h2 class="simplebox-title">Title</h2>
52 <div class="simplebox-content">
53 <p><img alt="The Eagle" src="assets/sample.jpg" width="150" style="float: left" /><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
54
55 <ul>
56 <li>Foo!</li>
57 <li>Bar!</li>
58 </ul>
59 </div>
60 </div>
61
62 <p>Ut eget ipsum a sapien porta ultrices. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus mi lacus, pharetra eu bibendum blandit, tristique sit amet leo. Integer eu nulla nec magna vulputate blandit. Praesent mattis quis ante eget adipiscing. Nulla vel tempus risus, in placerat velit. Mauris sed nibh at elit posuere laoreet. Morbi non sapien sed nunc fringilla imperdiet.</p>
63 </textarea>
64
65 <h2>Inline Sample</h2>
66 <div id="editor2" contenteditable="true">
67 <h1>Simple Box Sample</h1>
68
69 <div class="simplebox align-right" style="width: 60%">
70 <h2 class="simplebox-title">Title</h2>
71 <div class="simplebox-content">
72 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
73
74 <figure class="image" style="float: right">
75 <img alt="The Eagle" src="assets/sample.jpg" width="150" />
76 <figcaption>The Eagle in lunar orbit</figcaption>
77 </figure>
78
79 <ul>
80 <li>Foo!</li>
81 <li>Bar!</li>
82 </ul>
83
84 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
85 </div>
86 </div>
87
88 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
89
90 <p>Pellentesque vitae eleifend nisl, non accumsan tellus. Maecenas nec libero non tellus tincidunt mollis porttitor sed arcu. Donec ultricies nulla vitae eros lacinia, vel congue sem auctor. Vivamus convallis, urna ac tincidunt malesuada, lectus erat convallis metus, a hendrerit massa augue accumsan magna. Nulla mattis tellus elit, nec congue magna scelerisque eget. Aliquam posuere nisi augue, posuere sodales nisi iaculis eu. Donec fermentum urna id nibh sagittis fermentum sit amet sed enim. Aliquam neque elit, pretium elementum nunc a, faucibus accumsan lorem. Etiam pulvinar odio et hendrerit tincidunt. Suspendisse tempus eros lacus, in convallis velit mollis ut. Aenean congue, justo eleifend ultricies malesuada, nunc nunc molestie mauris, eget placerat libero eros vel nisi. Quisque diam arcu, mollis ac laoreet vitae, varius et sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis in vehicula sapien. Nunc feugiat porta elit nec volutpat.</p>
91
92 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet orci ut nisi adipiscing ultrices. Sed pellentesque iaculis malesuada. Pellentesque scelerisque, purus non porta dictum, neque urna bibendum dolor, eget tristique ipsum metus fringilla dolor. Nullam sed accumsan sapien. Vestibulum in placerat magna. Sed justo lacus, volutpat rhoncus odio luctus, ornare adipiscing mauris. Vivamus erat sem, egestas et lectus eget, varius cursus odio. Duis posuere lacus sit amet urna bibendum, id iaculis eros ultrices. Vestibulum a ultrices ante.</p>
93
94 <div class="simplebox align-center" style="width: 750px">
95 <h2 class="simplebox-title">Title</h2>
96 <div class="simplebox-content">
97 <p><img alt="The Eagle" src="assets/sample.jpg" width="150" style="float: left" /><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
98
99 <ul>
100 <li>Foo!</li>
101 <li>Bar!</li>
102 </ul>
103 </div>
104 </div>
105
106 <p>Ut eget ipsum a sapien porta ultrices. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus mi lacus, pharetra eu bibendum blandit, tristique sit amet leo. Integer eu nulla nec magna vulputate blandit. Praesent mattis quis ante eget adipiscing. Nulla vel tempus risus, in placerat velit. Mauris sed nibh at elit posuere laoreet. Morbi non sapien sed nunc fringilla imperdiet.</p>
107 </div>
108
109 <script>
110 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
111 CKEDITOR.tools.enableHtml5Elements( document );
112
113 CKEDITOR.plugins.addExternal( 'simplebox', 'plugins/widget/dev/assets/simplebox/' );
114
115 CKEDITOR.replace( 'editor1', {
116 extraPlugins: 'simplebox,placeholder,image2',
117 removePlugins: 'forms,bidi',
118 contentsCss: [ '../../../contents.css', 'assets/simplebox/contents.css' ],
119 height: 500
120 } );
121
122 CKEDITOR.inline( 'editor2', {
123 extraPlugins: 'simplebox,placeholder,image2',
124 removePlugins: 'forms,bidi'
125 } );
126
127 CKCONSOLE.create( 'widget', { editor: 'editor1' } );
128 CKCONSOLE.create( 'focus', { editor: 'editor1' } );
129 CKCONSOLE.create( 'widget', { editor: 'editor2', folded: true } );
130 CKCONSOLE.create( 'focus', { editor: 'editor2', folded: true } );
131
132 </script>
133</body>
134</html>
diff --git a/sources/plugins/widget/dev/widgetstyles.html b/sources/plugins/widget/dev/widgetstyles.html
new file mode 100644
index 0000000..8e54b8d
--- /dev/null
+++ b/sources/plugins/widget/dev/widgetstyles.html
@@ -0,0 +1,144 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
4For licensing, see LICENSE.md or http://ckeditor.com/license
5-->
6<html>
7<head>
8 <meta charset="utf-8">
9 <title>Applying styles to widgets &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <link rel="stylesheet" href="../../../samples/old/sample.css">
12 <link rel="stylesheet" href="../../../contents.css">
13 <link rel="stylesheet" href="assets/contents.css">
14</head>
15<body>
16 <h1 class="samples">Applying styles to widgets</h1>
17
18 <h2>Classic (iframe-based) Sample</h2>
19 <textarea cols="80" id="editor1" name="editor1" rows="10">
20 <h1>Apollo 11</h1>
21
22 <figure class="image" style="float: right">
23 <img alt="Saturn V" src="../../../samples/assets/sample.jpg" width="150" />
24 <figcaption>Roll out of Saturn V on launch pad</figcaption>
25 </figure>
26
27 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
28
29 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
30
31 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
32
33 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
34
35 <blockquote>
36 <p>One small step for [a] man, one giant leap for mankind.</p>
37 </blockquote>
38
39 <p><span class="math-tex">\( \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \)</span></p>
40
41 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
42
43 <blockquote>
44 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
45 </blockquote>
46
47 <figure class="image" style="float: right">
48 <img alt="The Eagle" src="../../../samples/assets/sample.jpg" width="150" />
49 <figcaption>The Eagle in lunar orbit</figcaption>
50 </figure>
51
52 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
53
54 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
55
56 <ol>
57 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
58 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
59 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
60 </ol>
61
62 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
63 </textarea>
64
65 <h2>Inline Sample</h2>
66 <div id="editor2" contenteditable="true">
67 <h1>Apollo 11</h1>
68
69 <figure class="image" style="float: right">
70 <img alt="Saturn V" src="../../../samples/assets/sample.jpg" width="150" />
71 <figcaption>Roll out of Saturn V on launch pad</figcaption>
72 </figure>
73
74 <p><strong>Apollo 11</strong> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on [[July 20, 1969, at 20:18 UTC]]. Armstrong became the first to step onto the lunar surface 6 hours later on [[July 21 at 02:56 UTC]].</p>
75
76 <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
77
78 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
79
80 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
81
82 <blockquote>
83 <p>One small step for [a] man, one giant leap for mankind.</p>
84 </blockquote>
85
86 <p><span class="math-tex">\( \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \)</span></p>
87
88 <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
89
90 <blockquote>
91 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
92 </blockquote>
93
94 <figure class="image" style="float: right">
95 <img alt="The Eagle" src="../../../samples/assets/sample.jpg" width="150" />
96 <figcaption>The Eagle in lunar orbit</figcaption>
97 </figure>
98
99 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
100
101 <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
102
103 <ol>
104 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
105 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
106 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
107 </ol>
108
109 <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
110 </div>
111
112 <script>
113 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
114 CKEDITOR.tools.enableHtml5Elements( document );
115
116 CKEDITOR.disableAutoInline = true;
117
118 var stylesSet = [
119 { name: 'Medium border', type: 'widget', widget: 'image', attributes: { 'class': 'mediumBorder' } },
120 { name: 'Thick border', type: 'widget', widget: 'image', attributes: { 'class': 'thickBorder' } },
121 { name: 'So important', type: 'widget', widget: 'image', attributes: { 'class': 'important soMuch' } },
122
123 { name: 'Red marker', type: 'widget', widget: 'placeholder', attributes: { 'class': 'redMarker' } },
124 { name: 'Invisible Placeholder', type: 'widget', widget: 'placeholder', attributes: { 'class': 'invisible' } },
125
126 { name: 'Invisible Mathjax', type: 'widget', widget: 'mathjax', attributes: { 'class': 'invisible' } }
127 ];
128
129 CKEDITOR.replace( 'editor1', {
130 extraPlugins: 'placeholder,image2,mathjax',
131 contentsCss: [ '../../../contents.css', 'assets/contents.css' ],
132 stylesSet: stylesSet,
133 height: 300
134 } );
135
136 CKEDITOR.inline( 'editor2', {
137 extraPlugins: 'placeholder,image2,mathjax',
138 stylesSet: stylesSet,
139 height: 300
140 } );
141
142 </script>
143</body>
144</html>
diff --git a/sources/plugins/widget/images/handle.png b/sources/plugins/widget/images/handle.png
new file mode 100644
index 0000000..ba8cda5
--- /dev/null
+++ b/sources/plugins/widget/images/handle.png
Binary files differ
diff --git a/sources/plugins/widget/lang/af.js b/sources/plugins/widget/lang/af.js
new file mode 100644
index 0000000..e37c598
--- /dev/null
+++ b/sources/plugins/widget/lang/af.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'af', {
6 'move': 'Klik en trek on te beweeg',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/ar.js b/sources/plugins/widget/lang/ar.js
new file mode 100644
index 0000000..0a4f8eb
--- /dev/null
+++ b/sources/plugins/widget/lang/ar.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ar', {
6 'move': 'إضغط و إسحب للتحريك',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/az.js b/sources/plugins/widget/lang/az.js
new file mode 100644
index 0000000..24ddaf3
--- /dev/null
+++ b/sources/plugins/widget/lang/az.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'az', {
6 'move': 'Tıklayın və aparın',
7 'label': '%1 vidjet'
8} );
diff --git a/sources/plugins/widget/lang/bg.js b/sources/plugins/widget/lang/bg.js
new file mode 100644
index 0000000..9b51458
--- /dev/null
+++ b/sources/plugins/widget/lang/bg.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'bg', {
6 'move': 'Кликни и влачи, за да преместиш',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/ca.js b/sources/plugins/widget/lang/ca.js
new file mode 100644
index 0000000..f46a4f8
--- /dev/null
+++ b/sources/plugins/widget/lang/ca.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ca', {
6 'move': 'Clicar i arrossegar per moure',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/cs.js b/sources/plugins/widget/lang/cs.js
new file mode 100644
index 0000000..5d9590b
--- /dev/null
+++ b/sources/plugins/widget/lang/cs.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'cs', {
6 'move': 'Klepněte a táhněte pro přesunutí',
7 'label': 'Ovládací prvek %1'
8} );
diff --git a/sources/plugins/widget/lang/cy.js b/sources/plugins/widget/lang/cy.js
new file mode 100644
index 0000000..29dc110
--- /dev/null
+++ b/sources/plugins/widget/lang/cy.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'cy', {
6 'move': 'Clcio a llusgo i symud',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/da.js b/sources/plugins/widget/lang/da.js
new file mode 100644
index 0000000..8dfe785
--- /dev/null
+++ b/sources/plugins/widget/lang/da.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'da', {
6 'move': 'Klik og træk for at flytte',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/de-ch.js b/sources/plugins/widget/lang/de-ch.js
new file mode 100644
index 0000000..a95febb
--- /dev/null
+++ b/sources/plugins/widget/lang/de-ch.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'de-ch', {
6 'move': 'Zum Verschieben anwählen und ziehen',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/de.js b/sources/plugins/widget/lang/de.js
new file mode 100644
index 0000000..838ad89
--- /dev/null
+++ b/sources/plugins/widget/lang/de.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'de', {
6 'move': 'Zum Verschieben anwählen und ziehen',
7 'label': '%1 Steuerelement'
8} );
diff --git a/sources/plugins/widget/lang/el.js b/sources/plugins/widget/lang/el.js
new file mode 100644
index 0000000..863604a
--- /dev/null
+++ b/sources/plugins/widget/lang/el.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'el', {
6 'move': 'Κάνετε κλικ και σύρετε το ποντίκι για να μετακινήστε',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/en-gb.js b/sources/plugins/widget/lang/en-gb.js
new file mode 100644
index 0000000..ed09606
--- /dev/null
+++ b/sources/plugins/widget/lang/en-gb.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'en-gb', {
6 'move': 'Click and drag to move',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/en.js b/sources/plugins/widget/lang/en.js
new file mode 100644
index 0000000..5967f4d
--- /dev/null
+++ b/sources/plugins/widget/lang/en.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'en', {
6 'move': 'Click and drag to move',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/eo.js b/sources/plugins/widget/lang/eo.js
new file mode 100644
index 0000000..381511a
--- /dev/null
+++ b/sources/plugins/widget/lang/eo.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'eo', {
6 'move': 'klaki kaj treni por movi',
7 'label': '%1 fenestraĵo'
8} );
diff --git a/sources/plugins/widget/lang/es-mx.js b/sources/plugins/widget/lang/es-mx.js
new file mode 100644
index 0000000..279ea3e
--- /dev/null
+++ b/sources/plugins/widget/lang/es-mx.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'es-mx', {
6 'move': 'Presiona y arrastra para mover',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/es.js b/sources/plugins/widget/lang/es.js
new file mode 100644
index 0000000..d8deb2c
--- /dev/null
+++ b/sources/plugins/widget/lang/es.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'es', {
6 'move': 'Dar clic y arrastrar para mover',
7 'label': 'reproductor %1'
8} );
diff --git a/sources/plugins/widget/lang/eu.js b/sources/plugins/widget/lang/eu.js
new file mode 100644
index 0000000..e128dfe
--- /dev/null
+++ b/sources/plugins/widget/lang/eu.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'eu', {
6 'move': 'Klikatu eta arrastatu lekuz aldatzeko',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/fa.js b/sources/plugins/widget/lang/fa.js
new file mode 100644
index 0000000..57ea31f
--- /dev/null
+++ b/sources/plugins/widget/lang/fa.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fa', {
6 'move': 'کلیک و کشیدن برای جابجایی',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/fi.js b/sources/plugins/widget/lang/fi.js
new file mode 100644
index 0000000..759b2cf
--- /dev/null
+++ b/sources/plugins/widget/lang/fi.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fi', {
6 'move': 'Siirrä klikkaamalla ja raahaamalla',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/fr.js b/sources/plugins/widget/lang/fr.js
new file mode 100644
index 0000000..9decbf6
--- /dev/null
+++ b/sources/plugins/widget/lang/fr.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fr', {
6 'move': 'Cliquer et glisser pour déplacer',
7 'label': 'Élément %1'
8} );
diff --git a/sources/plugins/widget/lang/gl.js b/sources/plugins/widget/lang/gl.js
new file mode 100644
index 0000000..32f65d1
--- /dev/null
+++ b/sources/plugins/widget/lang/gl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'gl', {
6 'move': 'Prema e arrastre para mover',
7 'label': 'Trebello %1'
8} );
diff --git a/sources/plugins/widget/lang/he.js b/sources/plugins/widget/lang/he.js
new file mode 100644
index 0000000..b8dd2cb
--- /dev/null
+++ b/sources/plugins/widget/lang/he.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'he', {
6 'move': 'לחץ וגרור להזזה',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/hr.js b/sources/plugins/widget/lang/hr.js
new file mode 100644
index 0000000..6b47d95
--- /dev/null
+++ b/sources/plugins/widget/lang/hr.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'hr', {
6 'move': 'Klikni i povuci za pomicanje',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/hu.js b/sources/plugins/widget/lang/hu.js
new file mode 100644
index 0000000..a06047f
--- /dev/null
+++ b/sources/plugins/widget/lang/hu.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'hu', {
6 'move': 'Kattints és húzd a mozgatáshoz',
7 'label': '%1 modul'
8} );
diff --git a/sources/plugins/widget/lang/id.js b/sources/plugins/widget/lang/id.js
new file mode 100644
index 0000000..ef19392
--- /dev/null
+++ b/sources/plugins/widget/lang/id.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'id', {
6 'move': 'Tekan dan geser untuk memindahkan',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/it.js b/sources/plugins/widget/lang/it.js
new file mode 100644
index 0000000..59b43f6
--- /dev/null
+++ b/sources/plugins/widget/lang/it.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'it', {
6 'move': 'Fare clic e trascinare per spostare',
7 'label': 'Widget %1'
8} );
diff --git a/sources/plugins/widget/lang/ja.js b/sources/plugins/widget/lang/ja.js
new file mode 100644
index 0000000..a742718
--- /dev/null
+++ b/sources/plugins/widget/lang/ja.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ja', {
6 'move': 'ドラッグして移動',
7 'label': '%1 ウィジェット'
8} );
diff --git a/sources/plugins/widget/lang/km.js b/sources/plugins/widget/lang/km.js
new file mode 100644
index 0000000..6c61c73
--- /dev/null
+++ b/sources/plugins/widget/lang/km.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'km', {
6 'move': 'ចុច​ហើយ​ទាញ​ដើម្បី​ផ្លាស់​ទី',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/ko.js b/sources/plugins/widget/lang/ko.js
new file mode 100644
index 0000000..03afb95
--- /dev/null
+++ b/sources/plugins/widget/lang/ko.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ko', {
6 'move': '움직이려면 클릭 후 드래그 하세요',
7 'label': '%1 위젯'
8} );
diff --git a/sources/plugins/widget/lang/ku.js b/sources/plugins/widget/lang/ku.js
new file mode 100644
index 0000000..2af50cb
--- /dev/null
+++ b/sources/plugins/widget/lang/ku.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ku', {
6 'move': 'کرتەبکە و ڕایبکێشە بۆ جوڵاندن',
7 'label': '%1 ویجێت'
8} );
diff --git a/sources/plugins/widget/lang/lv.js b/sources/plugins/widget/lang/lv.js
new file mode 100644
index 0000000..e5fc3ac
--- /dev/null
+++ b/sources/plugins/widget/lang/lv.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'lv', {
6 'move': 'Klikšķina un velc, lai pārvietotu',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/nb.js b/sources/plugins/widget/lang/nb.js
new file mode 100644
index 0000000..af46bc9
--- /dev/null
+++ b/sources/plugins/widget/lang/nb.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'nb', {
6 'move': 'Klikk og dra for å flytte',
7 'label': 'Widget %1'
8} );
diff --git a/sources/plugins/widget/lang/nl.js b/sources/plugins/widget/lang/nl.js
new file mode 100644
index 0000000..56b182c
--- /dev/null
+++ b/sources/plugins/widget/lang/nl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'nl', {
6 'move': 'Klik en sleep om te verplaatsen',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/no.js b/sources/plugins/widget/lang/no.js
new file mode 100644
index 0000000..b86bd7b
--- /dev/null
+++ b/sources/plugins/widget/lang/no.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'no', {
6 'move': 'Klikk og dra for å flytte',
7 'label': 'Widget %1'
8} );
diff --git a/sources/plugins/widget/lang/oc.js b/sources/plugins/widget/lang/oc.js
new file mode 100644
index 0000000..10ff842
--- /dev/null
+++ b/sources/plugins/widget/lang/oc.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'oc', {
6 'move': 'Clicar e lisar per desplaçar',
7 'label': 'Element %1'
8} );
diff --git a/sources/plugins/widget/lang/pl.js b/sources/plugins/widget/lang/pl.js
new file mode 100644
index 0000000..2d90df8
--- /dev/null
+++ b/sources/plugins/widget/lang/pl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pl', {
6 'move': 'Kliknij i przeciągnij, by przenieść.',
7 'label': 'Widget %1'
8} );
diff --git a/sources/plugins/widget/lang/pt-br.js b/sources/plugins/widget/lang/pt-br.js
new file mode 100644
index 0000000..9ce9be4
--- /dev/null
+++ b/sources/plugins/widget/lang/pt-br.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pt-br', {
6 'move': 'Click e arraste para mover',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/pt.js b/sources/plugins/widget/lang/pt.js
new file mode 100644
index 0000000..422ef93
--- /dev/null
+++ b/sources/plugins/widget/lang/pt.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pt', {
6 'move': 'Clique e arraste para mover',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/ru.js b/sources/plugins/widget/lang/ru.js
new file mode 100644
index 0000000..d6f36f5
--- /dev/null
+++ b/sources/plugins/widget/lang/ru.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ru', {
6 'move': 'Нажмите и перетащите, чтобы переместить',
7 'label': '%1 виджет'
8} );
diff --git a/sources/plugins/widget/lang/sk.js b/sources/plugins/widget/lang/sk.js
new file mode 100644
index 0000000..5bf4cc0
--- /dev/null
+++ b/sources/plugins/widget/lang/sk.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sk', {
6 'move': 'Kliknite a potiahnite pre presunutie',
7 'label': '%1 widget'
8} );
diff --git a/sources/plugins/widget/lang/sl.js b/sources/plugins/widget/lang/sl.js
new file mode 100644
index 0000000..96ffd1e
--- /dev/null
+++ b/sources/plugins/widget/lang/sl.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sl', {
6 'move': 'Kliknite in povlecite, da premaknete',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/sq.js b/sources/plugins/widget/lang/sq.js
new file mode 100644
index 0000000..ad46c7d
--- /dev/null
+++ b/sources/plugins/widget/lang/sq.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sq', {
6 'move': 'Kliko dhe tërhiqe për ta lëvizur',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/sv.js b/sources/plugins/widget/lang/sv.js
new file mode 100644
index 0000000..7faff37
--- /dev/null
+++ b/sources/plugins/widget/lang/sv.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sv', {
6 'move': 'Klicka och drag för att flytta',
7 'label': '%1-widget'
8} );
diff --git a/sources/plugins/widget/lang/tr.js b/sources/plugins/widget/lang/tr.js
new file mode 100644
index 0000000..92fa952
--- /dev/null
+++ b/sources/plugins/widget/lang/tr.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'tr', {
6 'move': 'Taşımak için, tıklayın ve sürükleyin',
7 'label': '%1 Grafik Beleşeni'
8} );
diff --git a/sources/plugins/widget/lang/tt.js b/sources/plugins/widget/lang/tt.js
new file mode 100644
index 0000000..30fb31a
--- /dev/null
+++ b/sources/plugins/widget/lang/tt.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'tt', {
6 'move': 'Күчереп куер өчен басып шудырыгыз',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/ug.js b/sources/plugins/widget/lang/ug.js
new file mode 100644
index 0000000..fc216c4
--- /dev/null
+++ b/sources/plugins/widget/lang/ug.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ug', {
6 'move': 'يۆتكەشتە چېكىپ سۆرەڭ',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/uk.js b/sources/plugins/widget/lang/uk.js
new file mode 100644
index 0000000..fab6d5d
--- /dev/null
+++ b/sources/plugins/widget/lang/uk.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'uk', {
6 'move': 'Клікніть і потягніть для переміщення',
7 'label': '%1 віджет'
8} );
diff --git a/sources/plugins/widget/lang/vi.js b/sources/plugins/widget/lang/vi.js
new file mode 100644
index 0000000..2bbe246
--- /dev/null
+++ b/sources/plugins/widget/lang/vi.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'vi', {
6 'move': 'Nhấp chuột và kéo để di chuyển',
7 'label': '%1 widget' // MISSING
8} );
diff --git a/sources/plugins/widget/lang/zh-cn.js b/sources/plugins/widget/lang/zh-cn.js
new file mode 100644
index 0000000..2381407
--- /dev/null
+++ b/sources/plugins/widget/lang/zh-cn.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'zh-cn', {
6 'move': '点击并拖拽以移动',
7 'label': '%1 小部件'
8} );
diff --git a/sources/plugins/widget/lang/zh.js b/sources/plugins/widget/lang/zh.js
new file mode 100644
index 0000000..b6e945b
--- /dev/null
+++ b/sources/plugins/widget/lang/zh.js
@@ -0,0 +1,8 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'zh', {
6 'move': '拖曳以移動',
7 'label': '%1 小工具'
8} );
diff --git a/sources/plugins/widget/plugin.js b/sources/plugins/widget/plugin.js
new file mode 100644
index 0000000..2708e09
--- /dev/null
+++ b/sources/plugins/widget/plugin.js
@@ -0,0 +1,4147 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/**
7 * @fileOverview [Widget](http://ckeditor.com/addon/widget) plugin.
8 */
9
10'use strict';
11
12( function() {
13 var DRAG_HANDLER_SIZE = 15;
14
15 CKEDITOR.plugins.add( 'widget', {
16 // jscs:disable maximumLineLength
17 lang: 'af,ar,az,bg,ca,cs,cy,da,de,de-ch,el,en,en-gb,eo,es,es-mx,eu,fa,fi,fr,gl,he,hr,hu,id,it,ja,km,ko,ku,lv,nb,nl,no,oc,pl,pt,pt-br,ru,sk,sl,sq,sv,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
18 // jscs:enable maximumLineLength
19 requires: 'lineutils,clipboard,widgetselection',
20 onLoad: function() {
21 CKEDITOR.addCss(
22 '.cke_widget_wrapper{' +
23 'position:relative;' +
24 'outline:none' +
25 '}' +
26 '.cke_widget_inline{' +
27 'display:inline-block' +
28 '}' +
29 '.cke_widget_wrapper:hover>.cke_widget_element{' +
30 'outline:2px solid yellow;' +
31 'cursor:default' +
32 '}' +
33 '.cke_widget_wrapper:hover .cke_widget_editable{' +
34 'outline:2px solid yellow' +
35 '}' +
36 '.cke_widget_wrapper.cke_widget_focused>.cke_widget_element,' +
37 // We need higher specificity than hover style.
38 '.cke_widget_wrapper .cke_widget_editable.cke_widget_editable_focused{' +
39 'outline:2px solid #ace' +
40 '}' +
41 '.cke_widget_editable{' +
42 'cursor:text' +
43 '}' +
44 '.cke_widget_drag_handler_container{' +
45 'position:absolute;' +
46 'width:' + DRAG_HANDLER_SIZE + 'px;' +
47 'height:0;' +
48 // Initially drag handler should not be visible, until its position will be
49 // calculated (http://dev.ckeditor.com/ticket/11177).
50 // We need to hide unpositined handlers, so they don't extend
51 // widget's outline far to the left (http://dev.ckeditor.com/ticket/12024).
52 'display:none;' +
53 'opacity:0.75;' +
54 'transition:height 0s 0.2s;' + // Delay hiding drag handler.
55 // Prevent drag handler from being misplaced (http://dev.ckeditor.com/ticket/11198).
56 'line-height:0' +
57 '}' +
58 '.cke_widget_wrapper:hover>.cke_widget_drag_handler_container{' +
59 'height:' + DRAG_HANDLER_SIZE + 'px;' +
60 'transition:none' +
61 '}' +
62 '.cke_widget_drag_handler_container:hover{' +
63 'opacity:1' +
64 '}' +
65 'img.cke_widget_drag_handler{' +
66 'cursor:move;' +
67 'width:' + DRAG_HANDLER_SIZE + 'px;' +
68 'height:' + DRAG_HANDLER_SIZE + 'px;' +
69 'display:inline-block' +
70 '}' +
71 '.cke_widget_mask{' +
72 'position:absolute;' +
73 'top:0;' +
74 'left:0;' +
75 'width:100%;' +
76 'height:100%;' +
77 'display:block' +
78 '}' +
79 '.cke_editable.cke_widget_dragging, .cke_editable.cke_widget_dragging *{' +
80 'cursor:move !important' +
81 '}'
82 );
83 },
84
85 beforeInit: function( editor ) {
86 /**
87 * An instance of widget repository. It contains all
88 * {@link CKEDITOR.plugins.widget.repository#registered registered widget definitions} and
89 * {@link CKEDITOR.plugins.widget.repository#instances initialized instances}.
90 *
91 * editor.widgets.add( 'someName', {
92 * // Widget definition...
93 * } );
94 *
95 * editor.widgets.registered.someName; // -> Widget definition
96 *
97 * @since 4.3
98 * @readonly
99 * @property {CKEDITOR.plugins.widget.repository} widgets
100 * @member CKEDITOR.editor
101 */
102 editor.widgets = new Repository( editor );
103 },
104
105 afterInit: function( editor ) {
106 addWidgetButtons( editor );
107 setupContextMenu( editor );
108 }
109 } );
110
111 /**
112 * Widget repository. It keeps track of all {@link #registered registered widget definitions} and
113 * {@link #instances initialized instances}. An instance of the repository is available under
114 * the {@link CKEDITOR.editor#widgets} property.
115 *
116 * @class CKEDITOR.plugins.widget.repository
117 * @mixins CKEDITOR.event
118 * @constructor Creates a widget repository instance. Note that the widget plugin automatically
119 * creates a repository instance which is available under the {@link CKEDITOR.editor#widgets} property.
120 * @param {CKEDITOR.editor} editor The editor instance for which the repository will be created.
121 */
122 function Repository( editor ) {
123 /**
124 * The editor instance for which this repository was created.
125 *
126 * @readonly
127 * @property {CKEDITOR.editor} editor
128 */
129 this.editor = editor;
130
131 /**
132 * A hash of registered widget definitions (definition name => {@link CKEDITOR.plugins.widget.definition}).
133 *
134 * To register a definition use the {@link #add} method.
135 *
136 * @readonly
137 */
138 this.registered = {};
139
140 /**
141 * An object containing initialized widget instances (widget id => {@link CKEDITOR.plugins.widget}).
142 *
143 * @readonly
144 */
145 this.instances = {};
146
147 /**
148 * An array of selected widget instances.
149 *
150 * @readonly
151 * @property {CKEDITOR.plugins.widget[]} selected
152 */
153 this.selected = [];
154
155 /**
156 * The focused widget instance. See also {@link CKEDITOR.plugins.widget#event-focus}
157 * and {@link CKEDITOR.plugins.widget#event-blur} events.
158 *
159 * editor.on( 'selectionChange', function() {
160 * if ( editor.widgets.focused ) {
161 * // Do something when a widget is focused...
162 * }
163 * } );
164 *
165 * @readonly
166 * @property {CKEDITOR.plugins.widget} focused
167 */
168 this.focused = null;
169
170 /**
171 * The widget instance that contains the nested editable which is currently focused.
172 *
173 * @readonly
174 * @property {CKEDITOR.plugins.widget} widgetHoldingFocusedEditable
175 */
176 this.widgetHoldingFocusedEditable = null;
177
178 this._ = {
179 nextId: 0,
180 upcasts: [],
181 upcastCallbacks: [],
182 filters: {}
183 };
184
185 setupWidgetsLifecycle( this );
186 setupSelectionObserver( this );
187 setupMouseObserver( this );
188 setupKeyboardObserver( this );
189 setupDragAndDrop( this );
190 setupNativeCutAndCopy( this );
191 }
192
193 Repository.prototype = {
194 /**
195 * Minimum interval between selection checks.
196 *
197 * @private
198 */
199 MIN_SELECTION_CHECK_INTERVAL: 500,
200
201 /**
202 * Adds a widget definition to the repository. Fires the {@link CKEDITOR.editor#widgetDefinition} event
203 * which allows to modify the widget definition which is going to be registered.
204 *
205 * @param {String} name The name of the widget definition.
206 * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition.
207 * @returns {CKEDITOR.plugins.widget.definition}
208 */
209 add: function( name, widgetDef ) {
210 // Create prototyped copy of original widget definition, so we won't modify it.
211 widgetDef = CKEDITOR.tools.prototypedCopy( widgetDef );
212 widgetDef.name = name;
213
214 widgetDef._ = widgetDef._ || {};
215
216 this.editor.fire( 'widgetDefinition', widgetDef );
217
218 if ( widgetDef.template )
219 widgetDef.template = new CKEDITOR.template( widgetDef.template );
220
221 addWidgetCommand( this.editor, widgetDef );
222 addWidgetProcessors( this, widgetDef );
223
224 this.registered[ name ] = widgetDef;
225
226 return widgetDef;
227 },
228
229 /**
230 * Adds a callback for element upcasting. Each callback will be executed
231 * for every element which is later tested by upcast methods. If a callback
232 * returns `false`, the element will not be upcasted.
233 *
234 * // Images with the "banner" class will not be upcasted (e.g. to the image widget).
235 * editor.widgets.addUpcastCallback( function( element ) {
236 * if ( element.name == 'img' && element.hasClass( 'banner' ) )
237 * return false;
238 * } );
239 *
240 * @param {Function} callback
241 * @param {CKEDITOR.htmlParser.element} callback.element
242 */
243 addUpcastCallback: function( callback ) {
244 this._.upcastCallbacks.push( callback );
245 },
246
247 /**
248 * Checks the selection to update widget states (selection and focus).
249 *
250 * This method is triggered by the {@link #event-checkSelection} event.
251 */
252 checkSelection: function() {
253 var sel = this.editor.getSelection(),
254 selectedElement = sel.getSelectedElement(),
255 updater = stateUpdater( this ),
256 widget;
257
258 // Widget is focused so commit and finish checking.
259 if ( selectedElement && ( widget = this.getByElement( selectedElement, true ) ) )
260 return updater.focus( widget ).select( widget ).commit();
261
262 var range = sel.getRanges()[ 0 ];
263
264 // No ranges or collapsed range mean that nothing is selected, so commit and finish checking.
265 if ( !range || range.collapsed )
266 return updater.commit();
267
268 // Range is not empty, so create walker checking for wrappers.
269 var walker = new CKEDITOR.dom.walker( range ),
270 wrapper;
271
272 walker.evaluator = Widget.isDomWidgetWrapper;
273
274 while ( ( wrapper = walker.next() ) )
275 updater.select( this.getByElement( wrapper ) );
276
277 updater.commit();
278 },
279
280 /**
281 * Checks if all widget instances are still present in the DOM.
282 * Destroys those instances that are not present.
283 * Reinitializes widgets on widget wrappers for which widget instances
284 * cannot be found. Takes nested widgets into account, too.
285 *
286 * This method triggers the {@link #event-checkWidgets} event whose listeners
287 * can cancel the method's execution or modify its options.
288 *
289 * @param [options] The options object.
290 * @param {Boolean} [options.initOnlyNew] Initializes widgets only on newly wrapped
291 * widget elements (those which still have the `cke_widget_new` class). When this option is
292 * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
293 * will not be reinitialized. This makes the check faster.
294 * @param {Boolean} [options.focusInited] If only one widget is initialized by
295 * the method, it will be focused.
296 */
297 checkWidgets: function( options ) {
298 this.fire( 'checkWidgets', CKEDITOR.tools.copy( options || {} ) );
299 },
300
301 /**
302 * Removes the widget from the editor and moves the selection to the closest
303 * editable position if the widget was focused before.
304 *
305 * @param {CKEDITOR.plugins.widget} widget The widget instance to be deleted.
306 */
307 del: function( widget ) {
308 if ( this.focused === widget ) {
309 var editor = widget.editor,
310 range = editor.createRange(),
311 found;
312
313 // If haven't found place for caret on the default side,
314 // try to find it on the other side.
315 if ( !( found = range.moveToClosestEditablePosition( widget.wrapper, true ) ) )
316 found = range.moveToClosestEditablePosition( widget.wrapper, false );
317
318 if ( found )
319 editor.getSelection().selectRanges( [ range ] );
320 }
321
322 widget.wrapper.remove();
323 this.destroy( widget, true );
324 },
325
326 /**
327 * Destroys the widget instance and all its nested widgets (widgets inside its nested editables).
328 *
329 * @param {CKEDITOR.plugins.widget} widget The widget instance to be destroyed.
330 * @param {Boolean} [offline] Whether the widget is offline (detached from the DOM tree) &mdash;
331 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
332 */
333 destroy: function( widget, offline ) {
334 if ( this.widgetHoldingFocusedEditable === widget )
335 setFocusedEditable( this, widget, null, offline );
336
337 widget.destroy( offline );
338 delete this.instances[ widget.id ];
339 this.fire( 'instanceDestroyed', widget );
340 },
341
342 /**
343 * Destroys all widget instances.
344 *
345 * @param {Boolean} [offline] Whether the widgets are offline (detached from the DOM tree) &mdash;
346 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
347 * @param {CKEDITOR.dom.element} [container] The container within widgets will be destroyed.
348 * This option will be ignored if the `offline` flag was set to `true`, because in such case
349 * it is not possible to find widgets within the passed block.
350 */
351 destroyAll: function( offline, container ) {
352 var widget,
353 id,
354 instances = this.instances;
355
356 if ( container && !offline ) {
357 var wrappers = container.find( '.cke_widget_wrapper' ),
358 l = wrappers.count(),
359 i = 0;
360
361 // Length is constant, because this is not a live node list.
362 // Note: since querySelectorAll returns nodes in document order,
363 // outer widgets are always placed before their nested widgets and therefore
364 // are destroyed before them.
365 for ( ; i < l; ++i ) {
366 widget = this.getByElement( wrappers.getItem( i ), true );
367 // Widget might not be found, because it could be a nested widget,
368 // which would be destroyed when destroying its parent.
369 if ( widget )
370 this.destroy( widget );
371 }
372
373 return;
374 }
375
376 for ( id in instances ) {
377 widget = instances[ id ];
378 this.destroy( widget, offline );
379 }
380 },
381
382 /**
383 * Finalizes a process of widget creation. This includes:
384 *
385 * * inserting widget element into editor,
386 * * marking widget instance as ready (see {@link CKEDITOR.plugins.widget#event-ready}),
387 * * focusing widget instance.
388 *
389 * This method is used by the default widget's command and is called
390 * after widget's dialog (if set) is closed. It may also be used in a
391 * customized process of widget creation and insertion.
392 *
393 * widget.once( 'edit', function() {
394 * // Finalize creation only of not ready widgets.
395 * if ( widget.isReady() )
396 * return;
397 *
398 * // Cancel edit event to prevent automatic widget insertion.
399 * evt.cancel();
400 *
401 * CustomDialog.open( widget.data, function saveCallback( savedData ) {
402 * // Cache the container, because widget may be destroyed while saving data,
403 * // if this process will require some deep transformations.
404 * var container = widget.wrapper.getParent();
405 *
406 * widget.setData( savedData );
407 *
408 * // Widget will be retrieved from container and inserted into editor.
409 * editor.widgets.finalizeCreation( container );
410 * } );
411 * } );
412 *
413 * @param {CKEDITOR.dom.element/CKEDITOR.dom.documentFragment} container The element
414 * or document fragment which contains widget wrapper. The container is used, so before
415 * finalizing creation the widget can be freely transformed (even destroyed and reinitialized).
416 */
417 finalizeCreation: function( container ) {
418 var wrapper = container.getFirst();
419 if ( wrapper && Widget.isDomWidgetWrapper( wrapper ) ) {
420 this.editor.insertElement( wrapper );
421
422 var widget = this.getByElement( wrapper );
423 // Fire postponed #ready event.
424 widget.ready = true;
425 widget.fire( 'ready' );
426 widget.focus();
427 }
428 },
429
430 /**
431 * Finds a widget instance which contains a given element. The element will be the {@link CKEDITOR.plugins.widget#wrapper wrapper}
432 * of the returned widget or a descendant of this {@link CKEDITOR.plugins.widget#wrapper wrapper}.
433 *
434 * editor.widgets.getByElement( someWidget.wrapper ); // -> someWidget
435 * editor.widgets.getByElement( someWidget.parts.caption ); // -> someWidget
436 *
437 * // Check wrapper only:
438 * editor.widgets.getByElement( someWidget.wrapper, true ); // -> someWidget
439 * editor.widgets.getByElement( someWidget.parts.caption, true ); // -> null
440 *
441 * @param {CKEDITOR.dom.element} element The element to be checked.
442 * @param {Boolean} [checkWrapperOnly] If set to `true`, the method will not check wrappers' descendants.
443 * @returns {CKEDITOR.plugins.widget} The widget instance or `null`.
444 */
445 getByElement: ( function() {
446 var validWrapperElements = { div: 1, span: 1 };
447 function getWidgetId( element ) {
448 return element.is( validWrapperElements ) && element.data( 'cke-widget-id' );
449 }
450
451 return function( element, checkWrapperOnly ) {
452 if ( !element )
453 return null;
454
455 var id = getWidgetId( element );
456
457 // There's no need to check element parents if element is a wrapper.
458 if ( !checkWrapperOnly && !id ) {
459 var limit = this.editor.editable();
460
461 // Try to find a closest ascendant which is a widget wrapper.
462 do {
463 element = element.getParent();
464 } while ( element && !element.equals( limit ) && !( id = getWidgetId( element ) ) );
465 }
466
467 return this.instances[ id ] || null;
468 };
469 } )(),
470
471 /**
472 * Initializes a widget on a given element if the widget has not been initialized on it yet.
473 *
474 * @param {CKEDITOR.dom.element} element The future widget element.
475 * @param {String/CKEDITOR.plugins.widget.definition} [widgetDef] Name of a widget or a widget definition.
476 * The widget definition should be previously registered by using the
477 * {@link CKEDITOR.plugins.widget.repository#add} method.
478 * @param [startupData] Widget startup data (has precedence over default one).
479 * @returns {CKEDITOR.plugins.widget} The widget instance or `null` if a widget could not be initialized on
480 * a given element.
481 */
482 initOn: function( element, widgetDef, startupData ) {
483 if ( !widgetDef )
484 widgetDef = this.registered[ element.data( 'widget' ) ];
485 else if ( typeof widgetDef == 'string' )
486 widgetDef = this.registered[ widgetDef ];
487
488 if ( !widgetDef )
489 return null;
490
491 // Wrap element if still wasn't wrapped (was added during runtime by method that skips dataProcessor).
492 var wrapper = this.wrapElement( element, widgetDef.name );
493
494 if ( wrapper ) {
495 // Check if widget wrapper is new (widget hasn't been initialized on it yet).
496 // This class will be removed by widget constructor to avoid locking snapshot twice.
497 if ( wrapper.hasClass( 'cke_widget_new' ) ) {
498 var widget = new Widget( this, this._.nextId++, element, widgetDef, startupData );
499
500 // Widget could be destroyed when initializing it.
501 if ( widget.isInited() ) {
502 this.instances[ widget.id ] = widget;
503
504 return widget;
505 } else {
506 return null;
507 }
508 }
509
510 // Widget already has been initialized, so try to get widget by element.
511 // Note - it may happen that other instance will returned than the one created above,
512 // if for example widget was destroyed and reinitialized.
513 return this.getByElement( element );
514 }
515
516 // No wrapper means that there's no widget for this element.
517 return null;
518 },
519
520 /**
521 * Initializes widgets on all elements which were wrapped by {@link #wrapElement} and
522 * have not been initialized yet.
523 *
524 * @param {CKEDITOR.dom.element} [container=editor.editable()] The container which will be checked for not
525 * initialized widgets. Defaults to editor's {@link CKEDITOR.editor#editable editable} element.
526 * @returns {CKEDITOR.plugins.widget[]} Array of widget instances which have been initialized.
527 * Note: Only first-level widgets are returned &mdash; without nested widgets.
528 */
529 initOnAll: function( container ) {
530 var newWidgets = ( container || this.editor.editable() ).find( '.cke_widget_new' ),
531 newInstances = [],
532 instance;
533
534 for ( var i = newWidgets.count(); i--; ) {
535 instance = this.initOn( newWidgets.getItem( i ).getFirst( Widget.isDomWidgetElement ) );
536 if ( instance )
537 newInstances.push( instance );
538 }
539
540 return newInstances;
541 },
542
543 /**
544 * Allows to listen to events on specific types of widgets, even if they are not created yet.
545 *
546 * Please note that this method inherits parameters from the {@link CKEDITOR.event#method-on} method with one
547 * extra parameter at the beginning which is the widget name.
548 *
549 * editor.widgets.onWidget( 'image', 'action', function( evt ) {
550 * // Event `action` occurs on `image` widget.
551 * } );
552 *
553 * @since 4.5
554 * @param {String} widgetName
555 * @param {String} eventName
556 * @param {Function} listenerFunction
557 * @param {Object} [scopeObj]
558 * @param {Object} [listenerData]
559 * @param {Number} [priority=10]
560 */
561 onWidget: function( widgetName ) {
562 var args = Array.prototype.slice.call( arguments );
563
564 args.shift();
565
566 for ( var i in this.instances ) {
567 var instance = this.instances[ i ];
568
569 if ( instance.name == widgetName ) {
570 instance.on.apply( instance, args );
571 }
572 }
573
574 this.on( 'instanceCreated', function( evt ) {
575 var widget = evt.data;
576
577 if ( widget.name == widgetName ) {
578 widget.on.apply( widget, args );
579 }
580 } );
581 },
582
583 /**
584 * Parses element classes string and returns an object
585 * whose keys contain class names. Skips all `cke_*` classes.
586 *
587 * This method is used by the {@link CKEDITOR.plugins.widget#getClasses} method and
588 * may be used when overriding that method.
589 *
590 * @since 4.4
591 * @param {String} classes String (value of `class` attribute).
592 * @returns {Object} Object containing classes or `null` if no classes found.
593 */
594 parseElementClasses: function( classes ) {
595 if ( !classes )
596 return null;
597
598 classes = CKEDITOR.tools.trim( classes ).split( /\s+/ );
599
600 var cl,
601 obj = {},
602 hasClasses = 0;
603
604 while ( ( cl = classes.pop() ) ) {
605 if ( cl.indexOf( 'cke_' ) == -1 )
606 obj[ cl ] = hasClasses = 1;
607 }
608
609 return hasClasses ? obj : null;
610 },
611
612 /**
613 * Wraps an element with a widget's non-editable container.
614 *
615 * If this method is called on an {@link CKEDITOR.htmlParser.element}, then it will
616 * also take care of fixing the DOM after wrapping (the wrapper may not be allowed in element's parent).
617 *
618 * @param {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} element The widget element to be wrapped.
619 * @param {String} [widgetName] The name of the widget definition. Defaults to element's `data-widget`
620 * attribute value.
621 * @returns {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} The wrapper element or `null` if
622 * the widget definition of this name is not registered.
623 */
624 wrapElement: function( element, widgetName ) {
625 var wrapper = null,
626 widgetDef,
627 isInline;
628
629 if ( element instanceof CKEDITOR.dom.element ) {
630 widgetName = widgetName || element.data( 'widget' );
631 widgetDef = this.registered[ widgetName ];
632
633 if ( !widgetDef )
634 return null;
635
636 // Do not wrap already wrapped element.
637 wrapper = element.getParent();
638 if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.data( 'cke-widget-wrapper' ) )
639 return wrapper;
640
641 // If attribute isn't already set (e.g. for pasted widget), set it.
642 if ( !element.hasAttribute( 'data-cke-widget-keep-attr' ) )
643 element.data( 'cke-widget-keep-attr', element.data( 'widget' ) ? 1 : 0 );
644
645 element.data( 'widget', widgetName );
646
647 isInline = isWidgetInline( widgetDef, element.getName() );
648
649 wrapper = new CKEDITOR.dom.element( isInline ? 'span' : 'div' );
650 wrapper.setAttributes( getWrapperAttributes( isInline, widgetName ) );
651
652 wrapper.data( 'cke-display-name', widgetDef.pathName ? widgetDef.pathName : element.getName() );
653
654 // Replace element unless it is a detached one.
655 if ( element.getParent( true ) )
656 wrapper.replace( element );
657 element.appendTo( wrapper );
658 }
659 else if ( element instanceof CKEDITOR.htmlParser.element ) {
660 widgetName = widgetName || element.attributes[ 'data-widget' ];
661 widgetDef = this.registered[ widgetName ];
662
663 if ( !widgetDef )
664 return null;
665
666 wrapper = element.parent;
667 if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.attributes[ 'data-cke-widget-wrapper' ] )
668 return wrapper;
669
670 // If attribute isn't already set (e.g. for pasted widget), set it.
671 if ( !( 'data-cke-widget-keep-attr' in element.attributes ) )
672 element.attributes[ 'data-cke-widget-keep-attr' ] = element.attributes[ 'data-widget' ] ? 1 : 0;
673 if ( widgetName )
674 element.attributes[ 'data-widget' ] = widgetName;
675
676 isInline = isWidgetInline( widgetDef, element.name );
677
678 wrapper = new CKEDITOR.htmlParser.element( isInline ? 'span' : 'div', getWrapperAttributes( isInline, widgetName ) );
679 wrapper.attributes[ 'data-cke-display-name' ] = widgetDef.pathName ? widgetDef.pathName : element.name;
680
681 var parent = element.parent,
682 index;
683
684 // Don't detach already detached element.
685 if ( parent ) {
686 index = element.getIndex();
687 element.remove();
688 }
689
690 wrapper.add( element );
691
692 // Insert wrapper fixing DOM (splitting parents if wrapper is not allowed inside them).
693 parent && insertElement( parent, index, wrapper );
694 }
695
696 return wrapper;
697 },
698
699 // Expose for tests.
700 _tests_createEditableFilter: createEditableFilter
701 };
702
703 CKEDITOR.event.implementOn( Repository.prototype );
704
705 /**
706 * An event fired when a widget instance is created, but before it is fully initialized.
707 *
708 * @event instanceCreated
709 * @param {CKEDITOR.plugins.widget} data The widget instance.
710 */
711
712 /**
713 * An event fired when a widget instance was destroyed.
714 *
715 * See also {@link CKEDITOR.plugins.widget#event-destroy}.
716 *
717 * @event instanceDestroyed
718 * @param {CKEDITOR.plugins.widget} data The widget instance.
719 */
720
721 /**
722 * An event fired to trigger the selection check.
723 *
724 * See the {@link #method-checkSelection} method.
725 *
726 * @event checkSelection
727 */
728
729 /**
730 * An event fired by the the {@link #method-checkWidgets} method.
731 *
732 * It can be canceled in order to stop the {@link #method-checkWidgets}
733 * method execution or the event listener can modify the method's options.
734 *
735 * @event checkWidgets
736 * @param [data]
737 * @param {Boolean} [data.initOnlyNew] Initialize widgets only on newly wrapped
738 * widget elements (those which still have the `cke_widget_new` class). When this option is
739 * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
740 * will not be reinitialized. This makes the check faster.
741 * @param {Boolean} [data.focusInited] If only one widget is initialized by
742 * the method, it will be focused.
743 */
744
745
746 /**
747 * An instance of a widget. Together with {@link CKEDITOR.plugins.widget.repository} these
748 * two classes constitute the core of the Widget System.
749 *
750 * Note that neither the repository nor the widget instances can be created by using their constructors.
751 * A repository instance is automatically set up by the Widget plugin and is accessible under
752 * {@link CKEDITOR.editor#widgets}, while widget instances are created and destroyed by the repository.
753 *
754 * To create a widget, first you need to {@link CKEDITOR.plugins.widget.repository#add register} its
755 * {@link CKEDITOR.plugins.widget.definition definition}:
756 *
757 * editor.widgets.add( 'simplebox', {
758 * upcast: function( element ) {
759 * // Defines which elements will become widgets.
760 * if ( element.hasClass( 'simplebox' ) )
761 * return true;
762 * },
763 * init: function() {
764 * // ...
765 * }
766 * } );
767 *
768 * Once the widget definition is registered, widgets will be automatically
769 * created when loading data:
770 *
771 * editor.setData( '<div class="simplebox">foo</div>', function() {
772 * console.log( editor.widgets.instances ); // -> An object containing one instance.
773 * } );
774 *
775 * It is also possible to create instances during runtime by using a command
776 * (if a {@link CKEDITOR.plugins.widget.definition#template} property was defined):
777 *
778 * // You can execute an automatically defined command to
779 * // insert a new simplebox widget or edit the one currently focused.
780 * editor.execCommand( 'simplebox' );
781 *
782 * Note: Since CKEditor 4.5 widget's `startupData` can be passed as the command argument:
783 *
784 * editor.execCommand( 'simplebox', {
785 * startupData: {
786 * align: 'left'
787 * }
788 * } );
789 *
790 * A widget can also be created in a completely custom way:
791 *
792 * var element = editor.document.createElement( 'div' );
793 * editor.insertElement( element );
794 * var widget = editor.widgets.initOn( element, 'simplebox' );
795 *
796 * @since 4.3
797 * @class CKEDITOR.plugins.widget
798 * @mixins CKEDITOR.event
799 * @extends CKEDITOR.plugins.widget.definition
800 * @constructor Creates an instance of the widget class. Do not use it directly, but instead initialize widgets
801 * by using the {@link CKEDITOR.plugins.widget.repository#initOn} method or by the upcasting system.
802 * @param {CKEDITOR.plugins.widget.repository} widgetsRepo
803 * @param {Number} id Unique ID of this widget instance.
804 * @param {CKEDITOR.dom.element} element The widget element.
805 * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget's registered definition.
806 * @param [startupData] Initial widget data. This data object will overwrite the default data and
807 * the data loaded from the DOM.
808 */
809 function Widget( widgetsRepo, id, element, widgetDef, startupData ) {
810 var editor = widgetsRepo.editor;
811
812 // Extend this widget with widgetDef-specific methods and properties.
813 CKEDITOR.tools.extend( this, widgetDef, {
814 /**
815 * The editor instance.
816 *
817 * @readonly
818 * @property {CKEDITOR.editor}
819 */
820 editor: editor,
821
822 /**
823 * This widget's unique (per editor instance) ID.
824 *
825 * @readonly
826 * @property {Number}
827 */
828 id: id,
829
830 /**
831 * Whether this widget is an inline widget (based on an inline element unless
832 * forced otherwise by {@link CKEDITOR.plugins.widget.definition#inline}).
833 *
834 * **Note:** This option does not allow to turn a block element into an inline widget.
835 * However, it makes it possible to turn an inline element into a block widget or to
836 * force a correct type in case when automatic recognition fails.
837 *
838 * @readonly
839 * @property {Boolean}
840 */
841 inline: element.getParent().getName() == 'span',
842
843 /**
844 * The widget element &mdash; the element on which the widget was initialized.
845 *
846 * @readonly
847 * @property {CKEDITOR.dom.element} element
848 */
849 element: element,
850
851 /**
852 * Widget's data object.
853 *
854 * The data can only be set by using the {@link #setData} method.
855 * Changes made to the data fire the {@link #event-data} event.
856 *
857 * @readonly
858 */
859 data: CKEDITOR.tools.extend( {}, typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ),
860
861 /**
862 * Indicates if a widget is data-ready. Set to `true` when data from all sources
863 * ({@link CKEDITOR.plugins.widget.definition#defaults}, set in the
864 * {@link #init} method, loaded from the widget's element and startup data coming from the constructor)
865 * are finally loaded. This is immediately followed by the first {@link #event-data}.
866 *
867 * @readonly
868 */
869 dataReady: false,
870
871 /**
872 * Whether a widget instance was initialized. This means that:
873 *
874 * * An instance was created,
875 * * Its properties were set,
876 * * The `init` method was executed.
877 *
878 * **Note**: The first {@link #event-data} event could not be fired yet which
879 * means that the widget's DOM has not been set up yet. Wait for the {@link #event-ready}
880 * event to be notified when a widget is fully initialized and ready.
881 *
882 * **Note**: Use the {@link #isInited} method to check whether a widget is initialized and
883 * has not been destroyed.
884 *
885 * @readonly
886 */
887 inited: false,
888
889 /**
890 * Whether a widget instance is ready. This means that the widget is {@link #inited} and
891 * that its DOM was finally set up.
892 *
893 * **Note:** Use the {@link #isReady} method to check whether a widget is ready and
894 * has not been destroyed.
895 *
896 * @readonly
897 */
898 ready: false,
899
900 // Revert what widgetDef could override (automatic #edit listener).
901 edit: Widget.prototype.edit,
902
903 /**
904 * The nested editable element which is currently focused.
905 *
906 * @readonly
907 * @property {CKEDITOR.plugins.widget.nestedEditable}
908 */
909 focusedEditable: null,
910
911 /**
912 * The widget definition from which this instance was created.
913 *
914 * @readonly
915 * @property {CKEDITOR.plugins.widget.definition} definition
916 */
917 definition: widgetDef,
918
919 /**
920 * Link to the widget repository which created this instance.
921 *
922 * @readonly
923 * @property {CKEDITOR.plugins.widget.repository} repository
924 */
925 repository: widgetsRepo,
926
927 draggable: widgetDef.draggable !== false,
928
929 // WAAARNING: Overwrite widgetDef's priv object, because otherwise violent unicorn's gonna visit you.
930 _: {
931 downcastFn: ( widgetDef.downcast && typeof widgetDef.downcast == 'string' ) ?
932 widgetDef.downcasts[ widgetDef.downcast ] : widgetDef.downcast
933 }
934 }, true );
935
936 /**
937 * An object of widget component elements.
938 *
939 * For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts},
940 * one `partName => element` pair is added to this object during the widget initialization.
941 *
942 * @readonly
943 * @property {Object} parts
944 */
945
946 /**
947 * The template which will be used to create a new widget element (when the widget's command is executed).
948 * It will be populated with {@link #defaults default values}.
949 *
950 * @readonly
951 * @property {CKEDITOR.template} template
952 */
953
954 /**
955 * The widget wrapper &mdash; a non-editable `div` or `span` element (depending on {@link #inline})
956 * which is a parent of the {@link #element} and widget compontents like the drag handler and the {@link #mask}.
957 * It is the outermost widget element.
958 *
959 * @readonly
960 * @property {CKEDITOR.dom.element} wrapper
961 */
962
963 widgetsRepo.fire( 'instanceCreated', this );
964
965 setupWidget( this, widgetDef );
966
967 this.init && this.init();
968
969 // Finally mark widget as inited.
970 this.inited = true;
971
972 setupWidgetData( this, startupData );
973
974 // If at some point (e.g. in #data listener) widget hasn't been destroyed
975 // and widget is already attached to document then fire #ready.
976 if ( this.isInited() && editor.editable().contains( this.wrapper ) ) {
977 this.ready = true;
978 this.fire( 'ready' );
979 }
980 }
981
982 Widget.prototype = {
983 /**
984 * Adds a class to the widget element. This method is used by
985 * the {@link #applyStyle} method and should be overridden by widgets
986 * which should handle classes differently (e.g. add them to other elements).
987 *
988 * Since 4.6.0 this method also adds a corresponding class prefixed with {@link #WRAPPER_CLASS_PREFIX}
989 * to the widget wrapper element.
990 *
991 * **Note**: This method should not be used directly. Use the {@link #setData} method to
992 * set the `classes` property. Read more in the {@link #setData} documentation.
993 *
994 * See also: {@link #removeClass}, {@link #hasClass}, {@link #getClasses}.
995 *
996 * @since 4.4
997 * @param {String} className The class name to be added.
998 */
999 addClass: function( className ) {
1000 this.element.addClass( className );
1001 this.wrapper.addClass( Widget.WRAPPER_CLASS_PREFIX + className );
1002 },
1003
1004 /**
1005 * Applies the specified style to the widget. It is highly recommended to use the
1006 * {@link CKEDITOR.editor#applyStyle} or {@link CKEDITOR.style#apply} methods instead of
1007 * using this method directly, because unlike editor's and style's methods, this one
1008 * does not perform any checks.
1009 *
1010 * By default this method handles only classes defined in the style. It clones existing
1011 * classes which are stored in the {@link #property-data widget data}'s `classes` property,
1012 * adds new classes, and calls the {@link #setData} method if at least one new class was added.
1013 * Then, using the {@link #event-data} event listener widget applies modifications passing
1014 * new classes to the {@link #addClass} method.
1015 *
1016 * If you need to handle classes differently than in the default way, you can override the
1017 * {@link #addClass} and related methods. You can also handle other style properties than `classes`
1018 * by overriding this method.
1019 *
1020 * See also: {@link #checkStyleActive}, {@link #removeStyle}.
1021 *
1022 * @since 4.4
1023 * @param {CKEDITOR.style} style The custom widget style to be applied.
1024 */
1025 applyStyle: function( style ) {
1026 applyRemoveStyle( this, style, 1 );
1027 },
1028
1029 /**
1030 * Checks if the specified style is applied to this widget. It is highly recommended to use the
1031 * {@link CKEDITOR.style#checkActive} method instead of using this method directly,
1032 * because unlike style's method, this one does not perform any checks.
1033 *
1034 * By default this method handles only classes defined in the style and passes
1035 * them to the {@link #hasClass} method. You can override these methods to handle classes
1036 * differently or to handle more of the style properties.
1037 *
1038 * See also: {@link #applyStyle}, {@link #removeStyle}.
1039 *
1040 * @since 4.4
1041 * @param {CKEDITOR.style} style The custom widget style to be checked.
1042 * @returns {Boolean} Whether the style is applied to this widget.
1043 */
1044 checkStyleActive: function( style ) {
1045 var classes = getStyleClasses( style ),
1046 cl;
1047
1048 if ( !classes )
1049 return false;
1050
1051 while ( ( cl = classes.pop() ) ) {
1052 if ( !this.hasClass( cl ) )
1053 return false;
1054 }
1055 return true;
1056 },
1057
1058 /**
1059 * Destroys this widget instance.
1060 *
1061 * Use {@link CKEDITOR.plugins.widget.repository#destroy} when possible instead of this method.
1062 *
1063 * This method fires the {#event-destroy} event.
1064 *
1065 * @param {Boolean} [offline] Whether a widget is offline (detached from the DOM tree) &mdash;
1066 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
1067 */
1068 destroy: function( offline ) {
1069 this.fire( 'destroy' );
1070
1071 if ( this.editables ) {
1072 for ( var name in this.editables )
1073 this.destroyEditable( name, offline );
1074 }
1075
1076 if ( !offline ) {
1077 if ( this.element.data( 'cke-widget-keep-attr' ) == '0' )
1078 this.element.removeAttribute( 'data-widget' );
1079 this.element.removeAttributes( [ 'data-cke-widget-data', 'data-cke-widget-keep-attr' ] );
1080 this.element.removeClass( 'cke_widget_element' );
1081 this.element.replace( this.wrapper );
1082 }
1083
1084 this.wrapper = null;
1085 },
1086
1087 /**
1088 * Destroys a nested editable and all nested widgets.
1089 *
1090 * @param {String} editableName Nested editable name.
1091 * @param {Boolean} [offline] See {@link #method-destroy} method.
1092 */
1093 destroyEditable: function( editableName, offline ) {
1094 var editable = this.editables[ editableName ];
1095
1096 editable.removeListener( 'focus', onEditableFocus );
1097 editable.removeListener( 'blur', onEditableBlur );
1098 this.editor.focusManager.remove( editable );
1099
1100 if ( !offline ) {
1101 this.repository.destroyAll( false, editable );
1102 editable.removeClass( 'cke_widget_editable' );
1103 editable.removeClass( 'cke_widget_editable_focused' );
1104 editable.removeAttributes( [ 'contenteditable', 'data-cke-widget-editable', 'data-cke-enter-mode' ] );
1105 }
1106
1107 delete this.editables[ editableName ];
1108 },
1109
1110 /**
1111 * Starts widget editing.
1112 *
1113 * This method fires the {@link CKEDITOR.plugins.widget#event-edit} event
1114 * which may be canceled in order to prevent it from opening a dialog window.
1115 *
1116 * The dialog window name is obtained from the event's data `dialog` property or
1117 * from {@link CKEDITOR.plugins.widget.definition#dialog}.
1118 *
1119 * @returns {Boolean} Returns `true` if a dialog window was opened.
1120 */
1121 edit: function() {
1122 var evtData = { dialog: this.dialog },
1123 that = this;
1124
1125 // Edit event was blocked or there's no dialog to be automatically opened.
1126 if ( this.fire( 'edit', evtData ) === false || !evtData.dialog )
1127 return false;
1128
1129 this.editor.openDialog( evtData.dialog, function( dialog ) {
1130 var showListener,
1131 okListener;
1132
1133 // Allow to add a custom dialog handler.
1134 if ( that.fire( 'dialog', dialog ) === false )
1135 return;
1136
1137 showListener = dialog.on( 'show', function() {
1138 dialog.setupContent( that );
1139 } );
1140
1141 okListener = dialog.on( 'ok', function() {
1142 // Commit dialog's fields, but prevent from
1143 // firing data event for every field. Fire only one,
1144 // bulk event at the end.
1145 var dataChanged,
1146 dataListener = that.on( 'data', function( evt ) {
1147 dataChanged = 1;
1148 evt.cancel();
1149 }, null, null, 0 );
1150
1151 // Create snapshot preceeding snapshot with changed widget...
1152 // TODO it should not be required, but it is and I found similar
1153 // code in dialog#ok listener in dialog/plugin.js.
1154 that.editor.fire( 'saveSnapshot' );
1155 dialog.commitContent( that );
1156
1157 dataListener.removeListener();
1158 if ( dataChanged ) {
1159 that.fire( 'data', that.data );
1160 that.editor.fire( 'saveSnapshot' );
1161 }
1162 } );
1163
1164 dialog.once( 'hide', function() {
1165 showListener.removeListener();
1166 okListener.removeListener();
1167 } );
1168 } );
1169
1170 return true;
1171 },
1172
1173 /**
1174 * Returns widget element classes parsed to an object. This method
1175 * is used to populate the `classes` property of widget's {@link #property-data}.
1176 *
1177 * This method reuses {@link CKEDITOR.plugins.widget.repository#parseElementClasses}.
1178 * It should be overriden if a widget should handle classes differently (e.g. on other elements).
1179 *
1180 * See also: {@link #removeClass}, {@link #addClass}, {@link #hasClass}.
1181 *
1182 * @since 4.4
1183 * @returns {Object}
1184 */
1185 getClasses: function() {
1186 return this.repository.parseElementClasses( this.element.getAttribute( 'class' ) );
1187 },
1188
1189 /**
1190 * Checks if the widget element has specified class. This method is used by
1191 * the {@link #checkStyleActive} method and should be overriden by widgets
1192 * which should handle classes differently (e.g. on other elements).
1193 *
1194 * See also: {@link #removeClass}, {@link #addClass}, {@link #getClasses}.
1195 *
1196 * @since 4.4
1197 * @param {String} className The class to be checked.
1198 * @param {Boolean} Whether a widget has specified class.
1199 */
1200 hasClass: function( className ) {
1201 return this.element.hasClass( className );
1202 },
1203
1204 /**
1205 * Initializes a nested editable.
1206 *
1207 * **Note**: Only elements from {@link CKEDITOR.dtd#$editable} may become editables.
1208 *
1209 * @param {String} editableName The nested editable name.
1210 * @param {CKEDITOR.plugins.widget.nestedEditable.definition} definition The definition of the nested editable.
1211 * @returns {Boolean} Whether an editable was successfully initialized.
1212 */
1213 initEditable: function( editableName, definition ) {
1214 // Don't fetch just first element which matched selector but look for a correct one. (http://dev.ckeditor.com/ticket/13334)
1215 var editable = this._findOneNotNested( definition.selector );
1216
1217 if ( editable && editable.is( CKEDITOR.dtd.$editable ) ) {
1218 editable = new NestedEditable( this.editor, editable, {
1219 filter: createEditableFilter.call( this.repository, this.name, editableName, definition )
1220 } );
1221 this.editables[ editableName ] = editable;
1222
1223 editable.setAttributes( {
1224 contenteditable: 'true',
1225 'data-cke-widget-editable': editableName,
1226 'data-cke-enter-mode': editable.enterMode
1227 } );
1228
1229 if ( editable.filter )
1230 editable.data( 'cke-filter', editable.filter.id );
1231
1232 editable.addClass( 'cke_widget_editable' );
1233 // This class may be left when d&ding widget which
1234 // had focused editable. Clean this class here, not in
1235 // cleanUpWidgetElement for performance and code size reasons.
1236 editable.removeClass( 'cke_widget_editable_focused' );
1237
1238 if ( definition.pathName )
1239 editable.data( 'cke-display-name', definition.pathName );
1240
1241 this.editor.focusManager.add( editable );
1242 editable.on( 'focus', onEditableFocus, this );
1243 CKEDITOR.env.ie && editable.on( 'blur', onEditableBlur, this );
1244
1245 // Finally, process editable's data. This data wasn't processed when loading
1246 // editor's data, becuase they need to be processed separately, with its own filters and settings.
1247 editable._.initialSetData = true;
1248 editable.setData( editable.getHtml() );
1249
1250 return true;
1251 }
1252
1253 return false;
1254 },
1255
1256 /**
1257 * Looks inside wrapper element to find a node that
1258 * matches given selector and is not nested in other widget. (http://dev.ckeditor.com/ticket/13334)
1259 *
1260 * @since 4.5
1261 * @private
1262 * @param {String} selector Selector to match.
1263 * @returns {CKEDITOR.dom.element} Matched element or `null` if a node has not been found.
1264 */
1265 _findOneNotNested: function( selector ) {
1266 var matchedElements = this.wrapper.find( selector ),
1267 match,
1268 closestWrapper;
1269
1270 for ( var i = 0; i < matchedElements.count(); i++ ) {
1271 match = matchedElements.getItem( i );
1272 closestWrapper = match.getAscendant( Widget.isDomWidgetWrapper );
1273
1274 // The closest ascendant-wrapper of this match defines to which widget
1275 // this match belongs. If the ascendant is this widget's wrapper
1276 // it means that the match is not nested in other widget.
1277 if ( this.wrapper.equals( closestWrapper ) ) {
1278 return match;
1279 }
1280 }
1281
1282 return null;
1283 },
1284
1285 /**
1286 * Checks if a widget has already been initialized and has not been destroyed yet.
1287 *
1288 * See {@link #inited} for more details.
1289 *
1290 * @returns {Boolean}
1291 */
1292 isInited: function() {
1293 return !!( this.wrapper && this.inited );
1294 },
1295
1296 /**
1297 * Checks if a widget is ready and has not been destroyed yet.
1298 *
1299 * See {@link #property-ready} for more details.
1300 *
1301 * @returns {Boolean}
1302 */
1303 isReady: function() {
1304 return this.isInited() && this.ready;
1305 },
1306
1307 /**
1308 * Focuses a widget by selecting it.
1309 */
1310 focus: function() {
1311 var sel = this.editor.getSelection();
1312
1313 // Fake the selection before focusing editor, to avoid unpreventable viewports scrolling
1314 // on Webkit/Blink/IE which is done because there's no selection or selection was somewhere else than widget.
1315 if ( sel ) {
1316 var isDirty = this.editor.checkDirty();
1317
1318 sel.fake( this.wrapper );
1319
1320 !isDirty && this.editor.resetDirty();
1321 }
1322
1323 // Always focus editor (not only when focusManger.hasFocus is false) (because of http://dev.ckeditor.com/ticket/10483).
1324 this.editor.focus();
1325 },
1326
1327 /**
1328 * Removes a class from the widget element. This method is used by
1329 * the {@link #removeStyle} method and should be overriden by widgets
1330 * which should handle classes differently (e.g. on other elements).
1331 *
1332 * **Note**: This method should not be used directly. Use the {@link #setData} method to
1333 * set the `classes` property. Read more in the {@link #setData} documentation.
1334 *
1335 * See also: {@link #hasClass}, {@link #addClass}.
1336 *
1337 * @since 4.4
1338 * @param {String} className The class to be removed.
1339 */
1340 removeClass: function( className ) {
1341 this.element.removeClass( className );
1342 this.wrapper.removeClass( Widget.WRAPPER_CLASS_PREFIX + className );
1343 },
1344
1345 /**
1346 * Removes the specified style from the widget. It is highly recommended to use the
1347 * {@link CKEDITOR.editor#removeStyle} or {@link CKEDITOR.style#remove} methods instead of
1348 * using this method directly, because unlike editor's and style's methods, this one
1349 * does not perform any checks.
1350 *
1351 * Read more about how applying/removing styles works in the {@link #applyStyle} method documentation.
1352 *
1353 * See also {@link #checkStyleActive}, {@link #applyStyle}, {@link #getClasses}.
1354 *
1355 * @since 4.4
1356 * @param {CKEDITOR.style} style The custom widget style to be removed.
1357 */
1358 removeStyle: function( style ) {
1359 applyRemoveStyle( this, style, 0 );
1360 },
1361
1362 /**
1363 * Sets widget value(s) in the {@link #property-data} object.
1364 * If the given value(s) modifies current ones, the {@link #event-data} event is fired.
1365 *
1366 * this.setData( 'align', 'left' );
1367 * this.data.align; // -> 'left'
1368 *
1369 * this.setData( { align: 'right', opened: false } );
1370 * this.data.align; // -> 'right'
1371 * this.data.opened; // -> false
1372 *
1373 * Set values are stored in {@link #element}'s attribute (`data-cke-widget-data`),
1374 * in a JSON string, therefore {@link #property-data} should contain
1375 * only serializable data.
1376 *
1377 * **Note:** A special data property, `classes`, exists. It contains an object with
1378 * classes which were returned by the {@link #getClasses} method during the widget initialization.
1379 * This property is then used by the {@link #applyStyle} and {@link #removeStyle} methods.
1380 * When it is changed (the reference to object must be changed!), the widget updates its classes by
1381 * using the {@link #addClass} and {@link #removeClass} methods.
1382 *
1383 * // Adding a new class.
1384 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1385 * classes.newClass = 1;
1386 * widget.setData( 'classes', classes );
1387 *
1388 * // Removing a class.
1389 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1390 * delete classes.newClass;
1391 * widget.setData( 'classes', classes );
1392 *
1393 * @param {String/Object} keyOrData
1394 * @param {Object} value
1395 * @chainable
1396 */
1397 setData: function( key, value ) {
1398 var data = this.data,
1399 modified = 0;
1400
1401 if ( typeof key == 'string' ) {
1402 if ( data[ key ] !== value ) {
1403 data[ key ] = value;
1404 modified = 1;
1405 }
1406 }
1407 else {
1408 var newData = key;
1409
1410 for ( key in newData ) {
1411 if ( data[ key ] !== newData[ key ] ) {
1412 modified = 1;
1413 data[ key ] = newData[ key ];
1414 }
1415 }
1416 }
1417
1418 // Block firing data event and overwriting data element before setupWidgetData is executed.
1419 if ( modified && this.dataReady ) {
1420 writeDataToElement( this );
1421 this.fire( 'data', data );
1422 }
1423
1424 return this;
1425 },
1426
1427 /**
1428 * Changes the widget's focus state. This method is executed automatically after
1429 * a widget was focused by the {@link #method-focus} method or the selection was moved
1430 * out of the widget.
1431 *
1432 * This is a low-level method which is not integrated with e.g. the undo manager.
1433 * Use the {@link #method-focus} method instead.
1434 *
1435 * @param {Boolean} selected Whether to select or deselect this widget.
1436 * @chainable
1437 */
1438 setFocused: function( focused ) {
1439 this.wrapper[ focused ? 'addClass' : 'removeClass' ]( 'cke_widget_focused' );
1440 this.fire( focused ? 'focus' : 'blur' );
1441 return this;
1442 },
1443
1444 /**
1445 * Changes the widget's select state. This method is executed automatically after
1446 * a widget was selected by the {@link #method-focus} method or the selection
1447 * was moved out of the widget.
1448 *
1449 * This is a low-level method which is not integrated with e.g. the undo manager.
1450 * Use the {@link #method-focus} method instead or simply change the selection.
1451 *
1452 * @param {Boolean} selected Whether to select or deselect this widget.
1453 * @chainable
1454 */
1455 setSelected: function( selected ) {
1456 this.wrapper[ selected ? 'addClass' : 'removeClass' ]( 'cke_widget_selected' );
1457 this.fire( selected ? 'select' : 'deselect' );
1458 return this;
1459 },
1460
1461 /**
1462 * Repositions drag handler according to the widget's element position. Should be called from events, like mouseover.
1463 */
1464 updateDragHandlerPosition: function() {
1465 var editor = this.editor,
1466 domElement = this.element.$,
1467 oldPos = this._.dragHandlerOffset,
1468 newPos = {
1469 x: domElement.offsetLeft,
1470 y: domElement.offsetTop - DRAG_HANDLER_SIZE
1471 };
1472
1473 if ( oldPos && newPos.x == oldPos.x && newPos.y == oldPos.y )
1474 return;
1475
1476 // We need to make sure that dirty state is not changed (http://dev.ckeditor.com/ticket/11487).
1477 var initialDirty = editor.checkDirty();
1478
1479 editor.fire( 'lockSnapshot' );
1480 this.dragHandlerContainer.setStyles( {
1481 top: newPos.y + 'px',
1482 left: newPos.x + 'px',
1483 display: 'block'
1484 } );
1485 editor.fire( 'unlockSnapshot' );
1486 !initialDirty && editor.resetDirty();
1487
1488 this._.dragHandlerOffset = newPos;
1489 }
1490 };
1491
1492 CKEDITOR.event.implementOn( Widget.prototype );
1493
1494 /**
1495 * Gets the {@link #isDomNestedEditable nested editable}
1496 * (returned as a {@link CKEDITOR.dom.element}, not as a {@link CKEDITOR.plugins.widget.nestedEditable})
1497 * closest to the `node` or the `node` if it is a nested editable itself.
1498 *
1499 * @since 4.5
1500 * @static
1501 * @param {CKEDITOR.dom.element} guard Stop ancestor search on this node (usually editor's editable).
1502 * @param {CKEDITOR.dom.node} node Start the search from this node.
1503 * @returns {CKEDITOR.dom.element/null} Element or `null` if not found.
1504 */
1505 Widget.getNestedEditable = function( guard, node ) {
1506 if ( !node || node.equals( guard ) )
1507 return null;
1508
1509 if ( Widget.isDomNestedEditable( node ) )
1510 return node;
1511
1512 return Widget.getNestedEditable( guard, node.getParent() );
1513 };
1514
1515 /**
1516 * Checks whether the `node` is a widget's drag handle element.
1517 *
1518 * @since 4.5
1519 * @static
1520 * @param {CKEDITOR.dom.node} node
1521 * @returns {Boolean}
1522 */
1523 Widget.isDomDragHandler = function( node ) {
1524 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-drag-handler' );
1525 };
1526
1527 /**
1528 * Checks whether the `node` is a container of the widget's drag handle element.
1529 *
1530 * @since 4.5
1531 * @static
1532 * @param {CKEDITOR.dom.node} node
1533 * @returns {Boolean}
1534 */
1535 Widget.isDomDragHandlerContainer = function( node ) {
1536 return node.type == CKEDITOR.NODE_ELEMENT && node.hasClass( 'cke_widget_drag_handler_container' );
1537 };
1538
1539 /**
1540 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#editables nested editable}.
1541 * Note that this function only checks whether it is the right element, not whether
1542 * the passed `node` is an instance of {@link CKEDITOR.plugins.widget.nestedEditable}.
1543 *
1544 * @since 4.5
1545 * @static
1546 * @param {CKEDITOR.dom.node} node
1547 * @returns {Boolean}
1548 */
1549 Widget.isDomNestedEditable = function( node ) {
1550 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-editable' );
1551 };
1552
1553 /**
1554 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1555 *
1556 * @since 4.5
1557 * @static
1558 * @param {CKEDITOR.dom.node} node
1559 * @returns {Boolean}
1560 */
1561 Widget.isDomWidgetElement = function( node ) {
1562 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-widget' );
1563 };
1564
1565 /**
1566 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1567 *
1568 * @since 4.5
1569 * @static
1570 * @param {CKEDITOR.dom.element} node
1571 * @returns {Boolean}
1572 */
1573 Widget.isDomWidgetWrapper = function( node ) {
1574 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-wrapper' );
1575 };
1576
1577 /**
1578 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1579 *
1580 * @since 4.5
1581 * @static
1582 * @param {CKEDITOR.htmlParser.node} node
1583 * @returns {Boolean}
1584 */
1585 Widget.isParserWidgetElement = function( node ) {
1586 return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-widget' ];
1587 };
1588
1589 /**
1590 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1591 *
1592 * @since 4.5
1593 * @static
1594 * @param {CKEDITOR.htmlParser.element} node
1595 * @returns {Boolean}
1596 */
1597 Widget.isParserWidgetWrapper = function( node ) {
1598 return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-cke-widget-wrapper' ];
1599 };
1600
1601 /**
1602 * Prefix added to wrapper classes. Each class added to the widget element by the {@link #addClass}
1603 * method will also be added to the wrapper prefixed with it.
1604 *
1605 * @since 4.6.0
1606 * @static
1607 * @readonly
1608 * @property {String} [='cke_widget_wrapper_']
1609 */
1610 Widget.WRAPPER_CLASS_PREFIX = 'cke_widget_wrapper_';
1611
1612 /**
1613 * An event fired when a widget is ready (fully initialized). This event is fired after:
1614 *
1615 * * {@link #init} is called,
1616 * * The first {@link #event-data} event is fired,
1617 * * A widget is attached to the document.
1618 *
1619 * Therefore, in case of widget creation with a command which opens a dialog window, this event
1620 * will be delayed after the dialog window is closed and the widget is finally inserted into the document.
1621 *
1622 * **Note**: If your widget does not use automatic dialog window binding (i.e. you open the dialog window manually)
1623 * or another situation in which the widget wrapper is not attached to document at the time when it is
1624 * initialized occurs, you need to take care of firing {@link #event-ready} yourself.
1625 *
1626 * See also {@link #property-ready} and {@link #property-inited} properties, and
1627 * {@link #isReady} and {@link #isInited} methods.
1628 *
1629 * @event ready
1630 */
1631
1632 /**
1633 * An event fired when a widget is about to be destroyed, but before it is
1634 * fully torn down.
1635 *
1636 * @event destroy
1637 */
1638
1639 /**
1640 * An event fired when a widget is focused.
1641 *
1642 * Widget can be focused by executing {@link #method-focus}.
1643 *
1644 * @event focus
1645 */
1646
1647 /**
1648 * An event fired when a widget is blurred.
1649 *
1650 * @event blur
1651 */
1652
1653 /**
1654 * An event fired when a widget is selected.
1655 *
1656 * @event select
1657 */
1658
1659 /**
1660 * An event fired when a widget is deselected.
1661 *
1662 * @event deselect
1663 */
1664
1665 /**
1666 * An event fired by the {@link #method-edit} method. It can be canceled
1667 * in order to stop the default action (opening a dialog window and/or
1668 * {@link CKEDITOR.plugins.widget.repository#finalizeCreation finalizing widget creation}).
1669 *
1670 * @event edit
1671 * @param data
1672 * @param {String} data.dialog Defaults to {@link CKEDITOR.plugins.widget.definition#dialog}
1673 * and can be changed or set by the listener.
1674 */
1675
1676 /**
1677 * An event fired when a dialog window for widget editing is opened.
1678 * This event can be canceled in order to handle the editing dialog in a custom manner.
1679 *
1680 * @event dialog
1681 * @param {CKEDITOR.dialog} data The opened dialog window instance.
1682 */
1683
1684 /**
1685 * An event fired when a key is pressed on a focused widget.
1686 * This event is forwarded from the {@link CKEDITOR.editor#key} event and
1687 * has the ability to block editor keystrokes if it is canceled.
1688 *
1689 * @event key
1690 * @param data
1691 * @param {Number} data.keyCode A number representing the key code (or combination).
1692 */
1693
1694 /**
1695 * An event fired when a widget is double clicked.
1696 *
1697 * **Note:** If a default editing action is executed on double click (i.e. a widget has a
1698 * {@link CKEDITOR.plugins.widget.definition#dialog dialog} defined and the {@link #event-doubleclick} event was not
1699 * canceled), this event will be automatically canceled, so a listener added with the default priority (10)
1700 * will not be executed. Use a listener with low priority (e.g. 5) to be sure that it will be executed.
1701 *
1702 * widget.on( 'doubleclick', function( evt ) {
1703 * console.log( 'widget#doubleclick' );
1704 * }, null, null, 5 );
1705 *
1706 * If your widget handles double click in a special way (so the default editing action is not executed),
1707 * make sure you cancel this event, because otherwise it will be propagated to {@link CKEDITOR.editor#doubleclick}
1708 * and another feature may step in (e.g. a Link dialog window may be opened if your widget was inside a link).
1709 *
1710 * @event doubleclick
1711 * @param data
1712 * @param {CKEDITOR.dom.element} data.element The double-clicked element.
1713 */
1714
1715 /**
1716 * An event fired when the context menu is opened for a widget.
1717 *
1718 * @event contextMenu
1719 * @param data The object containing context menu options to be added
1720 * for this widget. See {@link CKEDITOR.plugins.contextMenu#addListener}.
1721 */
1722
1723 /**
1724 * An event fired when the widget data changed. See the {@link #setData} method and the {@link #property-data} property.
1725 *
1726 * @event data
1727 */
1728
1729
1730
1731 /**
1732 * The wrapper class for editable elements inside widgets.
1733 *
1734 * Do not use directly. Use {@link CKEDITOR.plugins.widget.definition#editables} or
1735 * {@link CKEDITOR.plugins.widget#initEditable}.
1736 *
1737 * @class CKEDITOR.plugins.widget.nestedEditable
1738 * @extends CKEDITOR.dom.element
1739 * @constructor
1740 * @param {CKEDITOR.editor} editor
1741 * @param {CKEDITOR.dom.element} element
1742 * @param config
1743 * @param {CKEDITOR.filter} [config.filter]
1744 */
1745 function NestedEditable( editor, element, config ) {
1746 // Call the base constructor.
1747 CKEDITOR.dom.element.call( this, element.$ );
1748 this.editor = editor;
1749 this._ = {};
1750 var filter = this.filter = config.filter;
1751
1752 // If blockless editable - always use BR mode.
1753 if ( !CKEDITOR.dtd[ this.getName() ].p )
1754 this.enterMode = this.shiftEnterMode = CKEDITOR.ENTER_BR;
1755 else {
1756 this.enterMode = filter ? filter.getAllowedEnterMode( editor.enterMode ) : editor.enterMode;
1757 this.shiftEnterMode = filter ? filter.getAllowedEnterMode( editor.shiftEnterMode, true ) : editor.shiftEnterMode;
1758 }
1759 }
1760
1761 NestedEditable.prototype = CKEDITOR.tools.extend( CKEDITOR.tools.prototypedCopy( CKEDITOR.dom.element.prototype ), {
1762 /**
1763 * Sets the editable data. The data will be passed through the {@link CKEDITOR.editor#dataProcessor}
1764 * and the {@link CKEDITOR.editor#filter}. This ensures that the data was filtered and prepared to be
1765 * edited like the {@link CKEDITOR.editor#method-setData editor data}.
1766 *
1767 * Before content is changed, all nested widgets are destroyed. Afterwards, after new content is loaded,
1768 * all nested widgets are initialized.
1769 *
1770 * @param {String} data
1771 */
1772 setData: function( data ) {
1773 // For performance reasons don't call destroyAll when initializing a nested editable,
1774 // because there are no widgets inside.
1775 if ( !this._.initialSetData ) {
1776 // Destroy all nested widgets before setting data.
1777 this.editor.widgets.destroyAll( false, this );
1778 }
1779 this._.initialSetData = false;
1780
1781 data = this.editor.dataProcessor.toHtml( data, {
1782 context: this.getName(),
1783 filter: this.filter,
1784 enterMode: this.enterMode
1785 } );
1786 this.setHtml( data );
1787
1788 this.editor.widgets.initOnAll( this );
1789 },
1790
1791 /**
1792 * Gets the editable data. Like {@link #setData}, this method will process and filter the data.
1793 *
1794 * @returns {String}
1795 */
1796 getData: function() {
1797 return this.editor.dataProcessor.toDataFormat( this.getHtml(), {
1798 context: this.getName(),
1799 filter: this.filter,
1800 enterMode: this.enterMode
1801 } );
1802 }
1803 } );
1804
1805 /**
1806 * The editor instance.
1807 *
1808 * @readonly
1809 * @property {CKEDITOR.editor} editor
1810 */
1811
1812 /**
1813 * The filter instance if allowed content rules were defined.
1814 *
1815 * @readonly
1816 * @property {CKEDITOR.filter} filter
1817 */
1818
1819 /**
1820 * The enter mode active in this editable.
1821 * It is determined from editable's name (whether it is a blockless editable),
1822 * its allowed content rules (if defined) and the default editor's mode.
1823 *
1824 * @readonly
1825 * @property {Number} enterMode
1826 */
1827
1828 /**
1829 * The shift enter move active in this editable.
1830 *
1831 * @readonly
1832 * @property {Number} shiftEnterMode
1833 */
1834
1835
1836 //
1837 // REPOSITORY helpers -----------------------------------------------------
1838 //
1839
1840 function addWidgetButtons( editor ) {
1841 var widgets = editor.widgets.registered,
1842 widget,
1843 widgetName,
1844 widgetButton;
1845
1846 for ( widgetName in widgets ) {
1847 widget = widgets[ widgetName ];
1848
1849 // Create button if defined.
1850 widgetButton = widget.button;
1851 if ( widgetButton && editor.ui.addButton ) {
1852 editor.ui.addButton( CKEDITOR.tools.capitalize( widget.name, true ), {
1853 label: widgetButton,
1854 command: widget.name,
1855 toolbar: 'insert,10'
1856 } );
1857 }
1858 }
1859 }
1860
1861 // Create a command creating and editing widget.
1862 //
1863 // @param editor
1864 // @param {CKEDITOR.plugins.widget.definition} widgetDef
1865 function addWidgetCommand( editor, widgetDef ) {
1866 editor.addCommand( widgetDef.name, {
1867 exec: function( editor, commandData ) {
1868 var focused = editor.widgets.focused;
1869 // If a widget of the same type is focused, start editing.
1870 if ( focused && focused.name == widgetDef.name )
1871 focused.edit();
1872 // Otherwise...
1873 // ... use insert method is was defined.
1874 else if ( widgetDef.insert )
1875 widgetDef.insert();
1876 // ... or create a brand-new widget from template.
1877 else if ( widgetDef.template ) {
1878 var defaults = typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults,
1879 element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( defaults ) ),
1880 instance,
1881 wrapper = editor.widgets.wrapElement( element, widgetDef.name ),
1882 temp = new CKEDITOR.dom.documentFragment( wrapper.getDocument() );
1883
1884 // Append wrapper to a temporary document. This will unify the environment
1885 // in which #data listeners work when creating and editing widget.
1886 temp.append( wrapper );
1887 instance = editor.widgets.initOn( element, widgetDef, commandData && commandData.startupData );
1888
1889 // Instance could be destroyed during initialization.
1890 // In this case finalize creation if some new widget
1891 // was left in temporary document fragment.
1892 if ( !instance ) {
1893 finalizeCreation();
1894 return;
1895 }
1896
1897 // Listen on edit to finalize widget insertion.
1898 //
1899 // * If dialog was set, then insert widget after dialog was successfully saved or destroy this
1900 // temporary instance.
1901 // * If dialog wasn't set and edit wasn't canceled, insert widget.
1902 var editListener = instance.once( 'edit', function( evt ) {
1903 if ( evt.data.dialog ) {
1904 instance.once( 'dialog', function( evt ) {
1905 var dialog = evt.data,
1906 okListener,
1907 cancelListener;
1908
1909 // Finalize creation AFTER (20) new data was set.
1910 okListener = dialog.once( 'ok', finalizeCreation, null, null, 20 );
1911
1912 cancelListener = dialog.once( 'cancel', function( evt ) {
1913 if ( !( evt.data && evt.data.hide === false ) ) {
1914 editor.widgets.destroy( instance, true );
1915 }
1916 } );
1917
1918 dialog.once( 'hide', function() {
1919 okListener.removeListener();
1920 cancelListener.removeListener();
1921 } );
1922 } );
1923 } else {
1924 // Dialog hasn't been set, so insert widget now.
1925 finalizeCreation();
1926 }
1927 }, null, null, 999 );
1928
1929 instance.edit();
1930
1931 // Remove listener in case someone canceled it before this
1932 // listener was executed.
1933 editListener.removeListener();
1934 }
1935
1936 function finalizeCreation() {
1937 editor.widgets.finalizeCreation( temp );
1938 }
1939 },
1940
1941 allowedContent: widgetDef.allowedContent,
1942 requiredContent: widgetDef.requiredContent,
1943 contentForms: widgetDef.contentForms,
1944 contentTransformations: widgetDef.contentTransformations
1945 } );
1946 }
1947
1948 function addWidgetProcessors( widgetsRepo, widgetDef ) {
1949 var upcast = widgetDef.upcast,
1950 upcasts,
1951 priority = widgetDef.upcastPriority || 10;
1952
1953 if ( !upcast )
1954 return;
1955
1956 // Multiple upcasts defined in string.
1957 if ( typeof upcast == 'string' ) {
1958 upcasts = upcast.split( ',' );
1959 while ( upcasts.length ) {
1960 addUpcast( widgetDef.upcasts[ upcasts.pop() ], widgetDef.name, priority );
1961 }
1962 }
1963 // Single rule which is automatically activated.
1964 else {
1965 addUpcast( upcast, widgetDef.name, priority );
1966 }
1967
1968 function addUpcast( upcast, name, priority ) {
1969 // Find index of the first higher (in terms of value) priority upcast.
1970 var index = CKEDITOR.tools.getIndex( widgetsRepo._.upcasts, function( element ) {
1971 return element[ 2 ] > priority;
1972 } );
1973 // Add at the end if it is the highest priority so far.
1974 if ( index < 0 ) {
1975 index = widgetsRepo._.upcasts.length;
1976 }
1977
1978 widgetsRepo._.upcasts.splice( index, 0, [ upcast, name, priority ] );
1979 }
1980 }
1981
1982 function blurWidget( widgetsRepo, widget ) {
1983 widgetsRepo.focused = null;
1984
1985 if ( widget.isInited() ) {
1986 var isDirty = widget.editor.checkDirty();
1987
1988 // Widget could be destroyed in the meantime - e.g. data could be set.
1989 widgetsRepo.fire( 'widgetBlurred', { widget: widget } );
1990 widget.setFocused( false );
1991
1992 !isDirty && widget.editor.resetDirty();
1993 }
1994 }
1995
1996 function checkWidgets( evt ) {
1997 var options = evt.data;
1998
1999 if ( this.editor.mode != 'wysiwyg' )
2000 return;
2001
2002 var editable = this.editor.editable(),
2003 instances = this.instances,
2004 newInstances, i, count, wrapper, notYetInitialized;
2005
2006 if ( !editable )
2007 return;
2008
2009 // Remove widgets which have no corresponding elements in DOM.
2010 for ( i in instances ) {
2011 // http://dev.ckeditor.com/ticket/13410 Remove widgets that are ready. This prevents from destroying widgets that are during loading process.
2012 if ( instances[ i ].isReady() && !editable.contains( instances[ i ].wrapper ) )
2013 this.destroy( instances[ i ], true );
2014 }
2015
2016 // Init on all (new) if initOnlyNew option was passed.
2017 if ( options && options.initOnlyNew )
2018 newInstances = this.initOnAll();
2019 else {
2020 var wrappers = editable.find( '.cke_widget_wrapper' );
2021 newInstances = [];
2022
2023 // Create widgets on existing wrappers if they do not exists.
2024 for ( i = 0, count = wrappers.count(); i < count; i++ ) {
2025 wrapper = wrappers.getItem( i );
2026 notYetInitialized = !this.getByElement( wrapper, true );
2027
2028 // Check if:
2029 // * there's no instance for this widget
2030 // * wrapper is not inside some temporary element like copybin (http://dev.ckeditor.com/ticket/11088)
2031 // * it was a nested widget's wrapper which has been detached from DOM,
2032 // when nested editable has been initialized (it overwrites its innerHTML
2033 // and initializes nested widgets).
2034 if ( notYetInitialized && !findParent( wrapper, isDomTemp ) && editable.contains( wrapper ) ) {
2035 // Add cke_widget_new class because otherwise
2036 // widget will not be created on such wrapper.
2037 wrapper.addClass( 'cke_widget_new' );
2038 newInstances.push( this.initOn( wrapper.getFirst( Widget.isDomWidgetElement ) ) );
2039 }
2040 }
2041 }
2042
2043 // If only single widget was initialized and focusInited was passed, focus it.
2044 if ( options && options.focusInited && newInstances.length == 1 )
2045 newInstances[ 0 ].focus();
2046 }
2047
2048 // Unwraps widget element and clean up element.
2049 //
2050 // This function is used to clean up pasted widgets.
2051 // It should have similar result to widget#destroy plus
2052 // some additional adjustments, specific for pasting.
2053 //
2054 // @param {CKEDITOR.htmlParser.element} el
2055 function cleanUpWidgetElement( el ) {
2056 var parent = el.parent;
2057 if ( parent.type == CKEDITOR.NODE_ELEMENT && parent.attributes[ 'data-cke-widget-wrapper' ] )
2058 parent.replaceWith( el );
2059 }
2060
2061 // Similar to cleanUpWidgetElement, but works on DOM and finds
2062 // widget elements by its own.
2063 //
2064 // Unlike cleanUpWidgetElement it will wrap element back.
2065 //
2066 // @param {CKEDITOR.dom.element} container
2067 function cleanUpAllWidgetElements( widgetsRepo, container ) {
2068 var wrappers = container.find( '.cke_widget_wrapper' ),
2069 wrapper, element,
2070 i = 0,
2071 l = wrappers.count();
2072
2073 for ( ; i < l; ++i ) {
2074 wrapper = wrappers.getItem( i );
2075 element = wrapper.getFirst( Widget.isDomWidgetElement );
2076 // If wrapper contains widget element - unwrap it and wrap again.
2077 if ( element.type == CKEDITOR.NODE_ELEMENT && element.data( 'widget' ) ) {
2078 element.replace( wrapper );
2079 widgetsRepo.wrapElement( element );
2080 } else {
2081 // Otherwise - something is wrong... clean this up.
2082 wrapper.remove();
2083 }
2084 }
2085 }
2086
2087 // Creates {@link CKEDITOR.filter} instance for given widget, editable and rules.
2088 //
2089 // Once filter for widget-editable pair is created it is cached, so the same instance
2090 // will be returned when method is executed again.
2091 //
2092 // @param {String} widgetName
2093 // @param {String} editableName
2094 // @param {CKEDITOR.plugins.widget.nestedEditableDefinition} editableDefinition The nested editable definition.
2095 // @returns {CKEDITOR.filter} Filter instance or `null` if rules are not defined.
2096 // @context CKEDITOR.plugins.widget.repository
2097 function createEditableFilter( widgetName, editableName, editableDefinition ) {
2098 if ( !editableDefinition.allowedContent && !editableDefinition.disallowedContent )
2099 return null;
2100
2101 var editables = this._.filters[ widgetName ];
2102
2103 if ( !editables )
2104 this._.filters[ widgetName ] = editables = {};
2105
2106 var filter = editables[ editableName ];
2107
2108 if ( !filter ) {
2109 filter = editableDefinition.allowedContent ? new CKEDITOR.filter( editableDefinition.allowedContent ) : this.editor.filter.clone();
2110
2111 editables[ editableName ] = filter;
2112
2113 if ( editableDefinition.disallowedContent ) {
2114 filter.disallow( editableDefinition.disallowedContent );
2115 }
2116 }
2117
2118 return filter;
2119 }
2120
2121 // Creates an iterator function which when executed on all
2122 // elements in DOM tree will gather elements that should be wrapped
2123 // and initialized as widgets.
2124 function createUpcastIterator( widgetsRepo ) {
2125 var toBeWrapped = [],
2126 upcasts = widgetsRepo._.upcasts,
2127 upcastCallbacks = widgetsRepo._.upcastCallbacks;
2128
2129 return {
2130 toBeWrapped: toBeWrapped,
2131
2132 iterator: function( element ) {
2133 var upcast, upcasted,
2134 data,
2135 i,
2136 upcastsLength,
2137 upcastCallbacksLength;
2138
2139 // Wrapper found - find widget element, add it to be
2140 // cleaned up (unwrapped) and wrapped and stop iterating in this branch.
2141 if ( 'data-cke-widget-wrapper' in element.attributes ) {
2142 element = element.getFirst( Widget.isParserWidgetElement );
2143
2144 if ( element )
2145 toBeWrapped.push( [ element ] );
2146
2147 // Do not iterate over descendants.
2148 return false;
2149 }
2150 // Widget element found - add it to be cleaned up (just in case)
2151 // and wrapped and stop iterating in this branch.
2152 else if ( 'data-widget' in element.attributes ) {
2153 toBeWrapped.push( [ element ] );
2154
2155 // Do not iterate over descendants.
2156 return false;
2157 }
2158 else if ( ( upcastsLength = upcasts.length ) ) {
2159 // Ignore elements with data-cke-widget-upcasted to avoid multiple upcasts (http://dev.ckeditor.com/ticket/11533).
2160 // Do not iterate over descendants.
2161 if ( element.attributes[ 'data-cke-widget-upcasted' ] )
2162 return false;
2163
2164 // Check element with upcast callbacks first.
2165 // If any of them return false abort upcasting.
2166 for ( i = 0, upcastCallbacksLength = upcastCallbacks.length; i < upcastCallbacksLength; ++i ) {
2167 if ( upcastCallbacks[ i ]( element ) === false )
2168 return;
2169 // Return nothing in order to continue iterating over ascendants.
2170 // See http://dev.ckeditor.com/ticket/11186#comment:6
2171 }
2172
2173 for ( i = 0; i < upcastsLength; ++i ) {
2174 upcast = upcasts[ i ];
2175 data = {};
2176
2177 if ( ( upcasted = upcast[ 0 ]( element, data ) ) ) {
2178 // If upcast function returned element, upcast this one.
2179 // It can be e.g. a new element wrapping the original one.
2180 if ( upcasted instanceof CKEDITOR.htmlParser.element )
2181 element = upcasted;
2182
2183 // Set initial data attr with data from upcast method.
2184 element.attributes[ 'data-cke-widget-data' ] = encodeURIComponent( JSON.stringify( data ) );
2185 element.attributes[ 'data-cke-widget-upcasted' ] = 1;
2186
2187 toBeWrapped.push( [ element, upcast[ 1 ] ] );
2188
2189 // Do not iterate over descendants.
2190 return false;
2191 }
2192 }
2193 }
2194 }
2195 };
2196 }
2197
2198 // Finds a first parent that matches query.
2199 //
2200 // @param {CKEDITOR.dom.element} element
2201 // @param {Function} query
2202 function findParent( element, query ) {
2203 var parent = element;
2204
2205 while ( ( parent = parent.getParent() ) ) {
2206 if ( query( parent ) )
2207 return true;
2208 }
2209 return false;
2210 }
2211
2212 function getWrapperAttributes( inlineWidget, name ) {
2213 return {
2214 // tabindex="-1" means that it can receive focus by code.
2215 tabindex: -1,
2216 contenteditable: 'false',
2217 'data-cke-widget-wrapper': 1,
2218 'data-cke-filter': 'off',
2219 // Class cke_widget_new marks widgets which haven't been initialized yet.
2220 'class': 'cke_widget_wrapper cke_widget_new cke_widget_' +
2221 ( inlineWidget ? 'inline' : 'block' ) +
2222 ( name ? ' cke_widget_' + name : '' )
2223 };
2224 }
2225
2226 // Inserts element at given index.
2227 // It will check DTD and split ancestor elements up to the first
2228 // that can contain this element.
2229 //
2230 // @param {CKEDITOR.htmlParser.element} parent
2231 // @param {Number} index
2232 // @param {CKEDITOR.htmlParser.element} element
2233 function insertElement( parent, index, element ) {
2234 // Do not split doc fragment...
2235 if ( parent.type == CKEDITOR.NODE_ELEMENT ) {
2236 var parentAllows = CKEDITOR.dtd[ parent.name ];
2237 // Parent element is known (included in DTD) and cannot contain
2238 // this element.
2239 if ( parentAllows && !parentAllows[ element.name ] ) {
2240 var parent2 = parent.split( index ),
2241 parentParent = parent.parent;
2242
2243 // Element will now be inserted at right parent's index.
2244 index = parent2.getIndex();
2245
2246 // If left part of split is empty - remove it.
2247 if ( !parent.children.length ) {
2248 index -= 1;
2249 parent.remove();
2250 }
2251
2252 // If right part of split is empty - remove it.
2253 if ( !parent2.children.length )
2254 parent2.remove();
2255
2256 // Try inserting as grandpas' children.
2257 return insertElement( parentParent, index, element );
2258 }
2259 }
2260
2261 // Finally we can add this element.
2262 parent.add( element, index );
2263 }
2264
2265 // Checks whether for the given widget definition and element widget should be created in inline or block mode.
2266 //
2267 // See also: {@link CKEDITOR.plugins.widget.definition#inline} and {@link CKEDITOR.plugins.widget#element}.
2268 //
2269 // @param {CKEDITOR.plugins.widget.definition} widgetDef The widget definition.
2270 // @param {String} elementName The name of the widget element.
2271 // @returns {Boolean}
2272 function isWidgetInline( widgetDef, elementName ) {
2273 return typeof widgetDef.inline == 'boolean' ? widgetDef.inline : !!CKEDITOR.dtd.$inline[ elementName ];
2274 }
2275
2276 // @param {CKEDITOR.dom.element}
2277 // @returns {Boolean}
2278 function isDomTemp( element ) {
2279 return element.hasAttribute( 'data-cke-temp' );
2280 }
2281
2282 function onEditableKey( widget, keyCode ) {
2283 var focusedEditable = widget.focusedEditable,
2284 range;
2285
2286 // CTRL+A.
2287 if ( keyCode == CKEDITOR.CTRL + 65 ) {
2288 var bogus = focusedEditable.getBogus();
2289
2290 range = widget.editor.createRange();
2291 range.selectNodeContents( focusedEditable );
2292 // Exclude bogus if exists.
2293 if ( bogus )
2294 range.setEndAt( bogus, CKEDITOR.POSITION_BEFORE_START );
2295
2296 range.select();
2297 // Cancel event - block default.
2298 return false;
2299 }
2300 // DEL or BACKSPACE.
2301 else if ( keyCode == 8 || keyCode == 46 ) {
2302 var ranges = widget.editor.getSelection().getRanges();
2303
2304 range = ranges[ 0 ];
2305
2306 // Block del or backspace if at editable's boundary.
2307 return !( ranges.length == 1 && range.collapsed &&
2308 range.checkBoundaryOfElement( focusedEditable, CKEDITOR[ keyCode == 8 ? 'START' : 'END' ] ) );
2309 }
2310 }
2311
2312 function setFocusedEditable( widgetsRepo, widget, editableElement, offline ) {
2313 var editor = widgetsRepo.editor;
2314
2315 editor.fire( 'lockSnapshot' );
2316
2317 if ( editableElement ) {
2318 var editableName = editableElement.data( 'cke-widget-editable' ),
2319 editableInstance = widget.editables[ editableName ];
2320
2321 widgetsRepo.widgetHoldingFocusedEditable = widget;
2322 widget.focusedEditable = editableInstance;
2323 editableElement.addClass( 'cke_widget_editable_focused' );
2324
2325 if ( editableInstance.filter )
2326 editor.setActiveFilter( editableInstance.filter );
2327 editor.setActiveEnterMode( editableInstance.enterMode, editableInstance.shiftEnterMode );
2328 } else {
2329 if ( !offline )
2330 widget.focusedEditable.removeClass( 'cke_widget_editable_focused' );
2331
2332 widget.focusedEditable = null;
2333 widgetsRepo.widgetHoldingFocusedEditable = null;
2334 editor.setActiveFilter( null );
2335 editor.setActiveEnterMode( null, null );
2336 }
2337
2338 editor.fire( 'unlockSnapshot' );
2339 }
2340
2341 function setupContextMenu( editor ) {
2342 if ( !editor.contextMenu )
2343 return;
2344
2345 editor.contextMenu.addListener( function( element ) {
2346 var widget = editor.widgets.getByElement( element, true );
2347
2348 if ( widget )
2349 return widget.fire( 'contextMenu', {} );
2350 } );
2351 }
2352
2353 // And now we've got two problems - original problem and RegExp.
2354 // Some softeners:
2355 // * FF tends to copy all blocks up to the copybin container.
2356 // * IE tends to copy only the copybin, without its container.
2357 // * We use spans on IE and blockless editors, but divs in other cases.
2358 var pasteReplaceRegex = new RegExp(
2359 '^' +
2360 '(?:<(?:div|span)(?: data-cke-temp="1")?(?: id="cke_copybin")?(?: data-cke-temp="1")?>)?' +
2361 '(?:<(?:div|span)(?: style="[^"]+")?>)?' +
2362 '<span [^>]*data-cke-copybin-start="1"[^>]*>.?</span>([\\s\\S]+)<span [^>]*data-cke-copybin-end="1"[^>]*>.?</span>' +
2363 '(?:</(?:div|span)>)?' +
2364 '(?:</(?:div|span)>)?' +
2365 '$',
2366 // IE8 prefers uppercase when browsers stick to lowercase HTML (http://dev.ckeditor.com/ticket/13460).
2367 'i'
2368 );
2369
2370 function pasteReplaceFn( match, wrapperHtml ) {
2371 // Avoid polluting pasted data with any whitspaces,
2372 // what's going to break check whether only one widget was pasted.
2373 return CKEDITOR.tools.trim( wrapperHtml );
2374 }
2375
2376 function setupDragAndDrop( widgetsRepo ) {
2377 var editor = widgetsRepo.editor,
2378 lineutils = CKEDITOR.plugins.lineutils;
2379
2380 // These listeners handle inline and block widgets drag and drop.
2381 // The only thing we need to do to make block widgets custom drag and drop functionality
2382 // is to fire those events with the right properties (like the target which must be the drag handle).
2383 editor.on( 'dragstart', function( evt ) {
2384 var target = evt.data.target;
2385
2386 if ( Widget.isDomDragHandler( target ) ) {
2387 var widget = widgetsRepo.getByElement( target );
2388
2389 evt.data.dataTransfer.setData( 'cke/widget-id', widget.id );
2390
2391 // IE needs focus.
2392 editor.focus();
2393
2394 // and widget need to be focused on drag start (http://dev.ckeditor.com/ticket/12172#comment:10).
2395 widget.focus();
2396 }
2397 } );
2398
2399 editor.on( 'drop', function( evt ) {
2400 var dataTransfer = evt.data.dataTransfer,
2401 id = dataTransfer.getData( 'cke/widget-id' ),
2402 transferType = dataTransfer.getTransferType( editor ),
2403 dragRange = editor.createRange(),
2404 sourceWidget;
2405
2406 // Disable cross-editor drag & drop for widgets - http://dev.ckeditor.com/ticket/13599.
2407 if ( id !== '' && transferType === CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
2408 evt.cancel();
2409 return;
2410 }
2411
2412 if ( id === '' || transferType != CKEDITOR.DATA_TRANSFER_INTERNAL ) {
2413 return;
2414 }
2415
2416 sourceWidget = widgetsRepo.instances[ id ];
2417 if ( !sourceWidget ) {
2418 return;
2419 }
2420
2421 dragRange.setStartBefore( sourceWidget.wrapper );
2422 dragRange.setEndAfter( sourceWidget.wrapper );
2423 evt.data.dragRange = dragRange;
2424
2425 // [IE8-9] Reset state of the clipboard#fixSplitNodesAfterDrop fix because by setting evt.data.dragRange
2426 // (see above) after drop happened we do not need it. That fix is needed only if dragRange was created
2427 // before drop (before text node was split).
2428 delete CKEDITOR.plugins.clipboard.dragStartContainerChildCount;
2429 delete CKEDITOR.plugins.clipboard.dragEndContainerChildCount;
2430
2431 evt.data.dataTransfer.setData( 'text/html', editor.editable().getHtmlFromRange( dragRange ).getHtml() );
2432 editor.widgets.destroy( sourceWidget, true );
2433 } );
2434
2435 editor.on( 'contentDom', function() {
2436 var editable = editor.editable();
2437
2438 // Register Lineutils's utilities as properties of repo.
2439 CKEDITOR.tools.extend( widgetsRepo, {
2440 finder: new lineutils.finder( editor, {
2441 lookups: {
2442 // Element is block but not list item and not in nested editable.
2443 'default': function( el ) {
2444 if ( el.is( CKEDITOR.dtd.$listItem ) )
2445 return;
2446
2447 if ( !el.is( CKEDITOR.dtd.$block ) )
2448 return;
2449
2450 // Allow drop line inside, but never before or after nested editable (http://dev.ckeditor.com/ticket/12006).
2451 if ( Widget.isDomNestedEditable( el ) )
2452 return;
2453
2454 // Do not allow droping inside the widget being dragged (http://dev.ckeditor.com/ticket/13397).
2455 if ( widgetsRepo._.draggedWidget.wrapper.contains( el ) ) {
2456 return;
2457 }
2458
2459 // If element is nested editable, make sure widget can be dropped there (http://dev.ckeditor.com/ticket/12006).
2460 var nestedEditable = Widget.getNestedEditable( editable, el );
2461 if ( nestedEditable ) {
2462 var draggedWidget = widgetsRepo._.draggedWidget;
2463
2464 // Don't let the widget to be dropped into its own nested editable.
2465 if ( widgetsRepo.getByElement( nestedEditable ) == draggedWidget )
2466 return;
2467
2468 var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ],
2469 draggedRequiredContent = draggedWidget.requiredContent;
2470
2471 // There will be no relation if the filter of nested editable does not allow
2472 // requiredContent of dragged widget.
2473 if ( filter && draggedRequiredContent && !filter.check( draggedRequiredContent ) )
2474 return;
2475 }
2476
2477 return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER;
2478 }
2479 }
2480 } ),
2481 locator: new lineutils.locator( editor ),
2482 liner: new lineutils.liner( editor, {
2483 lineStyle: {
2484 cursor: 'move !important',
2485 'border-top-color': '#666'
2486 },
2487 tipLeftStyle: {
2488 'border-left-color': '#666'
2489 },
2490 tipRightStyle: {
2491 'border-right-color': '#666'
2492 }
2493 } )
2494 }, true );
2495 } );
2496 }
2497
2498 // Setup mouse observer which will trigger:
2499 // * widget focus on widget click,
2500 // * widget#doubleclick forwarded from editor#doubleclick.
2501 function setupMouseObserver( widgetsRepo ) {
2502 var editor = widgetsRepo.editor;
2503
2504 editor.on( 'contentDom', function() {
2505 var editable = editor.editable(),
2506 evtRoot = editable.isInline() ? editable : editor.document,
2507 widget,
2508 mouseDownOnDragHandler;
2509
2510 editable.attachListener( evtRoot, 'mousedown', function( evt ) {
2511 var target = evt.data.getTarget();
2512
2513 // Clicking scrollbar in Chrome will invoke event with target object of document type (#663).
2514 // In IE8 the target object will be empty (http://dev.ckeditor.com/ticket/10887).
2515 // We need to check if target is a proper element.
2516 widget = ( target instanceof CKEDITOR.dom.element ) ? widgetsRepo.getByElement( target ) : null;
2517
2518 mouseDownOnDragHandler = 0; // Reset.
2519
2520 // Widget was clicked, but not editable nested in it.
2521 if ( widget ) {
2522 // Ignore mousedown on drag and drop handler if the widget is inline.
2523 // Block widgets are handled by Lineutils.
2524 if ( widget.inline && target.type == CKEDITOR.NODE_ELEMENT && target.hasAttribute( 'data-cke-widget-drag-handler' ) ) {
2525 mouseDownOnDragHandler = 1;
2526
2527 // When drag handler is pressed we have to clear current selection if it wasn't already on this widget.
2528 // Otherwise, the selection may be in a fillingChar, which prevents dragging a widget. (http://dev.ckeditor.com/ticket/13284, see comment 8 and 9.)
2529 if ( widgetsRepo.focused != widget )
2530 editor.getSelection().removeAllRanges();
2531
2532 return;
2533 }
2534
2535 if ( !Widget.getNestedEditable( widget.wrapper, target ) ) {
2536 evt.data.preventDefault();
2537 if ( !CKEDITOR.env.ie )
2538 widget.focus();
2539 } else {
2540 // Reset widget so mouseup listener is not confused.
2541 widget = null;
2542 }
2543 }
2544 } );
2545
2546 // Focus widget on mouseup if mousedown was fired on drag handler.
2547 // Note: mouseup won't be fired at all if widget was dragged and dropped, so
2548 // this code will be executed only when drag handler was clicked.
2549 editable.attachListener( evtRoot, 'mouseup', function() {
2550 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null).
2551 if ( mouseDownOnDragHandler && widget && widget.wrapper ) {
2552 mouseDownOnDragHandler = 0;
2553 widget.focus();
2554 }
2555 } );
2556
2557 // On IE it is not enough to block mousedown. If widget wrapper (element with
2558 // contenteditable=false attribute) is clicked directly (it is a target),
2559 // then after mouseup/click IE will select that element.
2560 // It is not possible to prevent that default action,
2561 // so we force fake selection after everything happened.
2562 if ( CKEDITOR.env.ie ) {
2563 editable.attachListener( evtRoot, 'mouseup', function() {
2564 setTimeout( function() {
2565 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null) and
2566 // in editable contains widget (it could be dragged and removed).
2567 if ( widget && widget.wrapper && editable.contains( widget.wrapper ) ) {
2568 widget.focus();
2569 widget = null;
2570 }
2571 } );
2572 } );
2573 }
2574 } );
2575
2576 editor.on( 'doubleclick', function( evt ) {
2577 var widget = widgetsRepo.getByElement( evt.data.element );
2578
2579 // Not in widget or in nested editable.
2580 if ( !widget || Widget.getNestedEditable( widget.wrapper, evt.data.element ) )
2581 return;
2582
2583 return widget.fire( 'doubleclick', { element: evt.data.element } );
2584 }, null, null, 1 );
2585 }
2586
2587 // Setup editor#key observer which will forward it
2588 // to focused widget.
2589 function setupKeyboardObserver( widgetsRepo ) {
2590 var editor = widgetsRepo.editor;
2591
2592 editor.on( 'key', function( evt ) {
2593 var focused = widgetsRepo.focused,
2594 widgetHoldingFocusedEditable = widgetsRepo.widgetHoldingFocusedEditable,
2595 ret;
2596
2597 if ( focused )
2598 ret = focused.fire( 'key', { keyCode: evt.data.keyCode } );
2599 else if ( widgetHoldingFocusedEditable )
2600 ret = onEditableKey( widgetHoldingFocusedEditable, evt.data.keyCode );
2601
2602 return ret;
2603 }, null, null, 1 );
2604 }
2605
2606 // Setup copybin on native copy and cut events in order to handle copy and cut commands
2607 // if user accepted security alert on IEs.
2608 // Note: when copying or cutting using keystroke, copySingleWidget will be first executed
2609 // by the keydown listener. Conflict between two calls will be resolved by copy_bin existence check.
2610 function setupNativeCutAndCopy( widgetsRepo ) {
2611 var editor = widgetsRepo.editor;
2612
2613 editor.on( 'contentDom', function() {
2614 var editable = editor.editable();
2615
2616 editable.attachListener( editable, 'copy', eventListener );
2617 editable.attachListener( editable, 'cut', eventListener );
2618 } );
2619
2620 function eventListener( evt ) {
2621 if ( widgetsRepo.focused )
2622 copySingleWidget( widgetsRepo.focused, evt.name == 'cut' );
2623 }
2624 }
2625
2626 // Setup selection observer which will trigger:
2627 // * widget select & focus on selection change,
2628 // * nested editable focus (related properites and classes) on selection change,
2629 // * deselecting and blurring all widgets on data,
2630 // * blurring widget on editor blur.
2631 function setupSelectionObserver( widgetsRepo ) {
2632 var editor = widgetsRepo.editor;
2633
2634 editor.on( 'selectionCheck', function() {
2635 widgetsRepo.fire( 'checkSelection' );
2636 } );
2637
2638 widgetsRepo.on( 'checkSelection', widgetsRepo.checkSelection, widgetsRepo );
2639
2640 editor.on( 'selectionChange', function( evt ) {
2641 var nestedEditable = Widget.getNestedEditable( editor.editable(), evt.data.selection.getStartElement() ),
2642 newWidget = nestedEditable && widgetsRepo.getByElement( nestedEditable ),
2643 oldWidget = widgetsRepo.widgetHoldingFocusedEditable;
2644
2645 if ( oldWidget ) {
2646 if ( oldWidget !== newWidget || !oldWidget.focusedEditable.equals( nestedEditable ) ) {
2647 setFocusedEditable( widgetsRepo, oldWidget, null );
2648
2649 if ( newWidget && nestedEditable )
2650 setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2651 }
2652 }
2653 // It may happen that there's no widget even if editable was found -
2654 // e.g. if selection was automatically set in editable although widget wasn't initialized yet.
2655 else if ( newWidget && nestedEditable ) {
2656 setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2657 }
2658 } );
2659
2660 // Invalidate old widgets early - immediately on dataReady.
2661 editor.on( 'dataReady', function() {
2662 // Deselect and blur all widgets.
2663 stateUpdater( widgetsRepo ).commit();
2664 } );
2665
2666 editor.on( 'blur', function() {
2667 var widget;
2668
2669 if ( ( widget = widgetsRepo.focused ) )
2670 blurWidget( widgetsRepo, widget );
2671
2672 if ( ( widget = widgetsRepo.widgetHoldingFocusedEditable ) )
2673 setFocusedEditable( widgetsRepo, widget, null );
2674 } );
2675 }
2676
2677 // Set up actions like:
2678 // * processing in toHtml/toDataFormat,
2679 // * pasting handling,
2680 // * insertion handling,
2681 // * editable reload handling (setData, mode switch, undo/redo),
2682 // * DOM invalidation handling,
2683 // * widgets checks.
2684 function setupWidgetsLifecycle( widgetsRepo ) {
2685 setupWidgetsLifecycleStart( widgetsRepo );
2686 setupWidgetsLifecycleEnd( widgetsRepo );
2687
2688 widgetsRepo.on( 'checkWidgets', checkWidgets );
2689 widgetsRepo.editor.on( 'contentDomInvalidated', widgetsRepo.checkWidgets, widgetsRepo );
2690 }
2691
2692 function setupWidgetsLifecycleEnd( widgetsRepo ) {
2693 var editor = widgetsRepo.editor,
2694 downcastingSessions = {};
2695
2696 // Listen before htmlDP#htmlFilter is applied to cache all widgets, because we'll
2697 // loose data-cke-* attributes.
2698 editor.on( 'toDataFormat', function( evt ) {
2699 // To avoid conflicts between htmlDP#toDF calls done at the same time
2700 // (e.g. nestedEditable#getData called during downcasting some widget)
2701 // mark every toDataFormat event chain with the downcasting session id.
2702 var id = CKEDITOR.tools.getNextNumber(),
2703 toBeDowncasted = [];
2704 evt.data.downcastingSessionId = id;
2705 downcastingSessions[ id ] = toBeDowncasted;
2706
2707 evt.data.dataValue.forEach( function( element ) {
2708 var attrs = element.attributes,
2709 widget, widgetElement;
2710
2711 // Wrapper.
2712 // Perform first part of downcasting (cleanup) and cache widgets,
2713 // because after applying DP's filter all data-cke-* attributes will be gone.
2714 if ( 'data-cke-widget-id' in attrs ) {
2715 widget = widgetsRepo.instances[ attrs[ 'data-cke-widget-id' ] ];
2716 if ( widget ) {
2717 widgetElement = element.getFirst( Widget.isParserWidgetElement );
2718 toBeDowncasted.push( {
2719 wrapper: element,
2720 element: widgetElement,
2721 widget: widget,
2722 editables: {}
2723 } );
2724
2725 // If widget did not have data-cke-widget attribute before upcasting remove it.
2726 if ( widgetElement.attributes[ 'data-cke-widget-keep-attr' ] != '1' )
2727 delete widgetElement.attributes[ 'data-widget' ];
2728 }
2729 }
2730 // Nested editable.
2731 else if ( 'data-cke-widget-editable' in attrs ) {
2732 // Save the reference to this nested editable in the closest widget to be downcasted.
2733 // Nested editables are downcasted in the successive toDataFormat to create an opportunity
2734 // for dataFilter's "excludeNestedEditable" option to do its job (that option relies on
2735 // contenteditable="true" attribute) (http://dev.ckeditor.com/ticket/11372).
2736 toBeDowncasted[ toBeDowncasted.length - 1 ].editables[ attrs[ 'data-cke-widget-editable' ] ] = element;
2737
2738 // Don't check children - there won't be next wrapper or nested editable which we
2739 // should process in this session.
2740 return false;
2741 }
2742 }, CKEDITOR.NODE_ELEMENT, true );
2743 }, null, null, 8 );
2744
2745 // Listen after dataProcessor.htmlFilter and ACF were applied
2746 // so wrappers securing widgets' contents are removed after all filtering was done.
2747 editor.on( 'toDataFormat', function( evt ) {
2748 // Ignore some unmarked sessions.
2749 if ( !evt.data.downcastingSessionId )
2750 return;
2751
2752 var toBeDowncasted = downcastingSessions[ evt.data.downcastingSessionId ],
2753 toBe, widget, widgetElement, retElement, editableElement, e;
2754
2755 while ( ( toBe = toBeDowncasted.shift() ) ) {
2756 widget = toBe.widget;
2757 widgetElement = toBe.element;
2758 retElement = widget._.downcastFn && widget._.downcastFn.call( widget, widgetElement );
2759
2760 // Replace nested editables' content with their output data.
2761 for ( e in toBe.editables ) {
2762 editableElement = toBe.editables[ e ];
2763
2764 delete editableElement.attributes.contenteditable;
2765 editableElement.setHtml( widget.editables[ e ].getData() );
2766 }
2767
2768 // Returned element always defaults to widgetElement.
2769 if ( !retElement )
2770 retElement = widgetElement;
2771
2772 toBe.wrapper.replaceWith( retElement );
2773 }
2774 }, null, null, 13 );
2775
2776
2777 editor.on( 'contentDomUnload', function() {
2778 widgetsRepo.destroyAll( true );
2779 } );
2780 }
2781
2782 function setupWidgetsLifecycleStart( widgetsRepo ) {
2783 var editor = widgetsRepo.editor,
2784 processedWidgetOnly,
2785 snapshotLoaded;
2786
2787 // Listen after ACF (so data are filtered),
2788 // but before dataProcessor.dataFilter was applied (so we can secure widgets' internals).
2789 editor.on( 'toHtml', function( evt ) {
2790 var upcastIterator = createUpcastIterator( widgetsRepo ),
2791 toBeWrapped;
2792
2793 evt.data.dataValue.forEach( upcastIterator.iterator, CKEDITOR.NODE_ELEMENT, true );
2794
2795 // Clean up and wrap all queued elements.
2796 while ( ( toBeWrapped = upcastIterator.toBeWrapped.pop() ) ) {
2797 cleanUpWidgetElement( toBeWrapped[ 0 ] );
2798 widgetsRepo.wrapElement( toBeWrapped[ 0 ], toBeWrapped[ 1 ] );
2799 }
2800
2801 // Used to determine whether only widget was pasted.
2802 if ( evt.data.protectedWhitespaces ) {
2803 // Whitespaces are protected by wrapping content with spans. Take the middle node only.
2804 processedWidgetOnly = evt.data.dataValue.children.length == 3 &&
2805 Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 1 ] );
2806 } else {
2807 processedWidgetOnly = evt.data.dataValue.children.length == 1 &&
2808 Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 0 ] );
2809 }
2810 }, null, null, 8 );
2811
2812 editor.on( 'dataReady', function() {
2813 // Clean up all widgets loaded from snapshot.
2814 if ( snapshotLoaded )
2815 cleanUpAllWidgetElements( widgetsRepo, editor.editable() );
2816 snapshotLoaded = 0;
2817
2818 // Some widgets were destroyed on contentDomUnload,
2819 // some on loadSnapshot, but that does not include
2820 // e.g. setHtml on inline editor or widgets removed just
2821 // before setting data.
2822 widgetsRepo.destroyAll( true );
2823 widgetsRepo.initOnAll();
2824 } );
2825
2826 // Set flag so dataReady will know that additional
2827 // cleanup is needed, because snapshot containing widgets was loaded.
2828 editor.on( 'loadSnapshot', function( evt ) {
2829 // Primitive but sufficient check which will prevent from executing
2830 // heavier cleanUpAllWidgetElements if not needed.
2831 if ( ( /data-cke-widget/ ).test( evt.data ) )
2832 snapshotLoaded = 1;
2833
2834 widgetsRepo.destroyAll( true );
2835 }, null, null, 9 );
2836
2837 // Handle pasted single widget.
2838 editor.on( 'paste', function( evt ) {
2839 var data = evt.data;
2840
2841 data.dataValue = data.dataValue.replace( pasteReplaceRegex, pasteReplaceFn );
2842
2843 // If drag'n'drop kind of paste into nested editable (data.range), selection is set AFTER
2844 // data is pasted, which means editor has no chance to change activeFilter's context.
2845 // As a result, pasted data is filtered with default editor's filter instead of NE's and
2846 // funny things get inserted. Changing the filter by analysis of the paste range below (http://dev.ckeditor.com/ticket/13186).
2847 if ( data.range ) {
2848 // Check if pasting into nested editable.
2849 var nestedEditable = Widget.getNestedEditable( editor.editable(), data.range.startContainer );
2850
2851 if ( nestedEditable ) {
2852 // Retrieve the filter from NE's data and set it active before editor.insertHtml is done
2853 // in clipboard plugin.
2854 var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ];
2855
2856 if ( filter ) {
2857 editor.setActiveFilter( filter );
2858 }
2859 }
2860 }
2861 } );
2862
2863 // Listen with high priority to check widgets after data was inserted.
2864 editor.on( 'afterInsertHtml', function( evt ) {
2865 if ( evt.data.intoRange ) {
2866 widgetsRepo.checkWidgets( { initOnlyNew: true } );
2867 } else {
2868 editor.fire( 'lockSnapshot' );
2869 // Init only new for performance reason.
2870 // Focus inited if only widget was processed.
2871 widgetsRepo.checkWidgets( { initOnlyNew: true, focusInited: processedWidgetOnly } );
2872
2873 editor.fire( 'unlockSnapshot' );
2874 }
2875 } );
2876 }
2877
2878 // Helper for coordinating which widgets should be
2879 // selected/deselected and which one should be focused/blurred.
2880 function stateUpdater( widgetsRepo ) {
2881 var currentlySelected = widgetsRepo.selected,
2882 toBeSelected = [],
2883 toBeDeselected = currentlySelected.slice( 0 ),
2884 focused = null;
2885
2886 return {
2887 select: function( widget ) {
2888 if ( CKEDITOR.tools.indexOf( currentlySelected, widget ) < 0 )
2889 toBeSelected.push( widget );
2890
2891 var index = CKEDITOR.tools.indexOf( toBeDeselected, widget );
2892 if ( index >= 0 )
2893 toBeDeselected.splice( index, 1 );
2894
2895 return this;
2896 },
2897
2898 focus: function( widget ) {
2899 focused = widget;
2900 return this;
2901 },
2902
2903 commit: function() {
2904 var focusedChanged = widgetsRepo.focused !== focused,
2905 widget, isDirty;
2906
2907 widgetsRepo.editor.fire( 'lockSnapshot' );
2908
2909 if ( focusedChanged && ( widget = widgetsRepo.focused ) )
2910 blurWidget( widgetsRepo, widget );
2911
2912 while ( ( widget = toBeDeselected.pop() ) ) {
2913 currentlySelected.splice( CKEDITOR.tools.indexOf( currentlySelected, widget ), 1 );
2914 // Widget could be destroyed in the meantime - e.g. data could be set.
2915 if ( widget.isInited() ) {
2916 isDirty = widget.editor.checkDirty();
2917
2918 widget.setSelected( false );
2919
2920 !isDirty && widget.editor.resetDirty();
2921 }
2922 }
2923
2924 if ( focusedChanged && focused ) {
2925 isDirty = widgetsRepo.editor.checkDirty();
2926
2927 widgetsRepo.focused = focused;
2928 widgetsRepo.fire( 'widgetFocused', { widget: focused } );
2929 focused.setFocused( true );
2930
2931 !isDirty && widgetsRepo.editor.resetDirty();
2932 }
2933
2934 while ( ( widget = toBeSelected.pop() ) ) {
2935 currentlySelected.push( widget );
2936 widget.setSelected( true );
2937 }
2938
2939 widgetsRepo.editor.fire( 'unlockSnapshot' );
2940 }
2941 };
2942 }
2943
2944
2945 //
2946 // WIDGET helpers ---------------------------------------------------------
2947 //
2948
2949 // LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers.
2950 var keystrokesNotBlockedByWidget = { 37: 1, 38: 1, 39: 1, 40: 1, 8: 1, 46: 1 };
2951
2952 // Applies or removes style's classes from widget.
2953 // @param {CKEDITOR.style} style Custom widget style.
2954 // @param {Boolean} apply Whether to apply or remove style.
2955 function applyRemoveStyle( widget, style, apply ) {
2956 var changed = 0,
2957 classes = getStyleClasses( style ),
2958 updatedClasses = widget.data.classes || {},
2959 cl;
2960
2961 // Ee... Something is wrong with this style.
2962 if ( !classes )
2963 return;
2964
2965 // Clone, because we need to break reference.
2966 updatedClasses = CKEDITOR.tools.clone( updatedClasses );
2967
2968 while ( ( cl = classes.pop() ) ) {
2969 if ( apply ) {
2970 if ( !updatedClasses[ cl ] )
2971 changed = updatedClasses[ cl ] = 1;
2972 } else {
2973 if ( updatedClasses[ cl ] ) {
2974 delete updatedClasses[ cl ];
2975 changed = 1;
2976 }
2977 }
2978 }
2979 if ( changed )
2980 widget.setData( 'classes', updatedClasses );
2981 }
2982
2983 function cancel( evt ) {
2984 evt.cancel();
2985 }
2986
2987 function copySingleWidget( widget, isCut ) {
2988 var editor = widget.editor,
2989 doc = editor.document;
2990
2991 // We're still handling previous copy/cut.
2992 // When keystroke is used to copy/cut this will also prevent
2993 // conflict with copySingleWidget called again for native copy/cut event.
2994 if ( doc.getById( 'cke_copybin' ) )
2995 return;
2996
2997 // [IE] Use span for copybin and its container to avoid bug with expanding editable height by
2998 // absolutely positioned element.
2999 var copybinName = ( editor.blockless || CKEDITOR.env.ie ) ? 'span' : 'div',
3000 copybin = doc.createElement( copybinName ),
3001 copybinContainer = doc.createElement( copybinName ),
3002 // IE8 always jumps to the end of document.
3003 needsScrollHack = CKEDITOR.env.ie && CKEDITOR.env.version < 9;
3004
3005 copybinContainer.setAttributes( {
3006 id: 'cke_copybin',
3007 'data-cke-temp': '1'
3008 } );
3009
3010 // Position copybin element outside current viewport.
3011 copybin.setStyles( {
3012 position: 'absolute',
3013 width: '1px',
3014 height: '1px',
3015 overflow: 'hidden'
3016 } );
3017
3018 copybin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-5000px' );
3019
3020 var range = editor.createRange();
3021 range.setStartBefore( widget.wrapper );
3022 range.setEndAfter( widget.wrapper );
3023
3024 copybin.setHtml(
3025 '<span data-cke-copybin-start="1">\u200b</span>' +
3026 editor.editable().getHtmlFromRange( range ).getHtml() +
3027 '<span data-cke-copybin-end="1">\u200b</span>' );
3028
3029 // Save snapshot with the current state.
3030 editor.fire( 'saveSnapshot' );
3031
3032 // Ignore copybin.
3033 editor.fire( 'lockSnapshot' );
3034
3035 copybinContainer.append( copybin );
3036 editor.editable().append( copybinContainer );
3037
3038 var listener1 = editor.on( 'selectionChange', cancel, null, null, 0 ),
3039 listener2 = widget.repository.on( 'checkSelection', cancel, null, null, 0 );
3040
3041 if ( needsScrollHack ) {
3042 var docElement = doc.getDocumentElement().$,
3043 scrollTop = docElement.scrollTop;
3044 }
3045
3046 // Once the clone of the widget is inside of copybin, select
3047 // the entire contents. This selection will be copied by the
3048 // native browser's clipboard system.
3049 range = editor.createRange();
3050 range.selectNodeContents( copybin );
3051 range.select();
3052
3053 if ( needsScrollHack )
3054 docElement.scrollTop = scrollTop;
3055
3056 setTimeout( function() {
3057 // [IE] Focus widget before removing copybin to avoid scroll jump.
3058 if ( !isCut )
3059 widget.focus();
3060
3061 copybinContainer.remove();
3062
3063 listener1.removeListener();
3064 listener2.removeListener();
3065
3066 editor.fire( 'unlockSnapshot' );
3067
3068 if ( isCut ) {
3069 widget.repository.del( widget );
3070 editor.fire( 'saveSnapshot' );
3071 }
3072 }, 100 ); // Use 100ms, so Chrome (@Mac) will be able to grab the content.
3073 }
3074
3075 // Extracts classes array from style instance.
3076 function getStyleClasses( style ) {
3077 var attrs = style.getDefinition().attributes,
3078 classes = attrs && attrs[ 'class' ];
3079
3080 return classes ? classes.split( /\s+/ ) : null;
3081 }
3082
3083 // [IE] Force keeping focus because IE sometimes forgets to fire focus on main editable
3084 // when blurring nested editable.
3085 // @context widget
3086 function onEditableBlur() {
3087 var active = CKEDITOR.document.getActive(),
3088 editor = this.editor,
3089 editable = editor.editable();
3090
3091 // If focus stays within editor override blur and set currentActive because it should be
3092 // automatically changed to editable on editable#focus but it is not fired.
3093 if ( ( editable.isInline() ? editable : editor.document.getWindow().getFrame() ).equals( active ) )
3094 editor.focusManager.focus( editable );
3095 }
3096
3097 // Force selectionChange when editable was focused.
3098 // Similar to hack in selection.js#~620.
3099 // @context widget
3100 function onEditableFocus() {
3101 // Gecko does not support 'DOMFocusIn' event on which we unlock selection
3102 // in selection.js to prevent selection locking when entering nested editables.
3103 if ( CKEDITOR.env.gecko )
3104 this.editor.unlockSelection();
3105
3106 // We don't need to force selectionCheck on Webkit, because on Webkit
3107 // we do that on DOMFocusIn in selection.js.
3108 if ( !CKEDITOR.env.webkit ) {
3109 this.editor.forceNextSelectionCheck();
3110 this.editor.selectionChange( 1 );
3111 }
3112 }
3113
3114 // Setup listener on widget#data which will update (remove/add) classes
3115 // by comparing newly set classes with the old ones.
3116 function setupDataClassesListener( widget ) {
3117 // Note: previousClasses and newClasses may be null!
3118 // Tip: for ( cl in null ) is correct.
3119 var previousClasses = null;
3120
3121 widget.on( 'data', function() {
3122 var newClasses = this.data.classes,
3123 cl;
3124
3125 // When setting new classes one need to remember
3126 // that he must break reference.
3127 if ( previousClasses == newClasses )
3128 return;
3129
3130 for ( cl in previousClasses ) {
3131 // Avoid removing and adding classes again.
3132 if ( !( newClasses && newClasses[ cl ] ) )
3133 this.removeClass( cl );
3134 }
3135 for ( cl in newClasses )
3136 this.addClass( cl );
3137
3138 previousClasses = newClasses;
3139 } );
3140 }
3141
3142 // Add a listener to data event that will set/change widget's label (http://dev.ckeditor.com/ticket/14539).
3143 function setupA11yListener( widget ) {
3144 // Note, the function gets executed in a context of widget instance.
3145 function getLabelDefault() {
3146 return this.editor.lang.widget.label.replace( /%1/, this.pathName || this.element.getName() );
3147 }
3148
3149 // Setting a listener on data is enough, there's no need to perform it on widget initialization, as
3150 // setupWidgetData fires this event anyway.
3151 widget.on( 'data', function() {
3152 // In some cases widget might get destroyed in an earlier data listener. For instance, image2 plugin, does
3153 // so when changing its internal state.
3154 if ( !widget.wrapper ) {
3155 return;
3156 }
3157
3158 var label = this.getLabel ? this.getLabel() : getLabelDefault.call( this );
3159
3160 widget.wrapper.setAttribute( 'role', 'region' );
3161 widget.wrapper.setAttribute( 'aria-label', label );
3162 }, null, null, 9999 );
3163 }
3164
3165 function setupDragHandler( widget ) {
3166 if ( !widget.draggable )
3167 return;
3168
3169 var editor = widget.editor,
3170 // Use getLast to find wrapper's direct descendant (http://dev.ckeditor.com/ticket/12022).
3171 container = widget.wrapper.getLast( Widget.isDomDragHandlerContainer ),
3172 img;
3173
3174 // Reuse drag handler if already exists (http://dev.ckeditor.com/ticket/11281).
3175 if ( container )
3176 img = container.findOne( 'img' );
3177 else {
3178 container = new CKEDITOR.dom.element( 'span', editor.document );
3179 container.setAttributes( {
3180 'class': 'cke_reset cke_widget_drag_handler_container',
3181 // Split background and background-image for IE8 which will break on rgba().
3182 style: 'background:rgba(220,220,220,0.5);background-image:url(' + editor.plugins.widget.path + 'images/handle.png)'
3183 } );
3184
3185 img = new CKEDITOR.dom.element( 'img', editor.document );
3186 img.setAttributes( {
3187 'class': 'cke_reset cke_widget_drag_handler',
3188 'data-cke-widget-drag-handler': '1',
3189 src: CKEDITOR.tools.transparentImageData,
3190 width: DRAG_HANDLER_SIZE,
3191 title: editor.lang.widget.move,
3192 height: DRAG_HANDLER_SIZE,
3193 role: 'presentation'
3194 } );
3195 widget.inline && img.setAttribute( 'draggable', 'true' );
3196
3197 container.append( img );
3198 widget.wrapper.append( container );
3199 }
3200
3201 // Preventing page reload when dropped content on widget wrapper (http://dev.ckeditor.com/ticket/13015).
3202 // Widget is not editable so by default drop on it isn't allowed what means that
3203 // browser handles it (there's no editable#drop event). If there's no drop event we cannot block
3204 // the drop, so page is reloaded. This listener enables drop on widget wrappers.
3205 widget.wrapper.on( 'dragover', function( evt ) {
3206 evt.data.preventDefault();
3207 } );
3208
3209 widget.wrapper.on( 'mouseenter', widget.updateDragHandlerPosition, widget );
3210 setTimeout( function() {
3211 widget.on( 'data', widget.updateDragHandlerPosition, widget );
3212 }, 50 );
3213
3214 if ( !widget.inline ) {
3215 img.on( 'mousedown', onBlockWidgetDrag, widget );
3216
3217 // On IE8 'dragstart' is propagated to editable, so editor#dragstart is fired twice on block widgets.
3218 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3219 img.on( 'dragstart', function( evt ) {
3220 evt.data.preventDefault( true );
3221 } );
3222 }
3223 }
3224
3225 widget.dragHandlerContainer = container;
3226 }
3227
3228 function onBlockWidgetDrag( evt ) {
3229 var finder = this.repository.finder,
3230 locator = this.repository.locator,
3231 liner = this.repository.liner,
3232 editor = this.editor,
3233 editable = editor.editable(),
3234 listeners = [],
3235 sorted = [],
3236 locations,
3237 y;
3238
3239 // Mark dragged widget for repository#finder.
3240 this.repository._.draggedWidget = this;
3241
3242 // Harvest all possible relations and display some closest.
3243 var relations = finder.greedySearch(),
3244
3245 buffer = CKEDITOR.tools.eventsBuffer( 50, function() {
3246 locations = locator.locate( relations );
3247
3248 // There's only a single line displayed for D&D.
3249 sorted = locator.sort( y, 1 );
3250
3251 if ( sorted.length ) {
3252 liner.prepare( relations, locations );
3253 liner.placeLine( sorted[ 0 ] );
3254 liner.cleanup();
3255 }
3256 } );
3257
3258 // Let's have the "dragging cursor" over entire editable.
3259 editable.addClass( 'cke_widget_dragging' );
3260
3261 // Cache mouse position so it is re-used in events buffer.
3262 listeners.push( editable.on( 'mousemove', function( evt ) {
3263 y = evt.data.$.clientY;
3264 buffer.input();
3265 } ) );
3266
3267 // Fire drag start as it happens during the native D&D.
3268 editor.fire( 'dragstart', { target: evt.sender } );
3269
3270 function onMouseUp() {
3271 var l;
3272
3273 buffer.reset();
3274
3275 // Stop observing events.
3276 while ( ( l = listeners.pop() ) )
3277 l.removeListener();
3278
3279 onBlockWidgetDrop.call( this, sorted, evt.sender );
3280 }
3281
3282 // Mouseup means "drop". This is when the widget is being detached
3283 // from DOM and placed at range determined by the line (location).
3284 listeners.push( editor.document.once( 'mouseup', onMouseUp, this ) );
3285
3286 // Prevent calling 'onBlockWidgetDrop' twice in the inline editor.
3287 // `removeListener` does not work if it is called at the same time event is fired.
3288 if ( !editable.isInline() ) {
3289 // Mouseup may occur when user hovers the line, which belongs to
3290 // the outer document. This is, of course, a valid listener too.
3291 listeners.push( CKEDITOR.document.once( 'mouseup', onMouseUp, this ) );
3292 }
3293 }
3294
3295 function onBlockWidgetDrop( sorted, dragTarget ) {
3296 var finder = this.repository.finder,
3297 liner = this.repository.liner,
3298 editor = this.editor,
3299 editable = this.editor.editable();
3300
3301 if ( !CKEDITOR.tools.isEmpty( liner.visible ) ) {
3302 // Retrieve range for the closest location.
3303 var dropRange = finder.getRange( sorted[ 0 ] );
3304
3305 // Focus widget (it could lost focus after mousedown+mouseup)
3306 // and save this state as the one where we want to be taken back when undoing.
3307 this.focus();
3308
3309 // Drag range will be set in the drop listener.
3310 editor.fire( 'drop', {
3311 dropRange: dropRange,
3312 target: dropRange.startContainer
3313 } );
3314 }
3315
3316 // Clean-up custom cursor for editable.
3317 editable.removeClass( 'cke_widget_dragging' );
3318
3319 // Clean-up all remaining lines.
3320 liner.hideVisible();
3321
3322 // Clean-up drag & drop.
3323 editor.fire( 'dragend', { target: dragTarget } );
3324 }
3325
3326 function setupEditables( widget ) {
3327 var editableName,
3328 editableDef,
3329 definedEditables = widget.editables;
3330
3331 widget.editables = {};
3332
3333 if ( !widget.editables )
3334 return;
3335
3336 for ( editableName in definedEditables ) {
3337 editableDef = definedEditables[ editableName ];
3338 widget.initEditable( editableName, typeof editableDef == 'string' ? { selector: editableDef } : editableDef );
3339 }
3340 }
3341
3342 function setupMask( widget ) {
3343 if ( !widget.mask )
3344 return;
3345
3346 // Reuse mask if already exists (http://dev.ckeditor.com/ticket/11281).
3347 var img = widget.wrapper.findOne( '.cke_widget_mask' );
3348
3349 if ( !img ) {
3350 img = new CKEDITOR.dom.element( 'img', widget.editor.document );
3351 img.setAttributes( {
3352 src: CKEDITOR.tools.transparentImageData,
3353 'class': 'cke_reset cke_widget_mask'
3354 } );
3355 widget.wrapper.append( img );
3356 }
3357
3358 widget.mask = img;
3359 }
3360
3361 // Replace parts object containing:
3362 // partName => selector pairs
3363 // with:
3364 // partName => element pairs
3365 function setupParts( widget ) {
3366 if ( widget.parts ) {
3367 var parts = {},
3368 el, partName;
3369
3370 for ( partName in widget.parts ) {
3371 el = widget.wrapper.findOne( widget.parts[ partName ] );
3372 parts[ partName ] = el;
3373 }
3374 widget.parts = parts;
3375 }
3376 }
3377
3378 function setupWidget( widget, widgetDef ) {
3379 setupWrapper( widget );
3380 setupParts( widget );
3381 setupEditables( widget );
3382 setupMask( widget );
3383 setupDragHandler( widget );
3384 setupDataClassesListener( widget );
3385 setupA11yListener( widget );
3386
3387 // http://dev.ckeditor.com/ticket/11145: [IE8] Non-editable content of widget is draggable.
3388 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3389 widget.wrapper.on( 'dragstart', function( evt ) {
3390 var target = evt.data.getTarget();
3391
3392 // Allow text dragging inside nested editables or dragging inline widget's drag handler.
3393 if ( !Widget.getNestedEditable( widget, target ) && !( widget.inline && Widget.isDomDragHandler( target ) ) )
3394 evt.data.preventDefault();
3395 } );
3396 }
3397
3398 widget.wrapper.removeClass( 'cke_widget_new' );
3399 widget.element.addClass( 'cke_widget_element' );
3400
3401 widget.on( 'key', function( evt ) {
3402 var keyCode = evt.data.keyCode;
3403
3404 // ENTER.
3405 if ( keyCode == 13 ) {
3406 widget.edit();
3407 // CTRL+C or CTRL+X.
3408 } else if ( keyCode == CKEDITOR.CTRL + 67 || keyCode == CKEDITOR.CTRL + 88 ) {
3409 copySingleWidget( widget, keyCode == CKEDITOR.CTRL + 88 );
3410 return; // Do not preventDefault.
3411 } else if ( keyCode in keystrokesNotBlockedByWidget || ( CKEDITOR.CTRL & keyCode ) || ( CKEDITOR.ALT & keyCode ) ) {
3412 // Pass chosen keystrokes to other plugins or default fake sel handlers.
3413 // Pass all CTRL/ALT keystrokes.
3414 return;
3415 }
3416
3417 return false;
3418 }, null, null, 999 );
3419 // Listen with high priority so it's possible
3420 // to overwrite this callback.
3421
3422 widget.on( 'doubleclick', function( evt ) {
3423 if ( widget.edit() ) {
3424 // We have to cancel event if edit method opens a dialog, otherwise
3425 // link plugin may open extra dialog (http://dev.ckeditor.com/ticket/12140).
3426 evt.cancel();
3427 }
3428 } );
3429
3430 if ( widgetDef.data )
3431 widget.on( 'data', widgetDef.data );
3432
3433 if ( widgetDef.edit )
3434 widget.on( 'edit', widgetDef.edit );
3435 }
3436
3437 function setupWidgetData( widget, startupData ) {
3438 var widgetDataAttr = widget.element.data( 'cke-widget-data' );
3439
3440 if ( widgetDataAttr )
3441 widget.setData( JSON.parse( decodeURIComponent( widgetDataAttr ) ) );
3442 if ( startupData )
3443 widget.setData( startupData );
3444
3445 // Populate classes if they are not preset.
3446 if ( !widget.data.classes )
3447 widget.setData( 'classes', widget.getClasses() );
3448
3449 // Unblock data and...
3450 widget.dataReady = true;
3451
3452 // Write data to element because this was blocked when data wasn't ready.
3453 writeDataToElement( widget );
3454
3455 // Fire data event first time, because this was blocked when data wasn't ready.
3456 widget.fire( 'data', widget.data );
3457 }
3458
3459 function setupWrapper( widget ) {
3460 // Retrieve widget wrapper. Assign an id to it.
3461 var wrapper = widget.wrapper = widget.element.getParent();
3462 wrapper.setAttribute( 'data-cke-widget-id', widget.id );
3463 }
3464
3465 function writeDataToElement( widget ) {
3466 widget.element.data( 'cke-widget-data', encodeURIComponent( JSON.stringify( widget.data ) ) );
3467 }
3468
3469 //
3470 // WIDGET STYLE HANDLER ---------------------------------------------------
3471 //
3472
3473 ( function() {
3474 // Styles categorized by group. It is used to prevent applying styles for the same group being used together.
3475 var styleGroups = {};
3476
3477 /**
3478 * The class representing a widget style. It is an {@link CKEDITOR#STYLE_OBJECT object} like
3479 * the styles handler for widgets.
3480 *
3481 * **Note:** This custom style handler does not support all methods of the {@link CKEDITOR.style} class.
3482 * Not supported methods: {@link #applyToRange}, {@link #removeFromRange}, {@link #applyToObject}.
3483 *
3484 * @since 4.4
3485 * @class CKEDITOR.style.customHandlers.widget
3486 * @extends CKEDITOR.style
3487 */
3488 CKEDITOR.style.addCustomHandler( {
3489 type: 'widget',
3490
3491 setup: function( styleDefinition ) {
3492 /**
3493 * The name of widget to which this style can be applied.
3494 * It is extracted from style definition's `widget` property.
3495 *
3496 * @property {String} widget
3497 */
3498 this.widget = styleDefinition.widget;
3499
3500 /**
3501 * An array of groups that this style belongs to.
3502 * Styles assigned to the same group cannot be combined.
3503 *
3504 * @since 4.6.2
3505 * @property {Array} group
3506 */
3507 this.group = typeof styleDefinition.group == 'string' ? [ styleDefinition.group ] : styleDefinition.group;
3508
3509 // Store style categorized by its group.
3510 // It is used to prevent enabling two styles from same group.
3511 if ( this.group ) {
3512 saveStyleGroup( this );
3513 }
3514 },
3515
3516 apply: function( editor ) {
3517 var widget;
3518
3519 // Before CKEditor 4.4 wasn't a required argument, so we need to
3520 // handle a case when it wasn't provided.
3521 if ( !( editor instanceof CKEDITOR.editor ) )
3522 return;
3523
3524 // Theoretically we could bypass checkApplicable, get widget from
3525 // widgets.focused and check its name, what would be faster, but then
3526 // this custom style would work differently than the default style
3527 // which checks if it's applicable before applying or removing itself.
3528 if ( this.checkApplicable( editor.elementPath(), editor ) ) {
3529 widget = editor.widgets.focused;
3530
3531 // Remove other styles from the same group.
3532 if ( this.group ) {
3533 this.removeStylesFromSameGroup( editor );
3534 }
3535
3536 widget.applyStyle( this );
3537 }
3538 },
3539
3540 remove: function( editor ) {
3541 // Before CKEditor 4.4 wasn't a required argument, so we need to
3542 // handle a case when it wasn't provided.
3543 if ( !( editor instanceof CKEDITOR.editor ) )
3544 return;
3545
3546 if ( this.checkApplicable( editor.elementPath(), editor ) )
3547 editor.widgets.focused.removeStyle( this );
3548 },
3549
3550 /**
3551 * Removes all styles that belong to the same group as this style. This method will neither add nor remove
3552 * the current style.
3553 * Returns `true` if any style was removed, otherwise returns `false`.
3554 *
3555 * @since 4.6.2
3556 * @param {CKEDITOR.editor} editor
3557 * @returns {Boolean}
3558 */
3559 removeStylesFromSameGroup: function( editor ) {
3560 var stylesFromSameGroup,
3561 path,
3562 removed = false;
3563
3564 // Before CKEditor 4.4 wasn't a required argument, so we need to
3565 // handle a case when it wasn't provided.
3566 if ( !( editor instanceof CKEDITOR.editor ) )
3567 return false;
3568
3569 path = editor.elementPath();
3570 if ( this.checkApplicable( path, editor ) ) {
3571 // Iterate over each group.
3572 for ( var i = 0, l = this.group.length; i < l; i++ ) {
3573 stylesFromSameGroup = styleGroups[ this.widget ][ this.group[ i ] ];
3574 // Iterate over each style from group.
3575 for ( var j = 0; j < stylesFromSameGroup.length; j++ ) {
3576 if ( stylesFromSameGroup[ j ] !== this && stylesFromSameGroup[ j ].checkActive( path, editor ) ) {
3577 editor.widgets.focused.removeStyle( stylesFromSameGroup[ j ] );
3578 removed = true;
3579 }
3580 }
3581 }
3582 }
3583
3584 return removed;
3585 },
3586
3587 checkActive: function( elementPath, editor ) {
3588 return this.checkElementMatch( elementPath.lastElement, 0, editor );
3589 },
3590
3591 checkApplicable: function( elementPath, editor ) {
3592 // Before CKEditor 4.4 wasn't a required argument, so we need to
3593 // handle a case when it wasn't provided.
3594 if ( !( editor instanceof CKEDITOR.editor ) )
3595 return false;
3596
3597 return this.checkElement( elementPath.lastElement );
3598 },
3599
3600 checkElementMatch: checkElementMatch,
3601
3602 checkElementRemovable: checkElementMatch,
3603
3604 /**
3605 * Checks if an element is a {@link CKEDITOR.plugins.widget#wrapper wrapper} of a
3606 * widget whose name matches the {@link #widget widget name} specified in the style definition.
3607 *
3608 * @param {CKEDITOR.dom.element} element
3609 * @returns {Boolean}
3610 */
3611 checkElement: function( element ) {
3612 if ( !Widget.isDomWidgetWrapper( element ) )
3613 return false;
3614
3615 var widgetElement = element.getFirst( Widget.isDomWidgetElement );
3616 return widgetElement && widgetElement.data( 'widget' ) == this.widget;
3617 },
3618
3619 buildPreview: function( label ) {
3620 return label || this._.definition.name;
3621 },
3622
3623 /**
3624 * Returns allowed content rules which should be registered for this style.
3625 * Uses widget's {@link CKEDITOR.plugins.widget.definition#styleableElements} to make a rule
3626 * allowing classes on specified elements or use widget's
3627 * {@link CKEDITOR.plugins.widget.definition#styleToAllowedContentRules} method to transform a style
3628 * into allowed content rules.
3629 *
3630 * @param {CKEDITOR.editor} The editor instance.
3631 * @returns {CKEDITOR.filter.allowedContentRules}
3632 */
3633 toAllowedContentRules: function( editor ) {
3634 if ( !editor )
3635 return null;
3636
3637 var widgetDef = editor.widgets.registered[ this.widget ],
3638 classes,
3639 rule = {};
3640
3641 if ( !widgetDef )
3642 return null;
3643
3644 if ( widgetDef.styleableElements ) {
3645 classes = this.getClassesArray();
3646 if ( !classes )
3647 return null;
3648
3649 rule[ widgetDef.styleableElements ] = {
3650 classes: classes,
3651 propertiesOnly: true
3652 };
3653 return rule;
3654 }
3655 if ( widgetDef.styleToAllowedContentRules )
3656 return widgetDef.styleToAllowedContentRules( this );
3657 return null;
3658 },
3659
3660 /**
3661 * Returns classes defined in the style in form of an array.
3662 *
3663 * @returns {String[]}
3664 */
3665 getClassesArray: function() {
3666 var classes = this._.definition.attributes && this._.definition.attributes[ 'class' ];
3667
3668 return classes ? CKEDITOR.tools.trim( classes ).split( /\s+/ ) : null;
3669 },
3670
3671 /**
3672 * Not implemented.
3673 *
3674 * @method applyToRange
3675 */
3676 applyToRange: notImplemented,
3677
3678 /**
3679 * Not implemented.
3680 *
3681 * @method removeFromRange
3682 */
3683 removeFromRange: notImplemented,
3684
3685 /**
3686 * Not implemented.
3687 *
3688 * @method applyToObject
3689 */
3690 applyToObject: notImplemented
3691 } );
3692
3693 function notImplemented() {}
3694
3695 // @context style
3696 function checkElementMatch( element, fullMatch, editor ) {
3697 // Before CKEditor 4.4 wasn't a required argument, so we need to
3698 // handle a case when it wasn't provided.
3699 if ( !editor )
3700 return false;
3701
3702 if ( !this.checkElement( element ) )
3703 return false;
3704
3705 var widget = editor.widgets.getByElement( element, true );
3706 return widget && widget.checkStyleActive( this );
3707 }
3708
3709 // Save and categorize style by its group.
3710 function saveStyleGroup( style ) {
3711 var widgetName = style.widget,
3712 group;
3713
3714 if ( !styleGroups[ widgetName ] ) {
3715 styleGroups[ widgetName ] = {};
3716 }
3717
3718 for ( var i = 0, l = style.group.length; i < l; i++ ) {
3719 group = style.group[ i ];
3720 if ( !styleGroups[ widgetName ][ group ] ) {
3721 styleGroups[ widgetName ][ group ] = [];
3722 }
3723
3724 styleGroups[ widgetName ][ group ].push( style );
3725 }
3726 }
3727
3728 } )();
3729
3730 //
3731 // EXPOSE PUBLIC API ------------------------------------------------------
3732 //
3733
3734 CKEDITOR.plugins.widget = Widget;
3735 Widget.repository = Repository;
3736 Widget.nestedEditable = NestedEditable;
3737} )();
3738
3739/**
3740 * An event fired when a widget definition is registered by the {@link CKEDITOR.plugins.widget.repository#add} method.
3741 * It is possible to modify the definition being registered.
3742 *
3743 * @event widgetDefinition
3744 * @member CKEDITOR.editor
3745 * @param {CKEDITOR.plugins.widget.definition} data Widget definition.
3746 */
3747
3748/**
3749 * This is an abstract class that describes the definition of a widget.
3750 * It is a type of {@link CKEDITOR.plugins.widget.repository#add} method's second argument.
3751 *
3752 * Widget instances inherit from registered widget definitions, although not in a prototypal way.
3753 * They are simply extended with corresponding widget definitions. Note that not all properties of
3754 * the widget definition become properties of a widget. Some, like {@link #data} or {@link #edit}, become
3755 * widget's events listeners.
3756 *
3757 * @class CKEDITOR.plugins.widget.definition
3758 * @abstract
3759 * @mixins CKEDITOR.feature
3760 */
3761
3762/**
3763 * Widget definition name. It is automatically set when the definition is
3764 * {@link CKEDITOR.plugins.widget.repository#add registered}.
3765 *
3766 * @property {String} name
3767 */
3768
3769/**
3770 * The method executed while initializing a widget, after a widget instance
3771 * is created, but before it is ready. It is executed before the first
3772 * {@link CKEDITOR.plugins.widget#event-data} is fired so it is common to
3773 * use the `init` method to populate widget data with information loaded from
3774 * the DOM, like for exmaple:
3775 *
3776 * init: function() {
3777 * this.setData( 'width', this.element.getStyle( 'width' ) );
3778 *
3779 * if ( this.parts.caption.getStyle( 'display' ) != 'none' )
3780 * this.setData( 'showCaption', true );
3781 * }
3782 *
3783 * @property {Function} init
3784 */
3785
3786/**
3787 * The function to be used to upcast an element to this widget or a
3788 * comma-separated list of upcast methods from the {@link #upcasts} object.
3789 *
3790 * The upcast function **is not** executed in the widget context (because the widget
3791 * does not exist yet) and two arguments are passed:
3792 *
3793 * * `element` ({@link CKEDITOR.htmlParser.element}) &ndash; The element to be checked.
3794 * * `data` (`Object`) &ndash; The object which can be extended with data which will then be passed to the widget.
3795 *
3796 * An element will be upcasted if a function returned `true` or an instance of
3797 * a {@link CKEDITOR.htmlParser.element} if upcasting meant DOM structure changes
3798 * (in this case the widget will be initialized on the returned element).
3799 *
3800 * @property {String/Function} upcast
3801 */
3802
3803/**
3804 * The object containing functions which can be used to upcast this widget.
3805 * Only those pointed by the {@link #upcast} property will be used.
3806 *
3807 * In most cases it is appropriate to use {@link #upcast} directly,
3808 * because majority of widgets need just one method.
3809 * However, in some cases the widget author may want to expose more than one variant
3810 * and then this property may be used.
3811 *
3812 * upcasts: {
3813 * // This function may upcast only figure elements.
3814 * figure: function() {
3815 * // ...
3816 * },
3817 * // This function may upcast only image elements.
3818 * image: function() {
3819 * // ...
3820 * },
3821 * // More variants...
3822 * }
3823 *
3824 * // Then, widget user may choose which upcast methods will be enabled.
3825 * editor.on( 'widgetDefinition', function( evt ) {
3826 * if ( evt.data.name == 'image' )
3827 * evt.data.upcast = 'figure,image'; // Use both methods.
3828 * } );
3829 *
3830 * @property {Object} upcasts
3831 */
3832
3833/**
3834 * The {@link #upcast} method(s) priority. The upcast with a lower priority number will be called before
3835 * the one with a higher number. The default priority is `10`.
3836 *
3837 * @since 4.5
3838 * @property {Number} [upcastPriority=10]
3839 */
3840
3841/**
3842 * The function to be used to downcast this widget or
3843 * a name of the downcast option from the {@link #downcasts} object.
3844 *
3845 * The downcast funciton will be executed in the {@link CKEDITOR.plugins.widget} context
3846 * and with `widgetElement` ({@link CKEDITOR.htmlParser.element}) argument which is
3847 * the widget's main element.
3848 *
3849 * The function may return an instance of the {@link CKEDITOR.htmlParser.node} class if the widget
3850 * needs to be downcasted to a different node than the widget's main element.
3851 *
3852 * @property {String/Function} downcast
3853 */
3854
3855/**
3856 * The object containing functions which can be used to downcast this widget.
3857 * Only the one pointed by the {@link #downcast} property will be used.
3858 *
3859 * In most cases it is appropriate to use {@link #downcast} directly,
3860 * because majority of widgets have just one variant of downcasting (or none at all).
3861 * However, in some cases the widget author may want to expose more than one variant
3862 * and then this property may be used.
3863 *
3864 * downcasts: {
3865 * // This downcast may transform the widget into the figure element.
3866 * figure: function() {
3867 * // ...
3868 * },
3869 * // This downcast may transform the widget into the image element with data-* attributes.
3870 * image: function() {
3871 * // ...
3872 * }
3873 * }
3874 *
3875 * // Then, the widget user may choose one of the downcast options when setting up his editor.
3876 * editor.on( 'widgetDefinition', function( evt ) {
3877 * if ( evt.data.name == 'image' )
3878 * evt.data.downcast = 'figure';
3879 * } );
3880 *
3881 * @property downcasts
3882 */
3883
3884/**
3885 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-edit} event listener.
3886 * This means that it will be executed when a widget is being edited.
3887 * See the {@link CKEDITOR.plugins.widget#method-edit} method.
3888 *
3889 * @property {Function} edit
3890 */
3891
3892/**
3893 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-data} event listener.
3894 * This means that it will be executed every time the {@link CKEDITOR.plugins.widget#property-data widget data} changes.
3895 *
3896 * @property {Function} data
3897 */
3898
3899/**
3900 * The method to be executed when the widget's command is executed in order to insert a new widget
3901 * (widget of this type is not focused). If not defined, then the default action will be
3902 * performed which means that:
3903 *
3904 * * An instance of the widget will be created in a detached {@link CKEDITOR.dom.documentFragment document fragment},
3905 * * The {@link CKEDITOR.plugins.widget#method-edit} method will be called to trigger widget editing,
3906 * * The widget element will be inserted into DOM.
3907 *
3908 * @property {Function} insert
3909 */
3910
3911/**
3912 * The name of a dialog window which will be opened on {@link CKEDITOR.plugins.widget#method-edit}.
3913 * If not defined, then the {@link CKEDITOR.plugins.widget#method-edit} method will not perform any action and
3914 * widget's command will insert a new widget without opening a dialog window first.
3915 *
3916 * @property {String} dialog
3917 */
3918
3919/**
3920 * The template which will be used to create a new widget element (when the widget's command is executed).
3921 * This string is populated with {@link #defaults default values} by using the {@link CKEDITOR.template} format.
3922 * Therefore it has to be a valid {@link CKEDITOR.template} argument.
3923 *
3924 * @property {String} template
3925 */
3926
3927/**
3928 * The data object which will be used to populate the data of a newly created widget.
3929 * See {@link CKEDITOR.plugins.widget#property-data}.
3930 *
3931 * defaults: {
3932 * showCaption: true,
3933 * align: 'none'
3934 * }
3935 *
3936 * @property defaults
3937 */
3938
3939/**
3940 * An object containing definitions of widget components (part name => CSS selector).
3941 *
3942 * parts: {
3943 * image: 'img',
3944 * caption: 'div.caption'
3945 * }
3946 *
3947 * @property parts
3948 */
3949
3950/**
3951 * An object containing definitions of nested editables (editable name => {@link CKEDITOR.plugins.widget.nestedEditable.definition}).
3952 * Note that editables *have to* be defined in the same order as they are in DOM / {@link CKEDITOR.plugins.widget.definition#template template}.
3953 * Otherwise errors will occur when nesting widgets inside each other.
3954 *
3955 * editables: {
3956 * header: 'h1',
3957 * content: {
3958 * selector: 'div.content',
3959 * allowedContent: 'p strong em; a[!href]'
3960 * }
3961 * }
3962 *
3963 * @property editables
3964 */
3965
3966/**
3967 * The function used to obtain an accessibility label for the widget. It might be used to make
3968 * the widget labels as precise as possible, since it has access to the widget instance.
3969 *
3970 * If not specified, the default implementation will use the {@link #pathName} or the main
3971 * {@link CKEDITOR.plugins.widget#element element} tag name.
3972 *
3973 * @property {Function} getLabel
3974 */
3975
3976/**
3977 * The widget name displayed in the elements path.
3978 *
3979 * @property {String} pathName
3980 */
3981
3982/**
3983 * If set to `true`, the widget's element will be covered with a transparent mask.
3984 * This will prevent its content from being clickable, which matters in case
3985 * of special elements like embedded Flash or iframes that generate a separate "context".
3986 *
3987 * @property {Boolean} mask
3988 */
3989
3990/**
3991 * If set to `true/false`, it will force the widget to be either an inline or a block widget.
3992 * If not set, the widget type will be determined from the widget element.
3993 *
3994 * Widget type influences whether a block (`div`) or an inline (`span`) element is used
3995 * for the wrapper.
3996 *
3997 * @property {Boolean} inline
3998 */
3999
4000/**
4001 * The label for the widget toolbar button.
4002 *
4003 * editor.widgets.add( 'simplebox', {
4004 * button: 'Create a simple box'
4005 * } );
4006 *
4007 * editor.widgets.add( 'simplebox', {
4008 * button: editor.lang.simplebox.title
4009 * } );
4010 *
4011 * @property {String} button
4012 */
4013
4014/**
4015 * Whether widget should be draggable. Defaults to `true`.
4016 * If set to `false` drag handler will not be displayed when hovering widget.
4017 *
4018 * @property {Boolean} draggable
4019 */
4020
4021/**
4022 * Names of element(s) (separated by spaces) for which the {@link CKEDITOR.filter} should allow classes
4023 * defined in the widget styles. For example if your widget is upcasted from a simple `<div>`
4024 * element, then in order to make it styleable you can set:
4025 *
4026 * editor.widgets.add( 'customWidget', {
4027 * upcast: function( element ) {
4028 * return element.name == 'div';
4029 * },
4030 *
4031 * // ...
4032 *
4033 * styleableElements: 'div'
4034 * } );
4035 *
4036 * Then, when the following style is defined:
4037 *
4038 * {
4039 * name: 'Thick border', type: 'widget', widget: 'customWidget',
4040 * attributes: { 'class': 'thickBorder' }
4041 * }
4042 *
4043 * a rule allowing the `thickBorder` class for `div` elements will be registered in the {@link CKEDITOR.filter}.
4044 *
4045 * If you need to have more freedom when transforming widget style to allowed content rules,
4046 * you can use the {@link #styleToAllowedContentRules} callback.
4047 *
4048 * @since 4.4
4049 * @property {String} styleableElements
4050 */
4051
4052/**
4053 * Function transforming custom widget's {@link CKEDITOR.style} instance into
4054 * {@link CKEDITOR.filter.allowedContentRules}. It may be used when a static
4055 * {@link #styleableElements} property is not enough to inform the {@link CKEDITOR.filter}
4056 * what HTML features should be enabled when allowing the given style.
4057 *
4058 * In most cases, when style's classes just have to be added to element name(s) used by
4059 * the widget element, it is recommended to use simpler {@link #styleableElements} property.
4060 *
4061 * In order to get parsed classes from the style definition you can use
4062 * {@link CKEDITOR.style.customHandlers.widget#getClassesArray}.
4063 *
4064 * For example, if you want to use the [object format of allowed content rules](#!/guide/dev_allowed_content_rules-section-object-format),
4065 * to specify `match` validator, your implementation could look like this:
4066 *
4067 * editor.widgets.add( 'customWidget', {
4068 * // ...
4069 *
4070 * styleToAllowedContentRules: funciton( style ) {
4071 * // Retrieve classes defined in the style.
4072 * var classes = style.getClassesArray();
4073 *
4074 * // Do something crazy - for example return allowed content rules in object format,
4075 * // with custom match property and propertiesOnly flag.
4076 * return {
4077 * h1: {
4078 * match: isWidgetElement,
4079 * propertiesOnly: true,
4080 * classes: classes
4081 * }
4082 * };
4083 * }
4084 * } );
4085 *
4086 * @since 4.4
4087 * @property {Function} styleToAllowedContentRules
4088 * @param {CKEDITOR.style.customHandlers.widget} style The style to be transformed.
4089 * @returns {CKEDITOR.filter.allowedContentRules}
4090 */
4091
4092/**
4093 * This is an abstract class that describes the definition of a widget's nested editable.
4094 * It is a type of values in the {@link CKEDITOR.plugins.widget.definition#editables} object.
4095 *
4096 * In the simplest case the definition is a string which is a CSS selector used to
4097 * find an element that will become a nested editable inside the widget. Note that
4098 * the widget element can be a nested editable, too.
4099 *
4100 * In the more advanced case a definition is an object with a required `selector` property.
4101 *
4102 * editables: {
4103 * header: 'h1',
4104 * content: {
4105 * selector: 'div.content',
4106 * allowedContent: 'p strong em; a[!href]'
4107 * }
4108 * }
4109 *
4110 * @class CKEDITOR.plugins.widget.nestedEditable.definition
4111 * @abstract
4112 */
4113
4114/**
4115 * The CSS selector used to find an element which will become a nested editable.
4116 *
4117 * @property {String} selector
4118 */
4119
4120/**
4121 * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
4122 * which will be used to limit the content allowed in this nested editable.
4123 * This option is similar to {@link CKEDITOR.config#allowedContent} and one can
4124 * use it to limit the editor features available in the nested editable.
4125 *
4126 * If no `allowedContent` is specified, the editable will use the editor default
4127 * {@link CKEDITOR.editor#filter}.
4128 *
4129 * @property {CKEDITOR.filter.allowedContentRules} allowedContent
4130 */
4131
4132/**
4133 * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
4134 * which will be used to blacklist elements within this nested editable.
4135 * This option is similar to {@link CKEDITOR.config#disallowedContent}.
4136 *
4137 * Note that `disallowedContent` work on top of the definition's {@link #allowedContent}.
4138 *
4139 * @since 4.7.3
4140 * @property {CKEDITOR.filter.disallowedContentRules} disallowedContent
4141 */
4142
4143/**
4144 * Nested editable name displayed in the elements path.
4145 *
4146 * @property {String} pathName
4147 */