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.js7
-rw-r--r--sources/plugins/widget/lang/ar.js7
-rw-r--r--sources/plugins/widget/lang/bg.js7
-rw-r--r--sources/plugins/widget/lang/ca.js7
-rw-r--r--sources/plugins/widget/lang/cs.js7
-rw-r--r--sources/plugins/widget/lang/cy.js7
-rw-r--r--sources/plugins/widget/lang/da.js7
-rw-r--r--sources/plugins/widget/lang/de-ch.js7
-rw-r--r--sources/plugins/widget/lang/de.js7
-rw-r--r--sources/plugins/widget/lang/el.js7
-rw-r--r--sources/plugins/widget/lang/en-gb.js7
-rw-r--r--sources/plugins/widget/lang/en.js7
-rw-r--r--sources/plugins/widget/lang/eo.js7
-rw-r--r--sources/plugins/widget/lang/es.js7
-rw-r--r--sources/plugins/widget/lang/eu.js7
-rw-r--r--sources/plugins/widget/lang/fa.js7
-rw-r--r--sources/plugins/widget/lang/fi.js7
-rw-r--r--sources/plugins/widget/lang/fr.js7
-rw-r--r--sources/plugins/widget/lang/gl.js7
-rw-r--r--sources/plugins/widget/lang/he.js7
-rw-r--r--sources/plugins/widget/lang/hr.js7
-rw-r--r--sources/plugins/widget/lang/hu.js7
-rw-r--r--sources/plugins/widget/lang/id.js7
-rw-r--r--sources/plugins/widget/lang/it.js7
-rw-r--r--sources/plugins/widget/lang/ja.js7
-rw-r--r--sources/plugins/widget/lang/km.js7
-rw-r--r--sources/plugins/widget/lang/ko.js7
-rw-r--r--sources/plugins/widget/lang/ku.js7
-rw-r--r--sources/plugins/widget/lang/lv.js7
-rw-r--r--sources/plugins/widget/lang/nb.js7
-rw-r--r--sources/plugins/widget/lang/nl.js7
-rw-r--r--sources/plugins/widget/lang/no.js7
-rw-r--r--sources/plugins/widget/lang/pl.js7
-rw-r--r--sources/plugins/widget/lang/pt-br.js7
-rw-r--r--sources/plugins/widget/lang/pt.js7
-rw-r--r--sources/plugins/widget/lang/ru.js7
-rw-r--r--sources/plugins/widget/lang/sk.js7
-rw-r--r--sources/plugins/widget/lang/sl.js7
-rw-r--r--sources/plugins/widget/lang/sq.js7
-rw-r--r--sources/plugins/widget/lang/sv.js7
-rw-r--r--sources/plugins/widget/lang/tr.js7
-rw-r--r--sources/plugins/widget/lang/tt.js7
-rw-r--r--sources/plugins/widget/lang/ug.js7
-rw-r--r--sources/plugins/widget/lang/uk.js7
-rw-r--r--sources/plugins/widget/lang/vi.js7
-rw-r--r--sources/plugins/widget/lang/zh-cn.js7
-rw-r--r--sources/plugins/widget/lang/zh.js7
-rw-r--r--sources/plugins/widget/plugin.js3988
58 files changed, 4950 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 00000000..2cff316d
--- /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 00000000..a4a77fae
--- /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 00000000..ddf3675b
--- /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 00000000..45a150c2
--- /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 00000000..6a5e3131
--- /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 00000000..43dbad53
--- /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',
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 00000000..5d063c38
--- /dev/null
+++ b/sources/plugins/widget/dev/console.js
@@ -0,0 +1,131 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/* 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 00000000..0686d2c6
--- /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 00000000..8e54b8de
--- /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 00000000..ba8cda5b
--- /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 00000000..8bdcfc97
--- /dev/null
+++ b/sources/plugins/widget/lang/af.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'af', {
6 'move': 'Klik en trek on te beweeg'
7} );
diff --git a/sources/plugins/widget/lang/ar.js b/sources/plugins/widget/lang/ar.js
new file mode 100644
index 00000000..f2a2643c
--- /dev/null
+++ b/sources/plugins/widget/lang/ar.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ar', {
6 'move': 'إضغط و إسحب للتحريك'
7} );
diff --git a/sources/plugins/widget/lang/bg.js b/sources/plugins/widget/lang/bg.js
new file mode 100644
index 00000000..2f6c4cfa
--- /dev/null
+++ b/sources/plugins/widget/lang/bg.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'bg', {
6 'move': 'Кликни и влачи, за да преместиш'
7} );
diff --git a/sources/plugins/widget/lang/ca.js b/sources/plugins/widget/lang/ca.js
new file mode 100644
index 00000000..5ab11a29
--- /dev/null
+++ b/sources/plugins/widget/lang/ca.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ca', {
6 'move': 'Clicar i arrossegar per moure'
7} );
diff --git a/sources/plugins/widget/lang/cs.js b/sources/plugins/widget/lang/cs.js
new file mode 100644
index 00000000..9a35c2ee
--- /dev/null
+++ b/sources/plugins/widget/lang/cs.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'cs', {
6 'move': 'Klepněte a táhněte pro přesunutí'
7} );
diff --git a/sources/plugins/widget/lang/cy.js b/sources/plugins/widget/lang/cy.js
new file mode 100644
index 00000000..92dc5670
--- /dev/null
+++ b/sources/plugins/widget/lang/cy.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'cy', {
6 'move': 'Clcio a llusgo i symud'
7} );
diff --git a/sources/plugins/widget/lang/da.js b/sources/plugins/widget/lang/da.js
new file mode 100644
index 00000000..93ba4f9c
--- /dev/null
+++ b/sources/plugins/widget/lang/da.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'da', {
6 'move': 'Klik og træk for at flytte'
7} );
diff --git a/sources/plugins/widget/lang/de-ch.js b/sources/plugins/widget/lang/de-ch.js
new file mode 100644
index 00000000..02b07231
--- /dev/null
+++ b/sources/plugins/widget/lang/de-ch.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'de-ch', {
6 'move': 'Zum Verschieben anwählen und ziehen'
7} );
diff --git a/sources/plugins/widget/lang/de.js b/sources/plugins/widget/lang/de.js
new file mode 100644
index 00000000..07e93893
--- /dev/null
+++ b/sources/plugins/widget/lang/de.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'de', {
6 'move': 'Zum Verschieben anwählen und ziehen'
7} );
diff --git a/sources/plugins/widget/lang/el.js b/sources/plugins/widget/lang/el.js
new file mode 100644
index 00000000..5844a5cc
--- /dev/null
+++ b/sources/plugins/widget/lang/el.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'el', {
6 'move': 'Κάνετε κλικ και σύρετε το ποντίκι για να μετακινήστε'
7} );
diff --git a/sources/plugins/widget/lang/en-gb.js b/sources/plugins/widget/lang/en-gb.js
new file mode 100644
index 00000000..d0bb615b
--- /dev/null
+++ b/sources/plugins/widget/lang/en-gb.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'en-gb', {
6 'move': 'Click and drag to move'
7} );
diff --git a/sources/plugins/widget/lang/en.js b/sources/plugins/widget/lang/en.js
new file mode 100644
index 00000000..ef9f78d8
--- /dev/null
+++ b/sources/plugins/widget/lang/en.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'en', {
6 'move': 'Click and drag to move'
7} );
diff --git a/sources/plugins/widget/lang/eo.js b/sources/plugins/widget/lang/eo.js
new file mode 100644
index 00000000..7bd397a5
--- /dev/null
+++ b/sources/plugins/widget/lang/eo.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'eo', {
6 'move': 'klaki kaj treni por movi'
7} );
diff --git a/sources/plugins/widget/lang/es.js b/sources/plugins/widget/lang/es.js
new file mode 100644
index 00000000..4d18badf
--- /dev/null
+++ b/sources/plugins/widget/lang/es.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'es', {
6 'move': 'Dar clic y arrastrar para mover'
7} );
diff --git a/sources/plugins/widget/lang/eu.js b/sources/plugins/widget/lang/eu.js
new file mode 100644
index 00000000..c698b7cb
--- /dev/null
+++ b/sources/plugins/widget/lang/eu.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'eu', {
6 'move': 'Klikatu eta arrastatu lekuz aldatzeko'
7} );
diff --git a/sources/plugins/widget/lang/fa.js b/sources/plugins/widget/lang/fa.js
new file mode 100644
index 00000000..50e4ebef
--- /dev/null
+++ b/sources/plugins/widget/lang/fa.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fa', {
6 'move': 'کلیک و کشیدن برای جابجایی'
7} );
diff --git a/sources/plugins/widget/lang/fi.js b/sources/plugins/widget/lang/fi.js
new file mode 100644
index 00000000..70beda2f
--- /dev/null
+++ b/sources/plugins/widget/lang/fi.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fi', {
6 'move': 'Siirrä klikkaamalla ja raahaamalla'
7} );
diff --git a/sources/plugins/widget/lang/fr.js b/sources/plugins/widget/lang/fr.js
new file mode 100644
index 00000000..0415763e
--- /dev/null
+++ b/sources/plugins/widget/lang/fr.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'fr', {
6 'move': 'Cliquer et glisser pour déplacer'
7} );
diff --git a/sources/plugins/widget/lang/gl.js b/sources/plugins/widget/lang/gl.js
new file mode 100644
index 00000000..4a36ad6f
--- /dev/null
+++ b/sources/plugins/widget/lang/gl.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'gl', {
6 'move': 'Prema e arrastre para mover'
7} );
diff --git a/sources/plugins/widget/lang/he.js b/sources/plugins/widget/lang/he.js
new file mode 100644
index 00000000..4280ed26
--- /dev/null
+++ b/sources/plugins/widget/lang/he.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'he', {
6 'move': 'לחץ וגרור להזזה'
7} );
diff --git a/sources/plugins/widget/lang/hr.js b/sources/plugins/widget/lang/hr.js
new file mode 100644
index 00000000..b785a11e
--- /dev/null
+++ b/sources/plugins/widget/lang/hr.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'hr', {
6 'move': 'Klikni i povuci da pomakneš'
7} );
diff --git a/sources/plugins/widget/lang/hu.js b/sources/plugins/widget/lang/hu.js
new file mode 100644
index 00000000..fb2465e5
--- /dev/null
+++ b/sources/plugins/widget/lang/hu.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'hu', {
6 'move': 'Kattints és húzd a mozgatáshoz'
7} );
diff --git a/sources/plugins/widget/lang/id.js b/sources/plugins/widget/lang/id.js
new file mode 100644
index 00000000..b2df3b21
--- /dev/null
+++ b/sources/plugins/widget/lang/id.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'id', {
6 'move': 'Tekan dan geser untuk memindahkan'
7} );
diff --git a/sources/plugins/widget/lang/it.js b/sources/plugins/widget/lang/it.js
new file mode 100644
index 00000000..1b1bc90d
--- /dev/null
+++ b/sources/plugins/widget/lang/it.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'it', {
6 'move': 'Fare clic e trascinare per spostare'
7} );
diff --git a/sources/plugins/widget/lang/ja.js b/sources/plugins/widget/lang/ja.js
new file mode 100644
index 00000000..808e71f1
--- /dev/null
+++ b/sources/plugins/widget/lang/ja.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ja', {
6 'move': 'ドラッグして移動'
7} );
diff --git a/sources/plugins/widget/lang/km.js b/sources/plugins/widget/lang/km.js
new file mode 100644
index 00000000..08d6ff92
--- /dev/null
+++ b/sources/plugins/widget/lang/km.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'km', {
6 'move': 'ចុច​ហើយ​ទាញ​ដើម្បី​ផ្លាស់​ទី'
7} );
diff --git a/sources/plugins/widget/lang/ko.js b/sources/plugins/widget/lang/ko.js
new file mode 100644
index 00000000..902f2ae4
--- /dev/null
+++ b/sources/plugins/widget/lang/ko.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ko', {
6 'move': '움직이려면 클릭 후 드래그 하세요'
7} );
diff --git a/sources/plugins/widget/lang/ku.js b/sources/plugins/widget/lang/ku.js
new file mode 100644
index 00000000..621d3414
--- /dev/null
+++ b/sources/plugins/widget/lang/ku.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ku', {
6 'move': 'کرتەبکە و ڕایبکێشە بۆ جوڵاندن'
7} );
diff --git a/sources/plugins/widget/lang/lv.js b/sources/plugins/widget/lang/lv.js
new file mode 100644
index 00000000..ea6c2226
--- /dev/null
+++ b/sources/plugins/widget/lang/lv.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'lv', {
6 'move': 'Klikšķina un velc, lai pārvietotu'
7} );
diff --git a/sources/plugins/widget/lang/nb.js b/sources/plugins/widget/lang/nb.js
new file mode 100644
index 00000000..482b833b
--- /dev/null
+++ b/sources/plugins/widget/lang/nb.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'nb', {
6 'move': 'Klikk og dra for å flytte'
7} );
diff --git a/sources/plugins/widget/lang/nl.js b/sources/plugins/widget/lang/nl.js
new file mode 100644
index 00000000..037e25a9
--- /dev/null
+++ b/sources/plugins/widget/lang/nl.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'nl', {
6 'move': 'Klik en sleep om te verplaatsen'
7} );
diff --git a/sources/plugins/widget/lang/no.js b/sources/plugins/widget/lang/no.js
new file mode 100644
index 00000000..9f55ec10
--- /dev/null
+++ b/sources/plugins/widget/lang/no.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'no', {
6 'move': 'Klikk og dra for å flytte'
7} );
diff --git a/sources/plugins/widget/lang/pl.js b/sources/plugins/widget/lang/pl.js
new file mode 100644
index 00000000..763ea0c0
--- /dev/null
+++ b/sources/plugins/widget/lang/pl.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pl', {
6 'move': 'Kliknij i przeciągnij, by przenieść.'
7} );
diff --git a/sources/plugins/widget/lang/pt-br.js b/sources/plugins/widget/lang/pt-br.js
new file mode 100644
index 00000000..5dd9a1e4
--- /dev/null
+++ b/sources/plugins/widget/lang/pt-br.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pt-br', {
6 'move': 'Click e arraste para mover'
7} );
diff --git a/sources/plugins/widget/lang/pt.js b/sources/plugins/widget/lang/pt.js
new file mode 100644
index 00000000..cd756803
--- /dev/null
+++ b/sources/plugins/widget/lang/pt.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'pt', {
6 'move': 'Clique e arraste para mover'
7} );
diff --git a/sources/plugins/widget/lang/ru.js b/sources/plugins/widget/lang/ru.js
new file mode 100644
index 00000000..fc5f7100
--- /dev/null
+++ b/sources/plugins/widget/lang/ru.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ru', {
6 'move': 'Нажмите и перетащите'
7} );
diff --git a/sources/plugins/widget/lang/sk.js b/sources/plugins/widget/lang/sk.js
new file mode 100644
index 00000000..0aa179f7
--- /dev/null
+++ b/sources/plugins/widget/lang/sk.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sk', {
6 'move': 'Kliknite a potiahnite pre presunutie'
7} );
diff --git a/sources/plugins/widget/lang/sl.js b/sources/plugins/widget/lang/sl.js
new file mode 100644
index 00000000..06a0d828
--- /dev/null
+++ b/sources/plugins/widget/lang/sl.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sl', {
6 'move': 'Kliknite in povlecite, da premaknete'
7} );
diff --git a/sources/plugins/widget/lang/sq.js b/sources/plugins/widget/lang/sq.js
new file mode 100644
index 00000000..c7e92642
--- /dev/null
+++ b/sources/plugins/widget/lang/sq.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sq', {
6 'move': 'Kliko dhe tërhiqe për ta lëvizur'
7} );
diff --git a/sources/plugins/widget/lang/sv.js b/sources/plugins/widget/lang/sv.js
new file mode 100644
index 00000000..9fa3e4c3
--- /dev/null
+++ b/sources/plugins/widget/lang/sv.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'sv', {
6 'move': 'Klicka och drag för att flytta'
7} );
diff --git a/sources/plugins/widget/lang/tr.js b/sources/plugins/widget/lang/tr.js
new file mode 100644
index 00000000..71f3f07a
--- /dev/null
+++ b/sources/plugins/widget/lang/tr.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'tr', {
6 'move': 'Taşımak için, tıklayın ve sürükleyin'
7} );
diff --git a/sources/plugins/widget/lang/tt.js b/sources/plugins/widget/lang/tt.js
new file mode 100644
index 00000000..2521f30d
--- /dev/null
+++ b/sources/plugins/widget/lang/tt.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'tt', {
6 'move': 'Күчереп куер өчен басып шудырыгыз'
7} );
diff --git a/sources/plugins/widget/lang/ug.js b/sources/plugins/widget/lang/ug.js
new file mode 100644
index 00000000..96acdaa3
--- /dev/null
+++ b/sources/plugins/widget/lang/ug.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'ug', {
6 'move': 'يۆتكەشتە چېكىپ سۆرەڭ'
7} );
diff --git a/sources/plugins/widget/lang/uk.js b/sources/plugins/widget/lang/uk.js
new file mode 100644
index 00000000..e9d6c87c
--- /dev/null
+++ b/sources/plugins/widget/lang/uk.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'uk', {
6 'move': 'Клікніть і потягніть для переміщення'
7} );
diff --git a/sources/plugins/widget/lang/vi.js b/sources/plugins/widget/lang/vi.js
new file mode 100644
index 00000000..c13983ea
--- /dev/null
+++ b/sources/plugins/widget/lang/vi.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'vi', {
6 'move': 'Nhấp chuột và kéo để di chuyển'
7} );
diff --git a/sources/plugins/widget/lang/zh-cn.js b/sources/plugins/widget/lang/zh-cn.js
new file mode 100644
index 00000000..dce27835
--- /dev/null
+++ b/sources/plugins/widget/lang/zh-cn.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'zh-cn', {
6 'move': '点击并拖拽以移动'
7} );
diff --git a/sources/plugins/widget/lang/zh.js b/sources/plugins/widget/lang/zh.js
new file mode 100644
index 00000000..53676395
--- /dev/null
+++ b/sources/plugins/widget/lang/zh.js
@@ -0,0 +1,7 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5CKEDITOR.plugins.setLang( 'widget', 'zh', {
6 'move': '拖曳以移動'
7} );
diff --git a/sources/plugins/widget/plugin.js b/sources/plugins/widget/plugin.js
new file mode 100644
index 00000000..b77f5bc3
--- /dev/null
+++ b/sources/plugins/widget/plugin.js
@@ -0,0 +1,3988 @@
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6/**
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,bg,ca,cs,cy,da,de,de-ch,el,en,en-gb,eo,es,eu,fa,fi,fr,gl,he,hr,hu,id,it,ja,km,ko,ku,lv,nb,nl,no,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',
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 (#11177).
50 // We need to hide unpositined handlers, so they don't extend
51 // widget's outline far to the left (#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 (#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 widgetDef = this.registered[ widgetName || element.data( 'widget' ) ];
631 if ( !widgetDef )
632 return null;
633
634 // Do not wrap already wrapped element.
635 wrapper = element.getParent();
636 if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.data( 'cke-widget-wrapper' ) )
637 return wrapper;
638
639 // If attribute isn't already set (e.g. for pasted widget), set it.
640 if ( !element.hasAttribute( 'data-cke-widget-keep-attr' ) )
641 element.data( 'cke-widget-keep-attr', element.data( 'widget' ) ? 1 : 0 );
642 if ( widgetName )
643 element.data( 'widget', widgetName );
644
645 isInline = isWidgetInline( widgetDef, element.getName() );
646
647 wrapper = new CKEDITOR.dom.element( isInline ? 'span' : 'div' );
648 wrapper.setAttributes( getWrapperAttributes( isInline ) );
649
650 wrapper.data( 'cke-display-name', widgetDef.pathName ? widgetDef.pathName : element.getName() );
651
652 // Replace element unless it is a detached one.
653 if ( element.getParent( true ) )
654 wrapper.replace( element );
655 element.appendTo( wrapper );
656 }
657 else if ( element instanceof CKEDITOR.htmlParser.element ) {
658 widgetDef = this.registered[ widgetName || element.attributes[ 'data-widget' ] ];
659 if ( !widgetDef )
660 return null;
661
662 wrapper = element.parent;
663 if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.attributes[ 'data-cke-widget-wrapper' ] )
664 return wrapper;
665
666 // If attribute isn't already set (e.g. for pasted widget), set it.
667 if ( !( 'data-cke-widget-keep-attr' in element.attributes ) )
668 element.attributes[ 'data-cke-widget-keep-attr' ] = element.attributes[ 'data-widget' ] ? 1 : 0;
669 if ( widgetName )
670 element.attributes[ 'data-widget' ] = widgetName;
671
672 isInline = isWidgetInline( widgetDef, element.name );
673
674 wrapper = new CKEDITOR.htmlParser.element( isInline ? 'span' : 'div', getWrapperAttributes( isInline ) );
675
676 wrapper.attributes[ 'data-cke-display-name' ] = widgetDef.pathName ? widgetDef.pathName : element.name;
677
678 var parent = element.parent,
679 index;
680
681 // Don't detach already detached element.
682 if ( parent ) {
683 index = element.getIndex();
684 element.remove();
685 }
686
687 wrapper.add( element );
688
689 // Insert wrapper fixing DOM (splitting parents if wrapper is not allowed inside them).
690 parent && insertElement( parent, index, wrapper );
691 }
692
693 return wrapper;
694 },
695
696 // Expose for tests.
697 _tests_createEditableFilter: createEditableFilter
698 };
699
700 CKEDITOR.event.implementOn( Repository.prototype );
701
702 /**
703 * An event fired when a widget instance is created, but before it is fully initialized.
704 *
705 * @event instanceCreated
706 * @param {CKEDITOR.plugins.widget} data The widget instance.
707 */
708
709 /**
710 * An event fired when a widget instance was destroyed.
711 *
712 * See also {@link CKEDITOR.plugins.widget#event-destroy}.
713 *
714 * @event instanceDestroyed
715 * @param {CKEDITOR.plugins.widget} data The widget instance.
716 */
717
718 /**
719 * An event fired to trigger the selection check.
720 *
721 * See the {@link #method-checkSelection} method.
722 *
723 * @event checkSelection
724 */
725
726 /**
727 * An event fired by the the {@link #method-checkWidgets} method.
728 *
729 * It can be canceled in order to stop the {@link #method-checkWidgets}
730 * method execution or the event listener can modify the method's options.
731 *
732 * @event checkWidgets
733 * @param [data]
734 * @param {Boolean} [data.initOnlyNew] Initialize widgets only on newly wrapped
735 * widget elements (those which still have the `cke_widget_new` class). When this option is
736 * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
737 * will not be reinitialized. This makes the check faster.
738 * @param {Boolean} [data.focusInited] If only one widget is initialized by
739 * the method, it will be focused.
740 */
741
742
743 /**
744 * An instance of a widget. Together with {@link CKEDITOR.plugins.widget.repository} these
745 * two classes constitute the core of the Widget System.
746 *
747 * Note that neither the repository nor the widget instances can be created by using their constructors.
748 * A repository instance is automatically set up by the Widget plugin and is accessible under
749 * {@link CKEDITOR.editor#widgets}, while widget instances are created and destroyed by the repository.
750 *
751 * To create a widget, first you need to {@link CKEDITOR.plugins.widget.repository#add register} its
752 * {@link CKEDITOR.plugins.widget.definition definition}:
753 *
754 * editor.widgets.add( 'simplebox', {
755 * upcast: function( element ) {
756 * // Defines which elements will become widgets.
757 * if ( element.hasClass( 'simplebox' ) )
758 * return true;
759 * },
760 * init: function() {
761 * // ...
762 * }
763 * } );
764 *
765 * Once the widget definition is registered, widgets will be automatically
766 * created when loading data:
767 *
768 * editor.setData( '<div class="simplebox">foo</div>', function() {
769 * console.log( editor.widgets.instances ); // -> An object containing one instance.
770 * } );
771 *
772 * It is also possible to create instances during runtime by using a command
773 * (if a {@link CKEDITOR.plugins.widget.definition#template} property was defined):
774 *
775 * // You can execute an automatically defined command to
776 * // insert a new simplebox widget or edit the one currently focused.
777 * editor.execCommand( 'simplebox' );
778 *
779 * Note: Since CKEditor 4.5 widget's `startupData` can be passed as the command argument:
780 *
781 * editor.execCommand( 'simplebox', {
782 * startupData: {
783 * align: 'left'
784 * }
785 * } );
786 *
787 * A widget can also be created in a completely custom way:
788 *
789 * var element = editor.document.createElement( 'div' );
790 * editor.insertElement( element );
791 * var widget = editor.widgets.initOn( element, 'simplebox' );
792 *
793 * @since 4.3
794 * @class CKEDITOR.plugins.widget
795 * @mixins CKEDITOR.event
796 * @extends CKEDITOR.plugins.widget.definition
797 * @constructor Creates an instance of the widget class. Do not use it directly, but instead initialize widgets
798 * by using the {@link CKEDITOR.plugins.widget.repository#initOn} method or by the upcasting system.
799 * @param {CKEDITOR.plugins.widget.repository} widgetsRepo
800 * @param {Number} id Unique ID of this widget instance.
801 * @param {CKEDITOR.dom.element} element The widget element.
802 * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget's registered definition.
803 * @param [startupData] Initial widget data. This data object will overwrite the default data and
804 * the data loaded from the DOM.
805 */
806 function Widget( widgetsRepo, id, element, widgetDef, startupData ) {
807 var editor = widgetsRepo.editor;
808
809 // Extend this widget with widgetDef-specific methods and properties.
810 CKEDITOR.tools.extend( this, widgetDef, {
811 /**
812 * The editor instance.
813 *
814 * @readonly
815 * @property {CKEDITOR.editor}
816 */
817 editor: editor,
818
819 /**
820 * This widget's unique (per editor instance) ID.
821 *
822 * @readonly
823 * @property {Number}
824 */
825 id: id,
826
827 /**
828 * Whether this widget is an inline widget (based on an inline element unless
829 * forced otherwise by {@link CKEDITOR.plugins.widget.definition#inline}).
830 *
831 * **Note:** This option does not allow to turn a block element into an inline widget.
832 * However, it makes it possible to turn an inline element into a block widget or to
833 * force a correct type in case when automatic recognition fails.
834 *
835 * @readonly
836 * @property {Boolean}
837 */
838 inline: element.getParent().getName() == 'span',
839
840 /**
841 * The widget element &mdash; the element on which the widget was initialized.
842 *
843 * @readonly
844 * @property {CKEDITOR.dom.element} element
845 */
846 element: element,
847
848 /**
849 * Widget's data object.
850 *
851 * The data can only be set by using the {@link #setData} method.
852 * Changes made to the data fire the {@link #event-data} event.
853 *
854 * @readonly
855 */
856 data: CKEDITOR.tools.extend( {}, typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ),
857
858 /**
859 * Indicates if a widget is data-ready. Set to `true` when data from all sources
860 * ({@link CKEDITOR.plugins.widget.definition#defaults}, set in the
861 * {@link #init} method, loaded from the widget's element and startup data coming from the constructor)
862 * are finally loaded. This is immediately followed by the first {@link #event-data}.
863 *
864 * @readonly
865 */
866 dataReady: false,
867
868 /**
869 * Whether a widget instance was initialized. This means that:
870 *
871 * * An instance was created,
872 * * Its properties were set,
873 * * The `init` method was executed.
874 *
875 * **Note**: The first {@link #event-data} event could not be fired yet which
876 * means that the widget's DOM has not been set up yet. Wait for the {@link #event-ready}
877 * event to be notified when a widget is fully initialized and ready.
878 *
879 * **Note**: Use the {@link #isInited} method to check whether a widget is initialized and
880 * has not been destroyed.
881 *
882 * @readonly
883 */
884 inited: false,
885
886 /**
887 * Whether a widget instance is ready. This means that the widget is {@link #inited} and
888 * that its DOM was finally set up.
889 *
890 * **Note:** Use the {@link #isReady} method to check whether a widget is ready and
891 * has not been destroyed.
892 *
893 * @readonly
894 */
895 ready: false,
896
897 // Revert what widgetDef could override (automatic #edit listener).
898 edit: Widget.prototype.edit,
899
900 /**
901 * The nested editable element which is currently focused.
902 *
903 * @readonly
904 * @property {CKEDITOR.plugins.widget.nestedEditable}
905 */
906 focusedEditable: null,
907
908 /**
909 * The widget definition from which this instance was created.
910 *
911 * @readonly
912 * @property {CKEDITOR.plugins.widget.definition} definition
913 */
914 definition: widgetDef,
915
916 /**
917 * Link to the widget repository which created this instance.
918 *
919 * @readonly
920 * @property {CKEDITOR.plugins.widget.repository} repository
921 */
922 repository: widgetsRepo,
923
924 draggable: widgetDef.draggable !== false,
925
926 // WAAARNING: Overwrite widgetDef's priv object, because otherwise violent unicorn's gonna visit you.
927 _: {
928 downcastFn: ( widgetDef.downcast && typeof widgetDef.downcast == 'string' ) ?
929 widgetDef.downcasts[ widgetDef.downcast ] : widgetDef.downcast
930 }
931 }, true );
932
933 /**
934 * An object of widget component elements.
935 *
936 * For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts},
937 * one `partName => element` pair is added to this object during the widget initialization.
938 *
939 * @readonly
940 * @property {Object} parts
941 */
942
943 /**
944 * The template which will be used to create a new widget element (when the widget's command is executed).
945 * It will be populated with {@link #defaults default values}.
946 *
947 * @readonly
948 * @property {CKEDITOR.template} template
949 */
950
951 /**
952 * The widget wrapper &mdash; a non-editable `div` or `span` element (depending on {@link #inline})
953 * which is a parent of the {@link #element} and widget compontents like the drag handler and the {@link #mask}.
954 * It is the outermost widget element.
955 *
956 * @readonly
957 * @property {CKEDITOR.dom.element} wrapper
958 */
959
960 widgetsRepo.fire( 'instanceCreated', this );
961
962 setupWidget( this, widgetDef );
963
964 this.init && this.init();
965
966 // Finally mark widget as inited.
967 this.inited = true;
968
969 setupWidgetData( this, startupData );
970
971 // If at some point (e.g. in #data listener) widget hasn't been destroyed
972 // and widget is already attached to document then fire #ready.
973 if ( this.isInited() && editor.editable().contains( this.wrapper ) ) {
974 this.ready = true;
975 this.fire( 'ready' );
976 }
977 }
978
979 Widget.prototype = {
980 /**
981 * Adds a class to the widget element. This method is used by
982 * the {@link #applyStyle} method and should be overriden by widgets
983 * which should handle classes differently (e.g. add them to other elements).
984 *
985 * **Note**: This method should not be used directly. Use the {@link #setData} method to
986 * set the `classes` property. Read more in the {@link #setData} documentation.
987 *
988 * See also: {@link #removeClass}, {@link #hasClass}, {@link #getClasses}.
989 *
990 * @since 4.4
991 * @param {String} className The class name to be added.
992 */
993 addClass: function( className ) {
994 this.element.addClass( className );
995 },
996
997 /**
998 * Applies the specified style to the widget. It is highly recommended to use the
999 * {@link CKEDITOR.editor#applyStyle} or {@link CKEDITOR.style#apply} methods instead of
1000 * using this method directly, because unlike editor's and style's methods, this one
1001 * does not perform any checks.
1002 *
1003 * By default this method handles only classes defined in the style. It clones existing
1004 * classes which are stored in the {@link #property-data widget data}'s `classes` property,
1005 * adds new classes, and calls the {@link #setData} method if at least one new class was added.
1006 * Then, using the {@link #event-data} event listener widget applies modifications passing
1007 * new classes to the {@link #addClass} method.
1008 *
1009 * If you need to handle classes differently than in the default way, you can override the
1010 * {@link #addClass} and related methods. You can also handle other style properties than `classes`
1011 * by overriding this method.
1012 *
1013 * See also: {@link #checkStyleActive}, {@link #removeStyle}.
1014 *
1015 * @since 4.4
1016 * @param {CKEDITOR.style} style The custom widget style to be applied.
1017 */
1018 applyStyle: function( style ) {
1019 applyRemoveStyle( this, style, 1 );
1020 },
1021
1022 /**
1023 * Checks if the specified style is applied to this widget. It is highly recommended to use the
1024 * {@link CKEDITOR.style#checkActive} method instead of using this method directly,
1025 * because unlike style's method, this one does not perform any checks.
1026 *
1027 * By default this method handles only classes defined in the style and passes
1028 * them to the {@link #hasClass} method. You can override these methods to handle classes
1029 * differently or to handle more of the style properties.
1030 *
1031 * See also: {@link #applyStyle}, {@link #removeStyle}.
1032 *
1033 * @since 4.4
1034 * @param {CKEDITOR.style} style The custom widget style to be checked.
1035 * @returns {Boolean} Whether the style is applied to this widget.
1036 */
1037 checkStyleActive: function( style ) {
1038 var classes = getStyleClasses( style ),
1039 cl;
1040
1041 if ( !classes )
1042 return false;
1043
1044 while ( ( cl = classes.pop() ) ) {
1045 if ( !this.hasClass( cl ) )
1046 return false;
1047 }
1048 return true;
1049 },
1050
1051 /**
1052 * Destroys this widget instance.
1053 *
1054 * Use {@link CKEDITOR.plugins.widget.repository#destroy} when possible instead of this method.
1055 *
1056 * This method fires the {#event-destroy} event.
1057 *
1058 * @param {Boolean} [offline] Whether a widget is offline (detached from the DOM tree) &mdash;
1059 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
1060 */
1061 destroy: function( offline ) {
1062 this.fire( 'destroy' );
1063
1064 if ( this.editables ) {
1065 for ( var name in this.editables )
1066 this.destroyEditable( name, offline );
1067 }
1068
1069 if ( !offline ) {
1070 if ( this.element.data( 'cke-widget-keep-attr' ) == '0' )
1071 this.element.removeAttribute( 'data-widget' );
1072 this.element.removeAttributes( [ 'data-cke-widget-data', 'data-cke-widget-keep-attr' ] );
1073 this.element.removeClass( 'cke_widget_element' );
1074 this.element.replace( this.wrapper );
1075 }
1076
1077 this.wrapper = null;
1078 },
1079
1080 /**
1081 * Destroys a nested editable and all nested widgets.
1082 *
1083 * @param {String} editableName Nested editable name.
1084 * @param {Boolean} [offline] See {@link #method-destroy} method.
1085 */
1086 destroyEditable: function( editableName, offline ) {
1087 var editable = this.editables[ editableName ];
1088
1089 editable.removeListener( 'focus', onEditableFocus );
1090 editable.removeListener( 'blur', onEditableBlur );
1091 this.editor.focusManager.remove( editable );
1092
1093 if ( !offline ) {
1094 this.repository.destroyAll( false, editable );
1095 editable.removeClass( 'cke_widget_editable' );
1096 editable.removeClass( 'cke_widget_editable_focused' );
1097 editable.removeAttributes( [ 'contenteditable', 'data-cke-widget-editable', 'data-cke-enter-mode' ] );
1098 }
1099
1100 delete this.editables[ editableName ];
1101 },
1102
1103 /**
1104 * Starts widget editing.
1105 *
1106 * This method fires the {@link CKEDITOR.plugins.widget#event-edit} event
1107 * which may be canceled in order to prevent it from opening a dialog window.
1108 *
1109 * The dialog window name is obtained from the event's data `dialog` property or
1110 * from {@link CKEDITOR.plugins.widget.definition#dialog}.
1111 *
1112 * @returns {Boolean} Returns `true` if a dialog window was opened.
1113 */
1114 edit: function() {
1115 var evtData = { dialog: this.dialog },
1116 that = this;
1117
1118 // Edit event was blocked or there's no dialog to be automatically opened.
1119 if ( this.fire( 'edit', evtData ) === false || !evtData.dialog )
1120 return false;
1121
1122 this.editor.openDialog( evtData.dialog, function( dialog ) {
1123 var showListener,
1124 okListener;
1125
1126 // Allow to add a custom dialog handler.
1127 if ( that.fire( 'dialog', dialog ) === false )
1128 return;
1129
1130 showListener = dialog.on( 'show', function() {
1131 dialog.setupContent( that );
1132 } );
1133
1134 okListener = dialog.on( 'ok', function() {
1135 // Commit dialog's fields, but prevent from
1136 // firing data event for every field. Fire only one,
1137 // bulk event at the end.
1138 var dataChanged,
1139 dataListener = that.on( 'data', function( evt ) {
1140 dataChanged = 1;
1141 evt.cancel();
1142 }, null, null, 0 );
1143
1144 // Create snapshot preceeding snapshot with changed widget...
1145 // TODO it should not be required, but it is and I found similar
1146 // code in dialog#ok listener in dialog/plugin.js.
1147 that.editor.fire( 'saveSnapshot' );
1148 dialog.commitContent( that );
1149
1150 dataListener.removeListener();
1151 if ( dataChanged ) {
1152 that.fire( 'data', that.data );
1153 that.editor.fire( 'saveSnapshot' );
1154 }
1155 } );
1156
1157 dialog.once( 'hide', function() {
1158 showListener.removeListener();
1159 okListener.removeListener();
1160 } );
1161 } );
1162
1163 return true;
1164 },
1165
1166 /**
1167 * Returns widget element classes parsed to an object. This method
1168 * is used to populate the `classes` property of widget's {@link #property-data}.
1169 *
1170 * This method reuses {@link CKEDITOR.plugins.widget.repository#parseElementClasses}.
1171 * It should be overriden if a widget should handle classes differently (e.g. on other elements).
1172 *
1173 * See also: {@link #removeClass}, {@link #addClass}, {@link #hasClass}.
1174 *
1175 * @since 4.4
1176 * @returns {Object}
1177 */
1178 getClasses: function() {
1179 return this.repository.parseElementClasses( this.element.getAttribute( 'class' ) );
1180 },
1181
1182 /**
1183 * Checks if the widget element has specified class. This method is used by
1184 * the {@link #checkStyleActive} method and should be overriden by widgets
1185 * which should handle classes differently (e.g. on other elements).
1186 *
1187 * See also: {@link #removeClass}, {@link #addClass}, {@link #getClasses}.
1188 *
1189 * @since 4.4
1190 * @param {String} className The class to be checked.
1191 * @param {Boolean} Whether a widget has specified class.
1192 */
1193 hasClass: function( className ) {
1194 return this.element.hasClass( className );
1195 },
1196
1197 /**
1198 * Initializes a nested editable.
1199 *
1200 * **Note**: Only elements from {@link CKEDITOR.dtd#$editable} may become editables.
1201 *
1202 * @param {String} editableName The nested editable name.
1203 * @param {CKEDITOR.plugins.widget.nestedEditable.definition} definition The definition of the nested editable.
1204 * @returns {Boolean} Whether an editable was successfully initialized.
1205 */
1206 initEditable: function( editableName, definition ) {
1207 // Don't fetch just first element which matched selector but look for a correct one. (#13334)
1208 var editable = this._findOneNotNested( definition.selector );
1209
1210 if ( editable && editable.is( CKEDITOR.dtd.$editable ) ) {
1211 editable = new NestedEditable( this.editor, editable, {
1212 filter: createEditableFilter.call( this.repository, this.name, editableName, definition )
1213 } );
1214 this.editables[ editableName ] = editable;
1215
1216 editable.setAttributes( {
1217 contenteditable: 'true',
1218 'data-cke-widget-editable': editableName,
1219 'data-cke-enter-mode': editable.enterMode
1220 } );
1221
1222 if ( editable.filter )
1223 editable.data( 'cke-filter', editable.filter.id );
1224
1225 editable.addClass( 'cke_widget_editable' );
1226 // This class may be left when d&ding widget which
1227 // had focused editable. Clean this class here, not in
1228 // cleanUpWidgetElement for performance and code size reasons.
1229 editable.removeClass( 'cke_widget_editable_focused' );
1230
1231 if ( definition.pathName )
1232 editable.data( 'cke-display-name', definition.pathName );
1233
1234 this.editor.focusManager.add( editable );
1235 editable.on( 'focus', onEditableFocus, this );
1236 CKEDITOR.env.ie && editable.on( 'blur', onEditableBlur, this );
1237
1238 // Finally, process editable's data. This data wasn't processed when loading
1239 // editor's data, becuase they need to be processed separately, with its own filters and settings.
1240 editable._.initialSetData = true;
1241 editable.setData( editable.getHtml() );
1242
1243 return true;
1244 }
1245
1246 return false;
1247 },
1248
1249 /**
1250 * Looks inside wrapper element to find a node that
1251 * matches given selector and is not nested in other widget. (#13334)
1252 *
1253 * @since 4.5
1254 * @private
1255 * @param {String} selector Selector to match.
1256 * @returns {CKEDITOR.dom.element} Matched element or `null` if a node has not been found.
1257 */
1258 _findOneNotNested: function( selector ) {
1259 var matchedElements = this.wrapper.find( selector ),
1260 match,
1261 closestWrapper;
1262
1263 for ( var i = 0; i < matchedElements.count(); i++ ) {
1264 match = matchedElements.getItem( i );
1265 closestWrapper = match.getAscendant( Widget.isDomWidgetWrapper );
1266
1267 // The closest ascendant-wrapper of this match defines to which widget
1268 // this match belongs. If the ascendant is this widget's wrapper
1269 // it means that the match is not nested in other widget.
1270 if ( this.wrapper.equals( closestWrapper ) ) {
1271 return match;
1272 }
1273 }
1274
1275 return null;
1276 },
1277
1278 /**
1279 * Checks if a widget has already been initialized and has not been destroyed yet.
1280 *
1281 * See {@link #inited} for more details.
1282 *
1283 * @returns {Boolean}
1284 */
1285 isInited: function() {
1286 return !!( this.wrapper && this.inited );
1287 },
1288
1289 /**
1290 * Checks if a widget is ready and has not been destroyed yet.
1291 *
1292 * See {@link #property-ready} for more details.
1293 *
1294 * @returns {Boolean}
1295 */
1296 isReady: function() {
1297 return this.isInited() && this.ready;
1298 },
1299
1300 /**
1301 * Focuses a widget by selecting it.
1302 */
1303 focus: function() {
1304 var sel = this.editor.getSelection();
1305
1306 // Fake the selection before focusing editor, to avoid unpreventable viewports scrolling
1307 // on Webkit/Blink/IE which is done because there's no selection or selection was somewhere else than widget.
1308 if ( sel ) {
1309 var isDirty = this.editor.checkDirty();
1310
1311 sel.fake( this.wrapper );
1312
1313 !isDirty && this.editor.resetDirty();
1314 }
1315
1316 // Always focus editor (not only when focusManger.hasFocus is false) (because of #10483).
1317 this.editor.focus();
1318 },
1319
1320 /**
1321 * Removes a class from the widget element. This method is used by
1322 * the {@link #removeStyle} method and should be overriden by widgets
1323 * which should handle classes differently (e.g. on other elements).
1324 *
1325 * **Note**: This method should not be used directly. Use the {@link #setData} method to
1326 * set the `classes` property. Read more in the {@link #setData} documentation.
1327 *
1328 * See also: {@link #hasClass}, {@link #addClass}.
1329 *
1330 * @since 4.4
1331 * @param {String} className The class to be removed.
1332 */
1333 removeClass: function( className ) {
1334 this.element.removeClass( className );
1335 },
1336
1337 /**
1338 * Removes the specified style from the widget. It is highly recommended to use the
1339 * {@link CKEDITOR.editor#removeStyle} or {@link CKEDITOR.style#remove} methods instead of
1340 * using this method directly, because unlike editor's and style's methods, this one
1341 * does not perform any checks.
1342 *
1343 * Read more about how applying/removing styles works in the {@link #applyStyle} method documentation.
1344 *
1345 * See also {@link #checkStyleActive}, {@link #applyStyle}, {@link #getClasses}.
1346 *
1347 * @since 4.4
1348 * @param {CKEDITOR.style} style The custom widget style to be removed.
1349 */
1350 removeStyle: function( style ) {
1351 applyRemoveStyle( this, style, 0 );
1352 },
1353
1354 /**
1355 * Sets widget value(s) in the {@link #property-data} object.
1356 * If the given value(s) modifies current ones, the {@link #event-data} event is fired.
1357 *
1358 * this.setData( 'align', 'left' );
1359 * this.data.align; // -> 'left'
1360 *
1361 * this.setData( { align: 'right', opened: false } );
1362 * this.data.align; // -> 'right'
1363 * this.data.opened; // -> false
1364 *
1365 * Set values are stored in {@link #element}'s attribute (`data-cke-widget-data`),
1366 * in a JSON string, therefore {@link #property-data} should contain
1367 * only serializable data.
1368 *
1369 * **Note:** A special data property, `classes`, exists. It contains an object with
1370 * classes which were returned by the {@link #getClasses} method during the widget initialization.
1371 * This property is then used by the {@link #applyStyle} and {@link #removeStyle} methods.
1372 * When it is changed (the reference to object must be changed!), the widget updates its classes by
1373 * using the {@link #addClass} and {@link #removeClass} methods.
1374 *
1375 * // Adding a new class.
1376 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1377 * classes.newClass = 1;
1378 * widget.setData( 'classes', classes );
1379 *
1380 * // Removing a class.
1381 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1382 * delete classes.newClass;
1383 * widget.setData( 'classes', classes );
1384 *
1385 * @param {String/Object} keyOrData
1386 * @param {Object} value
1387 * @chainable
1388 */
1389 setData: function( key, value ) {
1390 var data = this.data,
1391 modified = 0;
1392
1393 if ( typeof key == 'string' ) {
1394 if ( data[ key ] !== value ) {
1395 data[ key ] = value;
1396 modified = 1;
1397 }
1398 }
1399 else {
1400 var newData = key;
1401
1402 for ( key in newData ) {
1403 if ( data[ key ] !== newData[ key ] ) {
1404 modified = 1;
1405 data[ key ] = newData[ key ];
1406 }
1407 }
1408 }
1409
1410 // Block firing data event and overwriting data element before setupWidgetData is executed.
1411 if ( modified && this.dataReady ) {
1412 writeDataToElement( this );
1413 this.fire( 'data', data );
1414 }
1415
1416 return this;
1417 },
1418
1419 /**
1420 * Changes the widget's focus state. This method is executed automatically after
1421 * a widget was focused by the {@link #method-focus} method or the selection was moved
1422 * out of the widget.
1423 *
1424 * This is a low-level method which is not integrated with e.g. the undo manager.
1425 * Use the {@link #method-focus} method instead.
1426 *
1427 * @param {Boolean} selected Whether to select or deselect this widget.
1428 * @chainable
1429 */
1430 setFocused: function( focused ) {
1431 this.wrapper[ focused ? 'addClass' : 'removeClass' ]( 'cke_widget_focused' );
1432 this.fire( focused ? 'focus' : 'blur' );
1433 return this;
1434 },
1435
1436 /**
1437 * Changes the widget's select state. This method is executed automatically after
1438 * a widget was selected by the {@link #method-focus} method or the selection
1439 * was moved out of the widget.
1440 *
1441 * This is a low-level method which is not integrated with e.g. the undo manager.
1442 * Use the {@link #method-focus} method instead or simply change the selection.
1443 *
1444 * @param {Boolean} selected Whether to select or deselect this widget.
1445 * @chainable
1446 */
1447 setSelected: function( selected ) {
1448 this.wrapper[ selected ? 'addClass' : 'removeClass' ]( 'cke_widget_selected' );
1449 this.fire( selected ? 'select' : 'deselect' );
1450 return this;
1451 },
1452
1453 /**
1454 * Repositions drag handler according to the widget's element position. Should be called from events, like mouseover.
1455 */
1456 updateDragHandlerPosition: function() {
1457 var editor = this.editor,
1458 domElement = this.element.$,
1459 oldPos = this._.dragHandlerOffset,
1460 newPos = {
1461 x: domElement.offsetLeft,
1462 y: domElement.offsetTop - DRAG_HANDLER_SIZE
1463 };
1464
1465 if ( oldPos && newPos.x == oldPos.x && newPos.y == oldPos.y )
1466 return;
1467
1468 // We need to make sure that dirty state is not changed (#11487).
1469 var initialDirty = editor.checkDirty();
1470
1471 editor.fire( 'lockSnapshot' );
1472 this.dragHandlerContainer.setStyles( {
1473 top: newPos.y + 'px',
1474 left: newPos.x + 'px',
1475 display: 'block'
1476 } );
1477 editor.fire( 'unlockSnapshot' );
1478 !initialDirty && editor.resetDirty();
1479
1480 this._.dragHandlerOffset = newPos;
1481 }
1482 };
1483
1484 CKEDITOR.event.implementOn( Widget.prototype );
1485
1486 /**
1487 * Gets the {@link #isDomNestedEditable nested editable}
1488 * (returned as a {@link CKEDITOR.dom.element}, not as a {@link CKEDITOR.plugins.widget.nestedEditable})
1489 * closest to the `node` or the `node` if it is a nested editable itself.
1490 *
1491 * @since 4.5
1492 * @static
1493 * @param {CKEDITOR.dom.element} guard Stop ancestor search on this node (usually editor's editable).
1494 * @param {CKEDITOR.dom.node} node Start the search from this node.
1495 * @returns {CKEDITOR.dom.element/null} Element or `null` if not found.
1496 */
1497 Widget.getNestedEditable = function( guard, node ) {
1498 if ( !node || node.equals( guard ) )
1499 return null;
1500
1501 if ( Widget.isDomNestedEditable( node ) )
1502 return node;
1503
1504 return Widget.getNestedEditable( guard, node.getParent() );
1505 };
1506
1507 /**
1508 * Checks whether the `node` is a widget's drag handle element.
1509 *
1510 * @since 4.5
1511 * @static
1512 * @param {CKEDITOR.dom.node} node
1513 * @returns {Boolean}
1514 */
1515 Widget.isDomDragHandler = function( node ) {
1516 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-drag-handler' );
1517 };
1518
1519 /**
1520 * Checks whether the `node` is a container of the widget's drag handle element.
1521 *
1522 * @since 4.5
1523 * @static
1524 * @param {CKEDITOR.dom.node} node
1525 * @returns {Boolean}
1526 */
1527 Widget.isDomDragHandlerContainer = function( node ) {
1528 return node.type == CKEDITOR.NODE_ELEMENT && node.hasClass( 'cke_widget_drag_handler_container' );
1529 };
1530
1531 /**
1532 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#editables nested editable}.
1533 * Note that this function only checks whether it is the right element, not whether
1534 * the passed `node` is an instance of {@link CKEDITOR.plugins.widget.nestedEditable}.
1535 *
1536 * @since 4.5
1537 * @static
1538 * @param {CKEDITOR.dom.node} node
1539 * @returns {Boolean}
1540 */
1541 Widget.isDomNestedEditable = function( node ) {
1542 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-editable' );
1543 };
1544
1545 /**
1546 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1547 *
1548 * @since 4.5
1549 * @static
1550 * @param {CKEDITOR.dom.node} node
1551 * @returns {Boolean}
1552 */
1553 Widget.isDomWidgetElement = function( node ) {
1554 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-widget' );
1555 };
1556
1557 /**
1558 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1559 *
1560 * @since 4.5
1561 * @static
1562 * @param {CKEDITOR.dom.element} node
1563 * @returns {Boolean}
1564 */
1565 Widget.isDomWidgetWrapper = function( node ) {
1566 return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-wrapper' );
1567 };
1568
1569 /**
1570 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1571 *
1572 * @since 4.5
1573 * @static
1574 * @param {CKEDITOR.htmlParser.node} node
1575 * @returns {Boolean}
1576 */
1577 Widget.isParserWidgetElement = function( node ) {
1578 return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-widget' ];
1579 };
1580
1581 /**
1582 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1583 *
1584 * @since 4.5
1585 * @static
1586 * @param {CKEDITOR.htmlParser.element} node
1587 * @returns {Boolean}
1588 */
1589 Widget.isParserWidgetWrapper = function( node ) {
1590 return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-cke-widget-wrapper' ];
1591 };
1592
1593 /**
1594 * An event fired when a widget is ready (fully initialized). This event is fired after:
1595 *
1596 * * {@link #init} is called,
1597 * * The first {@link #event-data} event is fired,
1598 * * A widget is attached to the document.
1599 *
1600 * Therefore, in case of widget creation with a command which opens a dialog window, this event
1601 * will be delayed after the dialog window is closed and the widget is finally inserted into the document.
1602 *
1603 * **Note**: If your widget does not use automatic dialog window binding (i.e. you open the dialog window manually)
1604 * or another situation in which the widget wrapper is not attached to document at the time when it is
1605 * initialized occurs, you need to take care of firing {@link #event-ready} yourself.
1606 *
1607 * See also {@link #property-ready} and {@link #property-inited} properties, and
1608 * {@link #isReady} and {@link #isInited} methods.
1609 *
1610 * @event ready
1611 */
1612
1613 /**
1614 * An event fired when a widget is about to be destroyed, but before it is
1615 * fully torn down.
1616 *
1617 * @event destroy
1618 */
1619
1620 /**
1621 * An event fired when a widget is focused.
1622 *
1623 * Widget can be focused by executing {@link #method-focus}.
1624 *
1625 * @event focus
1626 */
1627
1628 /**
1629 * An event fired when a widget is blurred.
1630 *
1631 * @event blur
1632 */
1633
1634 /**
1635 * An event fired when a widget is selected.
1636 *
1637 * @event select
1638 */
1639
1640 /**
1641 * An event fired when a widget is deselected.
1642 *
1643 * @event deselect
1644 */
1645
1646 /**
1647 * An event fired by the {@link #method-edit} method. It can be canceled
1648 * in order to stop the default action (opening a dialog window and/or
1649 * {@link CKEDITOR.plugins.widget.repository#finalizeCreation finalizing widget creation}).
1650 *
1651 * @event edit
1652 * @param data
1653 * @param {String} data.dialog Defaults to {@link CKEDITOR.plugins.widget.definition#dialog}
1654 * and can be changed or set by the listener.
1655 */
1656
1657 /**
1658 * An event fired when a dialog window for widget editing is opened.
1659 * This event can be canceled in order to handle the editing dialog in a custom manner.
1660 *
1661 * @event dialog
1662 * @param {CKEDITOR.dialog} data The opened dialog window instance.
1663 */
1664
1665 /**
1666 * An event fired when a key is pressed on a focused widget.
1667 * This event is forwarded from the {@link CKEDITOR.editor#key} event and
1668 * has the ability to block editor keystrokes if it is canceled.
1669 *
1670 * @event key
1671 * @param data
1672 * @param {Number} data.keyCode A number representing the key code (or combination).
1673 */
1674
1675 /**
1676 * An event fired when a widget is double clicked.
1677 *
1678 * **Note:** If a default editing action is executed on double click (i.e. a widget has a
1679 * {@link CKEDITOR.plugins.widget.definition#dialog dialog} defined and the {@link #event-doubleclick} event was not
1680 * canceled), this event will be automatically canceled, so a listener added with the default priority (10)
1681 * will not be executed. Use a listener with low priority (e.g. 5) to be sure that it will be executed.
1682 *
1683 * widget.on( 'doubleclick', function( evt ) {
1684 * console.log( 'widget#doubleclick' );
1685 * }, null, null, 5 );
1686 *
1687 * If your widget handles double click in a special way (so the default editing action is not executed),
1688 * make sure you cancel this event, because otherwise it will be propagated to {@link CKEDITOR.editor#doubleclick}
1689 * and another feature may step in (e.g. a Link dialog window may be opened if your widget was inside a link).
1690 *
1691 * @event doubleclick
1692 * @param data
1693 * @param {CKEDITOR.dom.element} data.element The double-clicked element.
1694 */
1695
1696 /**
1697 * An event fired when the context menu is opened for a widget.
1698 *
1699 * @event contextMenu
1700 * @param data The object containing context menu options to be added
1701 * for this widget. See {@link CKEDITOR.plugins.contextMenu#addListener}.
1702 */
1703
1704 /**
1705 * An event fired when the widget data changed. See the {@link #setData} method and the {@link #property-data} property.
1706 *
1707 * @event data
1708 */
1709
1710
1711
1712 /**
1713 * The wrapper class for editable elements inside widgets.
1714 *
1715 * Do not use directly. Use {@link CKEDITOR.plugins.widget.definition#editables} or
1716 * {@link CKEDITOR.plugins.widget#initEditable}.
1717 *
1718 * @class CKEDITOR.plugins.widget.nestedEditable
1719 * @extends CKEDITOR.dom.element
1720 * @constructor
1721 * @param {CKEDITOR.editor} editor
1722 * @param {CKEDITOR.dom.element} element
1723 * @param config
1724 * @param {CKEDITOR.filter} [config.filter]
1725 */
1726 function NestedEditable( editor, element, config ) {
1727 // Call the base constructor.
1728 CKEDITOR.dom.element.call( this, element.$ );
1729 this.editor = editor;
1730 this._ = {};
1731 var filter = this.filter = config.filter;
1732
1733 // If blockless editable - always use BR mode.
1734 if ( !CKEDITOR.dtd[ this.getName() ].p )
1735 this.enterMode = this.shiftEnterMode = CKEDITOR.ENTER_BR;
1736 else {
1737 this.enterMode = filter ? filter.getAllowedEnterMode( editor.enterMode ) : editor.enterMode;
1738 this.shiftEnterMode = filter ? filter.getAllowedEnterMode( editor.shiftEnterMode, true ) : editor.shiftEnterMode;
1739 }
1740 }
1741
1742 NestedEditable.prototype = CKEDITOR.tools.extend( CKEDITOR.tools.prototypedCopy( CKEDITOR.dom.element.prototype ), {
1743 /**
1744 * Sets the editable data. The data will be passed through the {@link CKEDITOR.editor#dataProcessor}
1745 * and the {@link CKEDITOR.editor#filter}. This ensures that the data was filtered and prepared to be
1746 * edited like the {@link CKEDITOR.editor#method-setData editor data}.
1747 *
1748 * Before content is changed, all nested widgets are destroyed. Afterwards, after new content is loaded,
1749 * all nested widgets are initialized.
1750 *
1751 * @param {String} data
1752 */
1753 setData: function( data ) {
1754 // For performance reasons don't call destroyAll when initializing a nested editable,
1755 // because there are no widgets inside.
1756 if ( !this._.initialSetData ) {
1757 // Destroy all nested widgets before setting data.
1758 this.editor.widgets.destroyAll( false, this );
1759 }
1760 this._.initialSetData = false;
1761
1762 data = this.editor.dataProcessor.toHtml( data, {
1763 context: this.getName(),
1764 filter: this.filter,
1765 enterMode: this.enterMode
1766 } );
1767 this.setHtml( data );
1768
1769 this.editor.widgets.initOnAll( this );
1770 },
1771
1772 /**
1773 * Gets the editable data. Like {@link #setData}, this method will process and filter the data.
1774 *
1775 * @returns {String}
1776 */
1777 getData: function() {
1778 return this.editor.dataProcessor.toDataFormat( this.getHtml(), {
1779 context: this.getName(),
1780 filter: this.filter,
1781 enterMode: this.enterMode
1782 } );
1783 }
1784 } );
1785
1786 /**
1787 * The editor instance.
1788 *
1789 * @readonly
1790 * @property {CKEDITOR.editor} editor
1791 */
1792
1793 /**
1794 * The filter instance if allowed content rules were defined.
1795 *
1796 * @readonly
1797 * @property {CKEDITOR.filter} filter
1798 */
1799
1800 /**
1801 * The enter mode active in this editable.
1802 * It is determined from editable's name (whether it is a blockless editable),
1803 * its allowed content rules (if defined) and the default editor's mode.
1804 *
1805 * @readonly
1806 * @property {Number} enterMode
1807 */
1808
1809 /**
1810 * The shift enter move active in this editable.
1811 *
1812 * @readonly
1813 * @property {Number} shiftEnterMode
1814 */
1815
1816
1817 //
1818 // REPOSITORY helpers -----------------------------------------------------
1819 //
1820
1821 function addWidgetButtons( editor ) {
1822 var widgets = editor.widgets.registered,
1823 widget,
1824 widgetName,
1825 widgetButton;
1826
1827 for ( widgetName in widgets ) {
1828 widget = widgets[ widgetName ];
1829
1830 // Create button if defined.
1831 widgetButton = widget.button;
1832 if ( widgetButton && editor.ui.addButton ) {
1833 editor.ui.addButton( CKEDITOR.tools.capitalize( widget.name, true ), {
1834 label: widgetButton,
1835 command: widget.name,
1836 toolbar: 'insert,10'
1837 } );
1838 }
1839 }
1840 }
1841
1842 // Create a command creating and editing widget.
1843 //
1844 // @param editor
1845 // @param {CKEDITOR.plugins.widget.definition} widgetDef
1846 function addWidgetCommand( editor, widgetDef ) {
1847 editor.addCommand( widgetDef.name, {
1848 exec: function( editor, commandData ) {
1849 var focused = editor.widgets.focused;
1850 // If a widget of the same type is focused, start editing.
1851 if ( focused && focused.name == widgetDef.name )
1852 focused.edit();
1853 // Otherwise...
1854 // ... use insert method is was defined.
1855 else if ( widgetDef.insert )
1856 widgetDef.insert();
1857 // ... or create a brand-new widget from template.
1858 else if ( widgetDef.template ) {
1859 var defaults = typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults,
1860 element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( defaults ) ),
1861 instance,
1862 wrapper = editor.widgets.wrapElement( element, widgetDef.name ),
1863 temp = new CKEDITOR.dom.documentFragment( wrapper.getDocument() );
1864
1865 // Append wrapper to a temporary document. This will unify the environment
1866 // in which #data listeners work when creating and editing widget.
1867 temp.append( wrapper );
1868 instance = editor.widgets.initOn( element, widgetDef, commandData && commandData.startupData );
1869
1870 // Instance could be destroyed during initialization.
1871 // In this case finalize creation if some new widget
1872 // was left in temporary document fragment.
1873 if ( !instance ) {
1874 finalizeCreation();
1875 return;
1876 }
1877
1878 // Listen on edit to finalize widget insertion.
1879 //
1880 // * If dialog was set, then insert widget after dialog was successfully saved or destroy this
1881 // temporary instance.
1882 // * If dialog wasn't set and edit wasn't canceled, insert widget.
1883 var editListener = instance.once( 'edit', function( evt ) {
1884 if ( evt.data.dialog ) {
1885 instance.once( 'dialog', function( evt ) {
1886 var dialog = evt.data,
1887 okListener,
1888 cancelListener;
1889
1890 // Finalize creation AFTER (20) new data was set.
1891 okListener = dialog.once( 'ok', finalizeCreation, null, null, 20 );
1892
1893 cancelListener = dialog.once( 'cancel', function( evt ) {
1894 if ( !( evt.data && evt.data.hide === false ) ) {
1895 editor.widgets.destroy( instance, true );
1896 }
1897 } );
1898
1899 dialog.once( 'hide', function() {
1900 okListener.removeListener();
1901 cancelListener.removeListener();
1902 } );
1903 } );
1904 } else {
1905 // Dialog hasn't been set, so insert widget now.
1906 finalizeCreation();
1907 }
1908 }, null, null, 999 );
1909
1910 instance.edit();
1911
1912 // Remove listener in case someone canceled it before this
1913 // listener was executed.
1914 editListener.removeListener();
1915 }
1916
1917 function finalizeCreation() {
1918 editor.widgets.finalizeCreation( temp );
1919 }
1920 },
1921
1922 allowedContent: widgetDef.allowedContent,
1923 requiredContent: widgetDef.requiredContent,
1924 contentForms: widgetDef.contentForms,
1925 contentTransformations: widgetDef.contentTransformations
1926 } );
1927 }
1928
1929 function addWidgetProcessors( widgetsRepo, widgetDef ) {
1930 var upcast = widgetDef.upcast,
1931 upcasts,
1932 priority = widgetDef.upcastPriority || 10;
1933
1934 if ( !upcast )
1935 return;
1936
1937 // Multiple upcasts defined in string.
1938 if ( typeof upcast == 'string' ) {
1939 upcasts = upcast.split( ',' );
1940 while ( upcasts.length ) {
1941 addUpcast( widgetDef.upcasts[ upcasts.pop() ], widgetDef.name, priority );
1942 }
1943 }
1944 // Single rule which is automatically activated.
1945 else {
1946 addUpcast( upcast, widgetDef.name, priority );
1947 }
1948
1949 function addUpcast( upcast, name, priority ) {
1950 // Find index of the first higher (in terms of value) priority upcast.
1951 var index = CKEDITOR.tools.getIndex( widgetsRepo._.upcasts, function( element ) {
1952 return element[ 2 ] > priority;
1953 } );
1954 // Add at the end if it is the highest priority so far.
1955 if ( index < 0 ) {
1956 index = widgetsRepo._.upcasts.length;
1957 }
1958
1959 widgetsRepo._.upcasts.splice( index, 0, [ upcast, name, priority ] );
1960 }
1961 }
1962
1963 function blurWidget( widgetsRepo, widget ) {
1964 widgetsRepo.focused = null;
1965
1966 if ( widget.isInited() ) {
1967 var isDirty = widget.editor.checkDirty();
1968
1969 // Widget could be destroyed in the meantime - e.g. data could be set.
1970 widgetsRepo.fire( 'widgetBlurred', { widget: widget } );
1971 widget.setFocused( false );
1972
1973 !isDirty && widget.editor.resetDirty();
1974 }
1975 }
1976
1977 function checkWidgets( evt ) {
1978 var options = evt.data;
1979
1980 if ( this.editor.mode != 'wysiwyg' )
1981 return;
1982
1983 var editable = this.editor.editable(),
1984 instances = this.instances,
1985 newInstances, i, count, wrapper, notYetInitialized;
1986
1987 if ( !editable )
1988 return;
1989
1990 // Remove widgets which have no corresponding elements in DOM.
1991 for ( i in instances ) {
1992 // #13410 Remove widgets that are ready. This prevents from destroying widgets that are during loading process.
1993 if ( instances[ i ].isReady() && !editable.contains( instances[ i ].wrapper ) )
1994 this.destroy( instances[ i ], true );
1995 }
1996
1997 // Init on all (new) if initOnlyNew option was passed.
1998 if ( options && options.initOnlyNew )
1999 newInstances = this.initOnAll();
2000 else {
2001 var wrappers = editable.find( '.cke_widget_wrapper' );
2002 newInstances = [];
2003
2004 // Create widgets on existing wrappers if they do not exists.
2005 for ( i = 0, count = wrappers.count(); i < count; i++ ) {
2006 wrapper = wrappers.getItem( i );
2007 notYetInitialized = !this.getByElement( wrapper, true );
2008
2009 // Check if:
2010 // * there's no instance for this widget
2011 // * wrapper is not inside some temporary element like copybin (#11088)
2012 // * it was a nested widget's wrapper which has been detached from DOM,
2013 // when nested editable has been initialized (it overwrites its innerHTML
2014 // and initializes nested widgets).
2015 if ( notYetInitialized && !findParent( wrapper, isDomTemp ) && editable.contains( wrapper ) ) {
2016 // Add cke_widget_new class because otherwise
2017 // widget will not be created on such wrapper.
2018 wrapper.addClass( 'cke_widget_new' );
2019 newInstances.push( this.initOn( wrapper.getFirst( Widget.isDomWidgetElement ) ) );
2020 }
2021 }
2022 }
2023
2024 // If only single widget was initialized and focusInited was passed, focus it.
2025 if ( options && options.focusInited && newInstances.length == 1 )
2026 newInstances[ 0 ].focus();
2027 }
2028
2029 // Unwraps widget element and clean up element.
2030 //
2031 // This function is used to clean up pasted widgets.
2032 // It should have similar result to widget#destroy plus
2033 // some additional adjustments, specific for pasting.
2034 //
2035 // @param {CKEDITOR.htmlParser.element} el
2036 function cleanUpWidgetElement( el ) {
2037 var parent = el.parent;
2038 if ( parent.type == CKEDITOR.NODE_ELEMENT && parent.attributes[ 'data-cke-widget-wrapper' ] )
2039 parent.replaceWith( el );
2040 }
2041
2042 // Similar to cleanUpWidgetElement, but works on DOM and finds
2043 // widget elements by its own.
2044 //
2045 // Unlike cleanUpWidgetElement it will wrap element back.
2046 //
2047 // @param {CKEDITOR.dom.element} container
2048 function cleanUpAllWidgetElements( widgetsRepo, container ) {
2049 var wrappers = container.find( '.cke_widget_wrapper' ),
2050 wrapper, element,
2051 i = 0,
2052 l = wrappers.count();
2053
2054 for ( ; i < l; ++i ) {
2055 wrapper = wrappers.getItem( i );
2056 element = wrapper.getFirst( Widget.isDomWidgetElement );
2057 // If wrapper contains widget element - unwrap it and wrap again.
2058 if ( element.type == CKEDITOR.NODE_ELEMENT && element.data( 'widget' ) ) {
2059 element.replace( wrapper );
2060 widgetsRepo.wrapElement( element );
2061 } else {
2062 // Otherwise - something is wrong... clean this up.
2063 wrapper.remove();
2064 }
2065 }
2066 }
2067
2068 // Creates {@link CKEDITOR.filter} instance for given widget, editable and rules.
2069 //
2070 // Once filter for widget-editable pair is created it is cached, so the same instance
2071 // will be returned when method is executed again.
2072 //
2073 // @param {String} widgetName
2074 // @param {String} editableName
2075 // @param {CKEDITOR.plugins.widget.nestedEditableDefinition} editableDefinition The nested editable definition.
2076 // @returns {CKEDITOR.filter} Filter instance or `null` if rules are not defined.
2077 // @context CKEDITOR.plugins.widget.repository
2078 function createEditableFilter( widgetName, editableName, editableDefinition ) {
2079 if ( !editableDefinition.allowedContent )
2080 return null;
2081
2082 var editables = this._.filters[ widgetName ];
2083
2084 if ( !editables )
2085 this._.filters[ widgetName ] = editables = {};
2086
2087 var filter = editables[ editableName ];
2088
2089 if ( !filter )
2090 editables[ editableName ] = filter = new CKEDITOR.filter( editableDefinition.allowedContent );
2091
2092 return filter;
2093 }
2094
2095 // Creates an iterator function which when executed on all
2096 // elements in DOM tree will gather elements that should be wrapped
2097 // and initialized as widgets.
2098 function createUpcastIterator( widgetsRepo ) {
2099 var toBeWrapped = [],
2100 upcasts = widgetsRepo._.upcasts,
2101 upcastCallbacks = widgetsRepo._.upcastCallbacks;
2102
2103 return {
2104 toBeWrapped: toBeWrapped,
2105
2106 iterator: function( element ) {
2107 var upcast, upcasted,
2108 data,
2109 i,
2110 upcastsLength,
2111 upcastCallbacksLength;
2112
2113 // Wrapper found - find widget element, add it to be
2114 // cleaned up (unwrapped) and wrapped and stop iterating in this branch.
2115 if ( 'data-cke-widget-wrapper' in element.attributes ) {
2116 element = element.getFirst( Widget.isParserWidgetElement );
2117
2118 if ( element )
2119 toBeWrapped.push( [ element ] );
2120
2121 // Do not iterate over descendants.
2122 return false;
2123 }
2124 // Widget element found - add it to be cleaned up (just in case)
2125 // and wrapped and stop iterating in this branch.
2126 else if ( 'data-widget' in element.attributes ) {
2127 toBeWrapped.push( [ element ] );
2128
2129 // Do not iterate over descendants.
2130 return false;
2131 }
2132 else if ( ( upcastsLength = upcasts.length ) ) {
2133 // Ignore elements with data-cke-widget-upcasted to avoid multiple upcasts (#11533).
2134 // Do not iterate over descendants.
2135 if ( element.attributes[ 'data-cke-widget-upcasted' ] )
2136 return false;
2137
2138 // Check element with upcast callbacks first.
2139 // If any of them return false abort upcasting.
2140 for ( i = 0, upcastCallbacksLength = upcastCallbacks.length; i < upcastCallbacksLength; ++i ) {
2141 if ( upcastCallbacks[ i ]( element ) === false )
2142 return;
2143 // Return nothing in order to continue iterating over ascendants.
2144 // See http://dev.ckeditor.com/ticket/11186#comment:6
2145 }
2146
2147 for ( i = 0; i < upcastsLength; ++i ) {
2148 upcast = upcasts[ i ];
2149 data = {};
2150
2151 if ( ( upcasted = upcast[ 0 ]( element, data ) ) ) {
2152 // If upcast function returned element, upcast this one.
2153 // It can be e.g. a new element wrapping the original one.
2154 if ( upcasted instanceof CKEDITOR.htmlParser.element )
2155 element = upcasted;
2156
2157 // Set initial data attr with data from upcast method.
2158 element.attributes[ 'data-cke-widget-data' ] = encodeURIComponent( JSON.stringify( data ) );
2159 element.attributes[ 'data-cke-widget-upcasted' ] = 1;
2160
2161 toBeWrapped.push( [ element, upcast[ 1 ] ] );
2162
2163 // Do not iterate over descendants.
2164 return false;
2165 }
2166 }
2167 }
2168 }
2169 };
2170 }
2171
2172 // Finds a first parent that matches query.
2173 //
2174 // @param {CKEDITOR.dom.element} element
2175 // @param {Function} query
2176 function findParent( element, query ) {
2177 var parent = element;
2178
2179 while ( ( parent = parent.getParent() ) ) {
2180 if ( query( parent ) )
2181 return true;
2182 }
2183 return false;
2184 }
2185
2186 function getWrapperAttributes( inlineWidget ) {
2187 return {
2188 // tabindex="-1" means that it can receive focus by code.
2189 tabindex: -1,
2190 contenteditable: 'false',
2191 'data-cke-widget-wrapper': 1,
2192 'data-cke-filter': 'off',
2193 // Class cke_widget_new marks widgets which haven't been initialized yet.
2194 'class': 'cke_widget_wrapper cke_widget_new cke_widget_' +
2195 ( inlineWidget ? 'inline' : 'block' )
2196 };
2197 }
2198
2199 // Inserts element at given index.
2200 // It will check DTD and split ancestor elements up to the first
2201 // that can contain this element.
2202 //
2203 // @param {CKEDITOR.htmlParser.element} parent
2204 // @param {Number} index
2205 // @param {CKEDITOR.htmlParser.element} element
2206 function insertElement( parent, index, element ) {
2207 // Do not split doc fragment...
2208 if ( parent.type == CKEDITOR.NODE_ELEMENT ) {
2209 var parentAllows = CKEDITOR.dtd[ parent.name ];
2210 // Parent element is known (included in DTD) and cannot contain
2211 // this element.
2212 if ( parentAllows && !parentAllows[ element.name ] ) {
2213 var parent2 = parent.split( index ),
2214 parentParent = parent.parent;
2215
2216 // Element will now be inserted at right parent's index.
2217 index = parent2.getIndex();
2218
2219 // If left part of split is empty - remove it.
2220 if ( !parent.children.length ) {
2221 index -= 1;
2222 parent.remove();
2223 }
2224
2225 // If right part of split is empty - remove it.
2226 if ( !parent2.children.length )
2227 parent2.remove();
2228
2229 // Try inserting as grandpas' children.
2230 return insertElement( parentParent, index, element );
2231 }
2232 }
2233
2234 // Finally we can add this element.
2235 parent.add( element, index );
2236 }
2237
2238 // Checks whether for the given widget definition and element widget should be created in inline or block mode.
2239 //
2240 // See also: {@link CKEDITOR.plugins.widget.definition#inline} and {@link CKEDITOR.plugins.widget#element}.
2241 //
2242 // @param {CKEDITOR.plugins.widget.definition} widgetDef The widget definition.
2243 // @param {String} elementName The name of the widget element.
2244 // @returns {Boolean}
2245 function isWidgetInline( widgetDef, elementName ) {
2246 return typeof widgetDef.inline == 'boolean' ? widgetDef.inline : !!CKEDITOR.dtd.$inline[ elementName ];
2247 }
2248
2249 // @param {CKEDITOR.dom.element}
2250 // @returns {Boolean}
2251 function isDomTemp( element ) {
2252 return element.hasAttribute( 'data-cke-temp' );
2253 }
2254
2255 function onEditableKey( widget, keyCode ) {
2256 var focusedEditable = widget.focusedEditable,
2257 range;
2258
2259 // CTRL+A.
2260 if ( keyCode == CKEDITOR.CTRL + 65 ) {
2261 var bogus = focusedEditable.getBogus();
2262
2263 range = widget.editor.createRange();
2264 range.selectNodeContents( focusedEditable );
2265 // Exclude bogus if exists.
2266 if ( bogus )
2267 range.setEndAt( bogus, CKEDITOR.POSITION_BEFORE_START );
2268
2269 range.select();
2270 // Cancel event - block default.
2271 return false;
2272 }
2273 // DEL or BACKSPACE.
2274 else if ( keyCode == 8 || keyCode == 46 ) {
2275 var ranges = widget.editor.getSelection().getRanges();
2276
2277 range = ranges[ 0 ];
2278
2279 // Block del or backspace if at editable's boundary.
2280 return !( ranges.length == 1 && range.collapsed &&
2281 range.checkBoundaryOfElement( focusedEditable, CKEDITOR[ keyCode == 8 ? 'START' : 'END' ] ) );
2282 }
2283 }
2284
2285 function setFocusedEditable( widgetsRepo, widget, editableElement, offline ) {
2286 var editor = widgetsRepo.editor;
2287
2288 editor.fire( 'lockSnapshot' );
2289
2290 if ( editableElement ) {
2291 var editableName = editableElement.data( 'cke-widget-editable' ),
2292 editableInstance = widget.editables[ editableName ];
2293
2294 widgetsRepo.widgetHoldingFocusedEditable = widget;
2295 widget.focusedEditable = editableInstance;
2296 editableElement.addClass( 'cke_widget_editable_focused' );
2297
2298 if ( editableInstance.filter )
2299 editor.setActiveFilter( editableInstance.filter );
2300 editor.setActiveEnterMode( editableInstance.enterMode, editableInstance.shiftEnterMode );
2301 } else {
2302 if ( !offline )
2303 widget.focusedEditable.removeClass( 'cke_widget_editable_focused' );
2304
2305 widget.focusedEditable = null;
2306 widgetsRepo.widgetHoldingFocusedEditable = null;
2307 editor.setActiveFilter( null );
2308 editor.setActiveEnterMode( null, null );
2309 }
2310
2311 editor.fire( 'unlockSnapshot' );
2312 }
2313
2314 function setupContextMenu( editor ) {
2315 if ( !editor.contextMenu )
2316 return;
2317
2318 editor.contextMenu.addListener( function( element ) {
2319 var widget = editor.widgets.getByElement( element, true );
2320
2321 if ( widget )
2322 return widget.fire( 'contextMenu', {} );
2323 } );
2324 }
2325
2326 // And now we've got two problems - original problem and RegExp.
2327 // Some softeners:
2328 // * FF tends to copy all blocks up to the copybin container.
2329 // * IE tends to copy only the copybin, without its container.
2330 // * We use spans on IE and blockless editors, but divs in other cases.
2331 var pasteReplaceRegex = new RegExp(
2332 '^' +
2333 '(?:<(?:div|span)(?: data-cke-temp="1")?(?: id="cke_copybin")?(?: data-cke-temp="1")?>)?' +
2334 '(?:<(?:div|span)(?: style="[^"]+")?>)?' +
2335 '<span [^>]*data-cke-copybin-start="1"[^>]*>.?</span>([\\s\\S]+)<span [^>]*data-cke-copybin-end="1"[^>]*>.?</span>' +
2336 '(?:</(?:div|span)>)?' +
2337 '(?:</(?:div|span)>)?' +
2338 '$',
2339 // IE8 prefers uppercase when browsers stick to lowercase HTML (#13460).
2340 'i'
2341 );
2342
2343 function pasteReplaceFn( match, wrapperHtml ) {
2344 // Avoid polluting pasted data with any whitspaces,
2345 // what's going to break check whether only one widget was pasted.
2346 return CKEDITOR.tools.trim( wrapperHtml );
2347 }
2348
2349 function setupDragAndDrop( widgetsRepo ) {
2350 var editor = widgetsRepo.editor,
2351 lineutils = CKEDITOR.plugins.lineutils;
2352
2353 // These listeners handle inline and block widgets drag and drop.
2354 // The only thing we need to do to make block widgets custom drag and drop functionality
2355 // is to fire those events with the right properties (like the target which must be the drag handle).
2356 editor.on( 'dragstart', function( evt ) {
2357 var target = evt.data.target;
2358
2359 if ( Widget.isDomDragHandler( target ) ) {
2360 var widget = widgetsRepo.getByElement( target );
2361
2362 evt.data.dataTransfer.setData( 'cke/widget-id', widget.id );
2363
2364 // IE needs focus.
2365 editor.focus();
2366
2367 // and widget need to be focused on drag start (#12172#comment:10).
2368 widget.focus();
2369 }
2370 } );
2371
2372 editor.on( 'drop', function( evt ) {
2373 var dataTransfer = evt.data.dataTransfer,
2374 id = dataTransfer.getData( 'cke/widget-id' ),
2375 transferType = dataTransfer.getTransferType( editor ),
2376 dragRange = editor.createRange(),
2377 sourceWidget;
2378
2379 // Disable cross-editor drag & drop for widgets - #13599.
2380 if ( id !== '' && transferType === CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
2381 evt.cancel();
2382 return;
2383 }
2384
2385 if ( id === '' || transferType != CKEDITOR.DATA_TRANSFER_INTERNAL ) {
2386 return;
2387 }
2388
2389 sourceWidget = widgetsRepo.instances[ id ];
2390 if ( !sourceWidget ) {
2391 return;
2392 }
2393
2394 dragRange.setStartBefore( sourceWidget.wrapper );
2395 dragRange.setEndAfter( sourceWidget.wrapper );
2396 evt.data.dragRange = dragRange;
2397
2398 // [IE8-9] Reset state of the clipboard#fixSplitNodesAfterDrop fix because by setting evt.data.dragRange
2399 // (see above) after drop happened we do not need it. That fix is needed only if dragRange was created
2400 // before drop (before text node was split).
2401 delete CKEDITOR.plugins.clipboard.dragStartContainerChildCount;
2402 delete CKEDITOR.plugins.clipboard.dragEndContainerChildCount;
2403
2404 evt.data.dataTransfer.setData( 'text/html', editor.editable().getHtmlFromRange( dragRange ).getHtml() );
2405 editor.widgets.destroy( sourceWidget, true );
2406 } );
2407
2408 editor.on( 'contentDom', function() {
2409 var editable = editor.editable();
2410
2411 // Register Lineutils's utilities as properties of repo.
2412 CKEDITOR.tools.extend( widgetsRepo, {
2413 finder: new lineutils.finder( editor, {
2414 lookups: {
2415 // Element is block but not list item and not in nested editable.
2416 'default': function( el ) {
2417 if ( el.is( CKEDITOR.dtd.$listItem ) )
2418 return;
2419
2420 if ( !el.is( CKEDITOR.dtd.$block ) )
2421 return;
2422
2423 // Allow drop line inside, but never before or after nested editable (#12006).
2424 if ( Widget.isDomNestedEditable( el ) )
2425 return;
2426
2427 // Do not allow droping inside the widget being dragged (#13397).
2428 if ( widgetsRepo._.draggedWidget.wrapper.contains( el ) ) {
2429 return;
2430 }
2431
2432 // If element is nested editable, make sure widget can be dropped there (#12006).
2433 var nestedEditable = Widget.getNestedEditable( editable, el );
2434 if ( nestedEditable ) {
2435 var draggedWidget = widgetsRepo._.draggedWidget;
2436
2437 // Don't let the widget to be dropped into its own nested editable.
2438 if ( widgetsRepo.getByElement( nestedEditable ) == draggedWidget )
2439 return;
2440
2441 var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ],
2442 draggedRequiredContent = draggedWidget.requiredContent;
2443
2444 // There will be no relation if the filter of nested editable does not allow
2445 // requiredContent of dragged widget.
2446 if ( filter && draggedRequiredContent && !filter.check( draggedRequiredContent ) )
2447 return;
2448 }
2449
2450 return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER;
2451 }
2452 }
2453 } ),
2454 locator: new lineutils.locator( editor ),
2455 liner: new lineutils.liner( editor, {
2456 lineStyle: {
2457 cursor: 'move !important',
2458 'border-top-color': '#666'
2459 },
2460 tipLeftStyle: {
2461 'border-left-color': '#666'
2462 },
2463 tipRightStyle: {
2464 'border-right-color': '#666'
2465 }
2466 } )
2467 }, true );
2468 } );
2469 }
2470
2471 // Setup mouse observer which will trigger:
2472 // * widget focus on widget click,
2473 // * widget#doubleclick forwarded from editor#doubleclick.
2474 function setupMouseObserver( widgetsRepo ) {
2475 var editor = widgetsRepo.editor;
2476
2477 editor.on( 'contentDom', function() {
2478 var editable = editor.editable(),
2479 evtRoot = editable.isInline() ? editable : editor.document,
2480 widget,
2481 mouseDownOnDragHandler;
2482
2483 editable.attachListener( evtRoot, 'mousedown', function( evt ) {
2484 var target = evt.data.getTarget();
2485
2486 // #10887 Clicking scrollbar in IE8 will invoke event with empty target object.
2487 if ( !target.type )
2488 return false;
2489
2490 widget = widgetsRepo.getByElement( target );
2491 mouseDownOnDragHandler = 0; // Reset.
2492
2493 // Widget was clicked, but not editable nested in it.
2494 if ( widget ) {
2495 // Ignore mousedown on drag and drop handler if the widget is inline.
2496 // Block widgets are handled by Lineutils.
2497 if ( widget.inline && target.type == CKEDITOR.NODE_ELEMENT && target.hasAttribute( 'data-cke-widget-drag-handler' ) ) {
2498 mouseDownOnDragHandler = 1;
2499
2500 // When drag handler is pressed we have to clear current selection if it wasn't already on this widget.
2501 // Otherwise, the selection may be in a fillingChar, which prevents dragging a widget. (#13284, see comment 8 and 9.)
2502 if ( widgetsRepo.focused != widget )
2503 editor.getSelection().removeAllRanges();
2504
2505 return;
2506 }
2507
2508 if ( !Widget.getNestedEditable( widget.wrapper, target ) ) {
2509 evt.data.preventDefault();
2510 if ( !CKEDITOR.env.ie )
2511 widget.focus();
2512 } else {
2513 // Reset widget so mouseup listener is not confused.
2514 widget = null;
2515 }
2516 }
2517 } );
2518
2519 // Focus widget on mouseup if mousedown was fired on drag handler.
2520 // Note: mouseup won't be fired at all if widget was dragged and dropped, so
2521 // this code will be executed only when drag handler was clicked.
2522 editable.attachListener( evtRoot, 'mouseup', function() {
2523 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null).
2524 if ( mouseDownOnDragHandler && widget && widget.wrapper ) {
2525 mouseDownOnDragHandler = 0;
2526 widget.focus();
2527 }
2528 } );
2529
2530 // On IE it is not enough to block mousedown. If widget wrapper (element with
2531 // contenteditable=false attribute) is clicked directly (it is a target),
2532 // then after mouseup/click IE will select that element.
2533 // It is not possible to prevent that default action,
2534 // so we force fake selection after everything happened.
2535 if ( CKEDITOR.env.ie ) {
2536 editable.attachListener( evtRoot, 'mouseup', function() {
2537 setTimeout( function() {
2538 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null) and
2539 // in editable contains widget (it could be dragged and removed).
2540 if ( widget && widget.wrapper && editable.contains( widget.wrapper ) ) {
2541 widget.focus();
2542 widget = null;
2543 }
2544 } );
2545 } );
2546 }
2547 } );
2548
2549 editor.on( 'doubleclick', function( evt ) {
2550 var widget = widgetsRepo.getByElement( evt.data.element );
2551
2552 // Not in widget or in nested editable.
2553 if ( !widget || Widget.getNestedEditable( widget.wrapper, evt.data.element ) )
2554 return;
2555
2556 return widget.fire( 'doubleclick', { element: evt.data.element } );
2557 }, null, null, 1 );
2558 }
2559
2560 // Setup editor#key observer which will forward it
2561 // to focused widget.
2562 function setupKeyboardObserver( widgetsRepo ) {
2563 var editor = widgetsRepo.editor;
2564
2565 editor.on( 'key', function( evt ) {
2566 var focused = widgetsRepo.focused,
2567 widgetHoldingFocusedEditable = widgetsRepo.widgetHoldingFocusedEditable,
2568 ret;
2569
2570 if ( focused )
2571 ret = focused.fire( 'key', { keyCode: evt.data.keyCode } );
2572 else if ( widgetHoldingFocusedEditable )
2573 ret = onEditableKey( widgetHoldingFocusedEditable, evt.data.keyCode );
2574
2575 return ret;
2576 }, null, null, 1 );
2577 }
2578
2579 // Setup copybin on native copy and cut events in order to handle copy and cut commands
2580 // if user accepted security alert on IEs.
2581 // Note: when copying or cutting using keystroke, copySingleWidget will be first executed
2582 // by the keydown listener. Conflict between two calls will be resolved by copy_bin existence check.
2583 function setupNativeCutAndCopy( widgetsRepo ) {
2584 var editor = widgetsRepo.editor;
2585
2586 editor.on( 'contentDom', function() {
2587 var editable = editor.editable();
2588
2589 editable.attachListener( editable, 'copy', eventListener );
2590 editable.attachListener( editable, 'cut', eventListener );
2591 } );
2592
2593 function eventListener( evt ) {
2594 if ( widgetsRepo.focused )
2595 copySingleWidget( widgetsRepo.focused, evt.name == 'cut' );
2596 }
2597 }
2598
2599 // Setup selection observer which will trigger:
2600 // * widget select & focus on selection change,
2601 // * nested editable focus (related properites and classes) on selection change,
2602 // * deselecting and blurring all widgets on data,
2603 // * blurring widget on editor blur.
2604 function setupSelectionObserver( widgetsRepo ) {
2605 var editor = widgetsRepo.editor;
2606
2607 editor.on( 'selectionCheck', function() {
2608 widgetsRepo.fire( 'checkSelection' );
2609 } );
2610
2611 widgetsRepo.on( 'checkSelection', widgetsRepo.checkSelection, widgetsRepo );
2612
2613 editor.on( 'selectionChange', function( evt ) {
2614 var nestedEditable = Widget.getNestedEditable( editor.editable(), evt.data.selection.getStartElement() ),
2615 newWidget = nestedEditable && widgetsRepo.getByElement( nestedEditable ),
2616 oldWidget = widgetsRepo.widgetHoldingFocusedEditable;
2617
2618 if ( oldWidget ) {
2619 if ( oldWidget !== newWidget || !oldWidget.focusedEditable.equals( nestedEditable ) ) {
2620 setFocusedEditable( widgetsRepo, oldWidget, null );
2621
2622 if ( newWidget && nestedEditable )
2623 setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2624 }
2625 }
2626 // It may happen that there's no widget even if editable was found -
2627 // e.g. if selection was automatically set in editable although widget wasn't initialized yet.
2628 else if ( newWidget && nestedEditable ) {
2629 setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2630 }
2631 } );
2632
2633 // Invalidate old widgets early - immediately on dataReady.
2634 editor.on( 'dataReady', function() {
2635 // Deselect and blur all widgets.
2636 stateUpdater( widgetsRepo ).commit();
2637 } );
2638
2639 editor.on( 'blur', function() {
2640 var widget;
2641
2642 if ( ( widget = widgetsRepo.focused ) )
2643 blurWidget( widgetsRepo, widget );
2644
2645 if ( ( widget = widgetsRepo.widgetHoldingFocusedEditable ) )
2646 setFocusedEditable( widgetsRepo, widget, null );
2647 } );
2648 }
2649
2650 // Set up actions like:
2651 // * processing in toHtml/toDataFormat,
2652 // * pasting handling,
2653 // * insertion handling,
2654 // * editable reload handling (setData, mode switch, undo/redo),
2655 // * DOM invalidation handling,
2656 // * widgets checks.
2657 function setupWidgetsLifecycle( widgetsRepo ) {
2658 setupWidgetsLifecycleStart( widgetsRepo );
2659 setupWidgetsLifecycleEnd( widgetsRepo );
2660
2661 widgetsRepo.on( 'checkWidgets', checkWidgets );
2662 widgetsRepo.editor.on( 'contentDomInvalidated', widgetsRepo.checkWidgets, widgetsRepo );
2663 }
2664
2665 function setupWidgetsLifecycleEnd( widgetsRepo ) {
2666 var editor = widgetsRepo.editor,
2667 downcastingSessions = {};
2668
2669 // Listen before htmlDP#htmlFilter is applied to cache all widgets, because we'll
2670 // loose data-cke-* attributes.
2671 editor.on( 'toDataFormat', function( evt ) {
2672 // To avoid conflicts between htmlDP#toDF calls done at the same time
2673 // (e.g. nestedEditable#getData called during downcasting some widget)
2674 // mark every toDataFormat event chain with the downcasting session id.
2675 var id = CKEDITOR.tools.getNextNumber(),
2676 toBeDowncasted = [];
2677 evt.data.downcastingSessionId = id;
2678 downcastingSessions[ id ] = toBeDowncasted;
2679
2680 evt.data.dataValue.forEach( function( element ) {
2681 var attrs = element.attributes,
2682 widget, widgetElement;
2683
2684 // Wrapper.
2685 // Perform first part of downcasting (cleanup) and cache widgets,
2686 // because after applying DP's filter all data-cke-* attributes will be gone.
2687 if ( 'data-cke-widget-id' in attrs ) {
2688 widget = widgetsRepo.instances[ attrs[ 'data-cke-widget-id' ] ];
2689 if ( widget ) {
2690 widgetElement = element.getFirst( Widget.isParserWidgetElement );
2691 toBeDowncasted.push( {
2692 wrapper: element,
2693 element: widgetElement,
2694 widget: widget,
2695 editables: {}
2696 } );
2697
2698 // If widget did not have data-cke-widget attribute before upcasting remove it.
2699 if ( widgetElement.attributes[ 'data-cke-widget-keep-attr' ] != '1' )
2700 delete widgetElement.attributes[ 'data-widget' ];
2701 }
2702 }
2703 // Nested editable.
2704 else if ( 'data-cke-widget-editable' in attrs ) {
2705 // Save the reference to this nested editable in the closest widget to be downcasted.
2706 // Nested editables are downcasted in the successive toDataFormat to create an opportunity
2707 // for dataFilter's "excludeNestedEditable" option to do its job (that option relies on
2708 // contenteditable="true" attribute) (#11372).
2709 toBeDowncasted[ toBeDowncasted.length - 1 ].editables[ attrs[ 'data-cke-widget-editable' ] ] = element;
2710
2711 // Don't check children - there won't be next wrapper or nested editable which we
2712 // should process in this session.
2713 return false;
2714 }
2715 }, CKEDITOR.NODE_ELEMENT, true );
2716 }, null, null, 8 );
2717
2718 // Listen after dataProcessor.htmlFilter and ACF were applied
2719 // so wrappers securing widgets' contents are removed after all filtering was done.
2720 editor.on( 'toDataFormat', function( evt ) {
2721 // Ignore some unmarked sessions.
2722 if ( !evt.data.downcastingSessionId )
2723 return;
2724
2725 var toBeDowncasted = downcastingSessions[ evt.data.downcastingSessionId ],
2726 toBe, widget, widgetElement, retElement, editableElement, e;
2727
2728 while ( ( toBe = toBeDowncasted.shift() ) ) {
2729 widget = toBe.widget;
2730 widgetElement = toBe.element;
2731 retElement = widget._.downcastFn && widget._.downcastFn.call( widget, widgetElement );
2732
2733 // Replace nested editables' content with their output data.
2734 for ( e in toBe.editables ) {
2735 editableElement = toBe.editables[ e ];
2736
2737 delete editableElement.attributes.contenteditable;
2738 editableElement.setHtml( widget.editables[ e ].getData() );
2739 }
2740
2741 // Returned element always defaults to widgetElement.
2742 if ( !retElement )
2743 retElement = widgetElement;
2744
2745 toBe.wrapper.replaceWith( retElement );
2746 }
2747 }, null, null, 13 );
2748
2749
2750 editor.on( 'contentDomUnload', function() {
2751 widgetsRepo.destroyAll( true );
2752 } );
2753 }
2754
2755 function setupWidgetsLifecycleStart( widgetsRepo ) {
2756 var editor = widgetsRepo.editor,
2757 processedWidgetOnly,
2758 snapshotLoaded;
2759
2760 // Listen after ACF (so data are filtered),
2761 // but before dataProcessor.dataFilter was applied (so we can secure widgets' internals).
2762 editor.on( 'toHtml', function( evt ) {
2763 var upcastIterator = createUpcastIterator( widgetsRepo ),
2764 toBeWrapped;
2765
2766 evt.data.dataValue.forEach( upcastIterator.iterator, CKEDITOR.NODE_ELEMENT, true );
2767
2768 // Clean up and wrap all queued elements.
2769 while ( ( toBeWrapped = upcastIterator.toBeWrapped.pop() ) ) {
2770 cleanUpWidgetElement( toBeWrapped[ 0 ] );
2771 widgetsRepo.wrapElement( toBeWrapped[ 0 ], toBeWrapped[ 1 ] );
2772 }
2773
2774 // Used to determine whether only widget was pasted.
2775 if ( evt.data.protectedWhitespaces ) {
2776 // Whitespaces are protected by wrapping content with spans. Take the middle node only.
2777 processedWidgetOnly = evt.data.dataValue.children.length == 3 &&
2778 Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 1 ] );
2779 } else {
2780 processedWidgetOnly = evt.data.dataValue.children.length == 1 &&
2781 Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 0 ] );
2782 }
2783 }, null, null, 8 );
2784
2785 editor.on( 'dataReady', function() {
2786 // Clean up all widgets loaded from snapshot.
2787 if ( snapshotLoaded )
2788 cleanUpAllWidgetElements( widgetsRepo, editor.editable() );
2789 snapshotLoaded = 0;
2790
2791 // Some widgets were destroyed on contentDomUnload,
2792 // some on loadSnapshot, but that does not include
2793 // e.g. setHtml on inline editor or widgets removed just
2794 // before setting data.
2795 widgetsRepo.destroyAll( true );
2796 widgetsRepo.initOnAll();
2797 } );
2798
2799 // Set flag so dataReady will know that additional
2800 // cleanup is needed, because snapshot containing widgets was loaded.
2801 editor.on( 'loadSnapshot', function( evt ) {
2802 // Primitive but sufficient check which will prevent from executing
2803 // heavier cleanUpAllWidgetElements if not needed.
2804 if ( ( /data-cke-widget/ ).test( evt.data ) )
2805 snapshotLoaded = 1;
2806
2807 widgetsRepo.destroyAll( true );
2808 }, null, null, 9 );
2809
2810 // Handle pasted single widget.
2811 editor.on( 'paste', function( evt ) {
2812 var data = evt.data;
2813
2814 data.dataValue = data.dataValue.replace( pasteReplaceRegex, pasteReplaceFn );
2815
2816 // If drag'n'drop kind of paste into nested editable (data.range), selection is set AFTER
2817 // data is pasted, which means editor has no chance to change activeFilter's context.
2818 // As a result, pasted data is filtered with default editor's filter instead of NE's and
2819 // funny things get inserted. Changing the filter by analysis of the paste range below (#13186).
2820 if ( data.range ) {
2821 // Check if pasting into nested editable.
2822 var nestedEditable = Widget.getNestedEditable( editor.editable(), data.range.startContainer );
2823
2824 if ( nestedEditable ) {
2825 // Retrieve the filter from NE's data and set it active before editor.insertHtml is done
2826 // in clipboard plugin.
2827 var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ];
2828
2829 if ( filter ) {
2830 editor.setActiveFilter( filter );
2831 }
2832 }
2833 }
2834 } );
2835
2836 // Listen with high priority to check widgets after data was inserted.
2837 editor.on( 'afterInsertHtml', function( evt ) {
2838 if ( evt.data.intoRange ) {
2839 widgetsRepo.checkWidgets( { initOnlyNew: true } );
2840 } else {
2841 editor.fire( 'lockSnapshot' );
2842 // Init only new for performance reason.
2843 // Focus inited if only widget was processed.
2844 widgetsRepo.checkWidgets( { initOnlyNew: true, focusInited: processedWidgetOnly } );
2845
2846 editor.fire( 'unlockSnapshot' );
2847 }
2848 } );
2849 }
2850
2851 // Helper for coordinating which widgets should be
2852 // selected/deselected and which one should be focused/blurred.
2853 function stateUpdater( widgetsRepo ) {
2854 var currentlySelected = widgetsRepo.selected,
2855 toBeSelected = [],
2856 toBeDeselected = currentlySelected.slice( 0 ),
2857 focused = null;
2858
2859 return {
2860 select: function( widget ) {
2861 if ( CKEDITOR.tools.indexOf( currentlySelected, widget ) < 0 )
2862 toBeSelected.push( widget );
2863
2864 var index = CKEDITOR.tools.indexOf( toBeDeselected, widget );
2865 if ( index >= 0 )
2866 toBeDeselected.splice( index, 1 );
2867
2868 return this;
2869 },
2870
2871 focus: function( widget ) {
2872 focused = widget;
2873 return this;
2874 },
2875
2876 commit: function() {
2877 var focusedChanged = widgetsRepo.focused !== focused,
2878 widget, isDirty;
2879
2880 widgetsRepo.editor.fire( 'lockSnapshot' );
2881
2882 if ( focusedChanged && ( widget = widgetsRepo.focused ) )
2883 blurWidget( widgetsRepo, widget );
2884
2885 while ( ( widget = toBeDeselected.pop() ) ) {
2886 currentlySelected.splice( CKEDITOR.tools.indexOf( currentlySelected, widget ), 1 );
2887 // Widget could be destroyed in the meantime - e.g. data could be set.
2888 if ( widget.isInited() ) {
2889 isDirty = widget.editor.checkDirty();
2890
2891 widget.setSelected( false );
2892
2893 !isDirty && widget.editor.resetDirty();
2894 }
2895 }
2896
2897 if ( focusedChanged && focused ) {
2898 isDirty = widgetsRepo.editor.checkDirty();
2899
2900 widgetsRepo.focused = focused;
2901 widgetsRepo.fire( 'widgetFocused', { widget: focused } );
2902 focused.setFocused( true );
2903
2904 !isDirty && widgetsRepo.editor.resetDirty();
2905 }
2906
2907 while ( ( widget = toBeSelected.pop() ) ) {
2908 currentlySelected.push( widget );
2909 widget.setSelected( true );
2910 }
2911
2912 widgetsRepo.editor.fire( 'unlockSnapshot' );
2913 }
2914 };
2915 }
2916
2917
2918 //
2919 // WIDGET helpers ---------------------------------------------------------
2920 //
2921
2922 // LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers.
2923 var keystrokesNotBlockedByWidget = { 37: 1, 38: 1, 39: 1, 40: 1, 8: 1, 46: 1 };
2924
2925 // Applies or removes style's classes from widget.
2926 // @param {CKEDITOR.style} style Custom widget style.
2927 // @param {Boolean} apply Whether to apply or remove style.
2928 function applyRemoveStyle( widget, style, apply ) {
2929 var changed = 0,
2930 classes = getStyleClasses( style ),
2931 updatedClasses = widget.data.classes || {},
2932 cl;
2933
2934 // Ee... Something is wrong with this style.
2935 if ( !classes )
2936 return;
2937
2938 // Clone, because we need to break reference.
2939 updatedClasses = CKEDITOR.tools.clone( updatedClasses );
2940
2941 while ( ( cl = classes.pop() ) ) {
2942 if ( apply ) {
2943 if ( !updatedClasses[ cl ] )
2944 changed = updatedClasses[ cl ] = 1;
2945 } else {
2946 if ( updatedClasses[ cl ] ) {
2947 delete updatedClasses[ cl ];
2948 changed = 1;
2949 }
2950 }
2951 }
2952 if ( changed )
2953 widget.setData( 'classes', updatedClasses );
2954 }
2955
2956 function cancel( evt ) {
2957 evt.cancel();
2958 }
2959
2960 function copySingleWidget( widget, isCut ) {
2961 var editor = widget.editor,
2962 doc = editor.document;
2963
2964 // We're still handling previous copy/cut.
2965 // When keystroke is used to copy/cut this will also prevent
2966 // conflict with copySingleWidget called again for native copy/cut event.
2967 if ( doc.getById( 'cke_copybin' ) )
2968 return;
2969
2970 // [IE] Use span for copybin and its container to avoid bug with expanding editable height by
2971 // absolutely positioned element.
2972 var copybinName = ( editor.blockless || CKEDITOR.env.ie ) ? 'span' : 'div',
2973 copybin = doc.createElement( copybinName ),
2974 copybinContainer = doc.createElement( copybinName ),
2975 // IE8 always jumps to the end of document.
2976 needsScrollHack = CKEDITOR.env.ie && CKEDITOR.env.version < 9;
2977
2978 copybinContainer.setAttributes( {
2979 id: 'cke_copybin',
2980 'data-cke-temp': '1'
2981 } );
2982
2983 // Position copybin element outside current viewport.
2984 copybin.setStyles( {
2985 position: 'absolute',
2986 width: '1px',
2987 height: '1px',
2988 overflow: 'hidden'
2989 } );
2990
2991 copybin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-5000px' );
2992
2993 var range = editor.createRange();
2994 range.setStartBefore( widget.wrapper );
2995 range.setEndAfter( widget.wrapper );
2996
2997 copybin.setHtml(
2998 '<span data-cke-copybin-start="1">\u200b</span>' +
2999 editor.editable().getHtmlFromRange( range ).getHtml() +
3000 '<span data-cke-copybin-end="1">\u200b</span>' );
3001
3002 // Save snapshot with the current state.
3003 editor.fire( 'saveSnapshot' );
3004
3005 // Ignore copybin.
3006 editor.fire( 'lockSnapshot' );
3007
3008 copybinContainer.append( copybin );
3009 editor.editable().append( copybinContainer );
3010
3011 var listener1 = editor.on( 'selectionChange', cancel, null, null, 0 ),
3012 listener2 = widget.repository.on( 'checkSelection', cancel, null, null, 0 );
3013
3014 if ( needsScrollHack ) {
3015 var docElement = doc.getDocumentElement().$,
3016 scrollTop = docElement.scrollTop;
3017 }
3018
3019 // Once the clone of the widget is inside of copybin, select
3020 // the entire contents. This selection will be copied by the
3021 // native browser's clipboard system.
3022 range = editor.createRange();
3023 range.selectNodeContents( copybin );
3024 range.select();
3025
3026 if ( needsScrollHack )
3027 docElement.scrollTop = scrollTop;
3028
3029 setTimeout( function() {
3030 // [IE] Focus widget before removing copybin to avoid scroll jump.
3031 if ( !isCut )
3032 widget.focus();
3033
3034 copybinContainer.remove();
3035
3036 listener1.removeListener();
3037 listener2.removeListener();
3038
3039 editor.fire( 'unlockSnapshot' );
3040
3041 if ( isCut ) {
3042 widget.repository.del( widget );
3043 editor.fire( 'saveSnapshot' );
3044 }
3045 }, 100 ); // Use 100ms, so Chrome (@Mac) will be able to grab the content.
3046 }
3047
3048 // Extracts classes array from style instance.
3049 function getStyleClasses( style ) {
3050 var attrs = style.getDefinition().attributes,
3051 classes = attrs && attrs[ 'class' ];
3052
3053 return classes ? classes.split( /\s+/ ) : null;
3054 }
3055
3056 // [IE] Force keeping focus because IE sometimes forgets to fire focus on main editable
3057 // when blurring nested editable.
3058 // @context widget
3059 function onEditableBlur() {
3060 var active = CKEDITOR.document.getActive(),
3061 editor = this.editor,
3062 editable = editor.editable();
3063
3064 // If focus stays within editor override blur and set currentActive because it should be
3065 // automatically changed to editable on editable#focus but it is not fired.
3066 if ( ( editable.isInline() ? editable : editor.document.getWindow().getFrame() ).equals( active ) )
3067 editor.focusManager.focus( editable );
3068 }
3069
3070 // Force selectionChange when editable was focused.
3071 // Similar to hack in selection.js#~620.
3072 // @context widget
3073 function onEditableFocus() {
3074 // Gecko does not support 'DOMFocusIn' event on which we unlock selection
3075 // in selection.js to prevent selection locking when entering nested editables.
3076 if ( CKEDITOR.env.gecko )
3077 this.editor.unlockSelection();
3078
3079 // We don't need to force selectionCheck on Webkit, because on Webkit
3080 // we do that on DOMFocusIn in selection.js.
3081 if ( !CKEDITOR.env.webkit ) {
3082 this.editor.forceNextSelectionCheck();
3083 this.editor.selectionChange( 1 );
3084 }
3085 }
3086
3087 // Setup listener on widget#data which will update (remove/add) classes
3088 // by comparing newly set classes with the old ones.
3089 function setupDataClassesListener( widget ) {
3090 // Note: previousClasses and newClasses may be null!
3091 // Tip: for ( cl in null ) is correct.
3092 var previousClasses = null;
3093
3094 widget.on( 'data', function() {
3095 var newClasses = this.data.classes,
3096 cl;
3097
3098 // When setting new classes one need to remember
3099 // that he must break reference.
3100 if ( previousClasses == newClasses )
3101 return;
3102
3103 for ( cl in previousClasses ) {
3104 // Avoid removing and adding classes again.
3105 if ( !( newClasses && newClasses[ cl ] ) )
3106 this.removeClass( cl );
3107 }
3108 for ( cl in newClasses )
3109 this.addClass( cl );
3110
3111 previousClasses = newClasses;
3112 } );
3113 }
3114
3115 function setupDragHandler( widget ) {
3116 if ( !widget.draggable )
3117 return;
3118
3119 var editor = widget.editor,
3120 // Use getLast to find wrapper's direct descendant (#12022).
3121 container = widget.wrapper.getLast( Widget.isDomDragHandlerContainer ),
3122 img;
3123
3124 // Reuse drag handler if already exists (#11281).
3125 if ( container )
3126 img = container.findOne( 'img' );
3127 else {
3128 container = new CKEDITOR.dom.element( 'span', editor.document );
3129 container.setAttributes( {
3130 'class': 'cke_reset cke_widget_drag_handler_container',
3131 // Split background and background-image for IE8 which will break on rgba().
3132 style: 'background:rgba(220,220,220,0.5);background-image:url(' + editor.plugins.widget.path + 'images/handle.png)'
3133 } );
3134
3135 img = new CKEDITOR.dom.element( 'img', editor.document );
3136 img.setAttributes( {
3137 'class': 'cke_reset cke_widget_drag_handler',
3138 'data-cke-widget-drag-handler': '1',
3139 src: CKEDITOR.tools.transparentImageData,
3140 width: DRAG_HANDLER_SIZE,
3141 title: editor.lang.widget.move,
3142 height: DRAG_HANDLER_SIZE
3143 } );
3144 widget.inline && img.setAttribute( 'draggable', 'true' );
3145
3146 container.append( img );
3147 widget.wrapper.append( container );
3148 }
3149
3150 // Preventing page reload when dropped content on widget wrapper (#13015).
3151 // Widget is not editable so by default drop on it isn't allowed what means that
3152 // browser handles it (there's no editable#drop event). If there's no drop event we cannot block
3153 // the drop, so page is reloaded. This listener enables drop on widget wrappers.
3154 widget.wrapper.on( 'dragover', function( evt ) {
3155 evt.data.preventDefault();
3156 } );
3157
3158 widget.wrapper.on( 'mouseenter', widget.updateDragHandlerPosition, widget );
3159 setTimeout( function() {
3160 widget.on( 'data', widget.updateDragHandlerPosition, widget );
3161 }, 50 );
3162
3163 if ( !widget.inline ) {
3164 img.on( 'mousedown', onBlockWidgetDrag, widget );
3165
3166 // On IE8 'dragstart' is propagated to editable, so editor#dragstart is fired twice on block widgets.
3167 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3168 img.on( 'dragstart', function( evt ) {
3169 evt.data.preventDefault( true );
3170 } );
3171 }
3172 }
3173
3174 widget.dragHandlerContainer = container;
3175 }
3176
3177 function onBlockWidgetDrag( evt ) {
3178 var finder = this.repository.finder,
3179 locator = this.repository.locator,
3180 liner = this.repository.liner,
3181 editor = this.editor,
3182 editable = editor.editable(),
3183 listeners = [],
3184 sorted = [];
3185
3186 // Mark dragged widget for repository#finder.
3187 this.repository._.draggedWidget = this;
3188
3189 // Harvest all possible relations and display some closest.
3190 var relations = finder.greedySearch(),
3191
3192 buffer = CKEDITOR.tools.eventsBuffer( 50, function() {
3193 locations = locator.locate( relations );
3194
3195 // There's only a single line displayed for D&D.
3196 sorted = locator.sort( y, 1 );
3197
3198 if ( sorted.length ) {
3199 liner.prepare( relations, locations );
3200 liner.placeLine( sorted[ 0 ] );
3201 liner.cleanup();
3202 }
3203 } ),
3204
3205 locations, y;
3206
3207 // Let's have the "dragging cursor" over entire editable.
3208 editable.addClass( 'cke_widget_dragging' );
3209
3210 // Cache mouse position so it is re-used in events buffer.
3211 listeners.push( editable.on( 'mousemove', function( evt ) {
3212 y = evt.data.$.clientY;
3213 buffer.input();
3214 } ) );
3215
3216 // Fire drag start as it happens during the native D&D.
3217 editor.fire( 'dragstart', { target: evt.sender } );
3218
3219 function onMouseUp() {
3220 var l;
3221
3222 buffer.reset();
3223
3224 // Stop observing events.
3225 while ( ( l = listeners.pop() ) )
3226 l.removeListener();
3227
3228 onBlockWidgetDrop.call( this, sorted, evt.sender );
3229 }
3230
3231 // Mouseup means "drop". This is when the widget is being detached
3232 // from DOM and placed at range determined by the line (location).
3233 listeners.push( editor.document.once( 'mouseup', onMouseUp, this ) );
3234
3235 // Prevent calling 'onBlockWidgetDrop' twice in the inline editor.
3236 // `removeListener` does not work if it is called at the same time event is fired.
3237 if ( !editable.isInline() ) {
3238 // Mouseup may occur when user hovers the line, which belongs to
3239 // the outer document. This is, of course, a valid listener too.
3240 listeners.push( CKEDITOR.document.once( 'mouseup', onMouseUp, this ) );
3241 }
3242 }
3243
3244 function onBlockWidgetDrop( sorted, dragTarget ) {
3245 var finder = this.repository.finder,
3246 liner = this.repository.liner,
3247 editor = this.editor,
3248 editable = this.editor.editable();
3249
3250 if ( !CKEDITOR.tools.isEmpty( liner.visible ) ) {
3251 // Retrieve range for the closest location.
3252 var dropRange = finder.getRange( sorted[ 0 ] );
3253
3254 // Focus widget (it could lost focus after mousedown+mouseup)
3255 // and save this state as the one where we want to be taken back when undoing.
3256 this.focus();
3257
3258 // Drag range will be set in the drop listener.
3259 editor.fire( 'drop', {
3260 dropRange: dropRange,
3261 target: dropRange.startContainer
3262 } );
3263 }
3264
3265 // Clean-up custom cursor for editable.
3266 editable.removeClass( 'cke_widget_dragging' );
3267
3268 // Clean-up all remaining lines.
3269 liner.hideVisible();
3270
3271 // Clean-up drag & drop.
3272 editor.fire( 'dragend', { target: dragTarget } );
3273 }
3274
3275 function setupEditables( widget ) {
3276 var editableName,
3277 editableDef,
3278 definedEditables = widget.editables;
3279
3280 widget.editables = {};
3281
3282 if ( !widget.editables )
3283 return;
3284
3285 for ( editableName in definedEditables ) {
3286 editableDef = definedEditables[ editableName ];
3287 widget.initEditable( editableName, typeof editableDef == 'string' ? { selector: editableDef } : editableDef );
3288 }
3289 }
3290
3291 function setupMask( widget ) {
3292 if ( !widget.mask )
3293 return;
3294
3295 // Reuse mask if already exists (#11281).
3296 var img = widget.wrapper.findOne( '.cke_widget_mask' );
3297
3298 if ( !img ) {
3299 img = new CKEDITOR.dom.element( 'img', widget.editor.document );
3300 img.setAttributes( {
3301 src: CKEDITOR.tools.transparentImageData,
3302 'class': 'cke_reset cke_widget_mask'
3303 } );
3304 widget.wrapper.append( img );
3305 }
3306
3307 widget.mask = img;
3308 }
3309
3310 // Replace parts object containing:
3311 // partName => selector pairs
3312 // with:
3313 // partName => element pairs
3314 function setupParts( widget ) {
3315 if ( widget.parts ) {
3316 var parts = {},
3317 el, partName;
3318
3319 for ( partName in widget.parts ) {
3320 el = widget.wrapper.findOne( widget.parts[ partName ] );
3321 parts[ partName ] = el;
3322 }
3323 widget.parts = parts;
3324 }
3325 }
3326
3327 function setupWidget( widget, widgetDef ) {
3328 setupWrapper( widget );
3329 setupParts( widget );
3330 setupEditables( widget );
3331 setupMask( widget );
3332 setupDragHandler( widget );
3333 setupDataClassesListener( widget );
3334
3335 // #11145: [IE8] Non-editable content of widget is draggable.
3336 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3337 widget.wrapper.on( 'dragstart', function( evt ) {
3338 var target = evt.data.getTarget();
3339
3340 // Allow text dragging inside nested editables or dragging inline widget's drag handler.
3341 if ( !Widget.getNestedEditable( widget, target ) && !( widget.inline && Widget.isDomDragHandler( target ) ) )
3342 evt.data.preventDefault();
3343 } );
3344 }
3345
3346 widget.wrapper.removeClass( 'cke_widget_new' );
3347 widget.element.addClass( 'cke_widget_element' );
3348
3349 widget.on( 'key', function( evt ) {
3350 var keyCode = evt.data.keyCode;
3351
3352 // ENTER.
3353 if ( keyCode == 13 ) {
3354 widget.edit();
3355 // CTRL+C or CTRL+X.
3356 } else if ( keyCode == CKEDITOR.CTRL + 67 || keyCode == CKEDITOR.CTRL + 88 ) {
3357 copySingleWidget( widget, keyCode == CKEDITOR.CTRL + 88 );
3358 return; // Do not preventDefault.
3359 } else if ( keyCode in keystrokesNotBlockedByWidget || ( CKEDITOR.CTRL & keyCode ) || ( CKEDITOR.ALT & keyCode ) ) {
3360 // Pass chosen keystrokes to other plugins or default fake sel handlers.
3361 // Pass all CTRL/ALT keystrokes.
3362 return;
3363 }
3364
3365 return false;
3366 }, null, null, 999 );
3367 // Listen with high priority so it's possible
3368 // to overwrite this callback.
3369
3370 widget.on( 'doubleclick', function( evt ) {
3371 if ( widget.edit() ) {
3372 // We have to cancel event if edit method opens a dialog, otherwise
3373 // link plugin may open extra dialog (#12140).
3374 evt.cancel();
3375 }
3376 } );
3377
3378 if ( widgetDef.data )
3379 widget.on( 'data', widgetDef.data );
3380
3381 if ( widgetDef.edit )
3382 widget.on( 'edit', widgetDef.edit );
3383 }
3384
3385 function setupWidgetData( widget, startupData ) {
3386 var widgetDataAttr = widget.element.data( 'cke-widget-data' );
3387
3388 if ( widgetDataAttr )
3389 widget.setData( JSON.parse( decodeURIComponent( widgetDataAttr ) ) );
3390 if ( startupData )
3391 widget.setData( startupData );
3392
3393 // Populate classes if they are not preset.
3394 if ( !widget.data.classes )
3395 widget.setData( 'classes', widget.getClasses() );
3396
3397 // Unblock data and...
3398 widget.dataReady = true;
3399
3400 // Write data to element because this was blocked when data wasn't ready.
3401 writeDataToElement( widget );
3402
3403 // Fire data event first time, because this was blocked when data wasn't ready.
3404 widget.fire( 'data', widget.data );
3405 }
3406
3407 function setupWrapper( widget ) {
3408 // Retrieve widget wrapper. Assign an id to it.
3409 var wrapper = widget.wrapper = widget.element.getParent();
3410 wrapper.setAttribute( 'data-cke-widget-id', widget.id );
3411 }
3412
3413 function writeDataToElement( widget ) {
3414 widget.element.data( 'cke-widget-data', encodeURIComponent( JSON.stringify( widget.data ) ) );
3415 }
3416
3417 //
3418 // WIDGET STYLE HANDLER ---------------------------------------------------
3419 //
3420
3421 ( function() {
3422
3423 /**
3424 * The class representing a widget style. It is an {@link CKEDITOR#STYLE_OBJECT object} like
3425 * the styles handler for widgets.
3426 *
3427 * **Note:** This custom style handler does not support all methods of the {@link CKEDITOR.style} class.
3428 * Not supported methods: {@link #applyToRange}, {@link #removeFromRange}, {@link #applyToObject}.
3429 *
3430 * @since 4.4
3431 * @class CKEDITOR.style.customHandlers.widget
3432 * @extends CKEDITOR.style
3433 */
3434 CKEDITOR.style.addCustomHandler( {
3435 type: 'widget',
3436
3437 setup: function( styleDefinition ) {
3438 /**
3439 * The name of widget to which this style can be applied.
3440 * It is extracted from style definition's `widget` property.
3441 *
3442 * @property {String} widget
3443 */
3444 this.widget = styleDefinition.widget;
3445 },
3446
3447 apply: function( editor ) {
3448 // Before CKEditor 4.4 wasn't a required argument, so we need to
3449 // handle a case when it wasn't provided.
3450 if ( !( editor instanceof CKEDITOR.editor ) )
3451 return;
3452
3453 // Theoretically we could bypass checkApplicable, get widget from
3454 // widgets.focused and check its name, what would be faster, but then
3455 // this custom style would work differently than the default style
3456 // which checks if it's applicable before applying or removeing itself.
3457 if ( this.checkApplicable( editor.elementPath(), editor ) )
3458 editor.widgets.focused.applyStyle( this );
3459 },
3460
3461 remove: function( editor ) {
3462 // Before CKEditor 4.4 wasn't a required argument, so we need to
3463 // handle a case when it wasn't provided.
3464 if ( !( editor instanceof CKEDITOR.editor ) )
3465 return;
3466
3467 if ( this.checkApplicable( editor.elementPath(), editor ) )
3468 editor.widgets.focused.removeStyle( this );
3469 },
3470
3471 checkActive: function( elementPath, editor ) {
3472 return this.checkElementMatch( elementPath.lastElement, 0, editor );
3473 },
3474
3475 checkApplicable: function( elementPath, editor ) {
3476 // Before CKEditor 4.4 wasn't a required argument, so we need to
3477 // handle a case when it wasn't provided.
3478 if ( !( editor instanceof CKEDITOR.editor ) )
3479 return false;
3480
3481 return this.checkElement( elementPath.lastElement );
3482 },
3483
3484 checkElementMatch: checkElementMatch,
3485
3486 checkElementRemovable: checkElementMatch,
3487
3488 /**
3489 * Checks if an element is a {@link CKEDITOR.plugins.widget#wrapper wrapper} of a
3490 * widget whose name matches the {@link #widget widget name} specified in the style definition.
3491 *
3492 * @param {CKEDITOR.dom.element} element
3493 * @returns {Boolean}
3494 */
3495 checkElement: function( element ) {
3496 if ( !Widget.isDomWidgetWrapper( element ) )
3497 return false;
3498
3499 var widgetElement = element.getFirst( Widget.isDomWidgetElement );
3500 return widgetElement && widgetElement.data( 'widget' ) == this.widget;
3501 },
3502
3503 buildPreview: function( label ) {
3504 return label || this._.definition.name;
3505 },
3506
3507 /**
3508 * Returns allowed content rules which should be registered for this style.
3509 * Uses widget's {@link CKEDITOR.plugins.widget.definition#styleableElements} to make a rule
3510 * allowing classes on specified elements or use widget's
3511 * {@link CKEDITOR.plugins.widget.definition#styleToAllowedContentRules} method to transform a style
3512 * into allowed content rules.
3513 *
3514 * @param {CKEDITOR.editor} The editor instance.
3515 * @returns {CKEDITOR.filter.allowedContentRules}
3516 */
3517 toAllowedContentRules: function( editor ) {
3518 if ( !editor )
3519 return null;
3520
3521 var widgetDef = editor.widgets.registered[ this.widget ],
3522 classes,
3523 rule = {};
3524
3525 if ( !widgetDef )
3526 return null;
3527
3528 if ( widgetDef.styleableElements ) {
3529 classes = this.getClassesArray();
3530 if ( !classes )
3531 return null;
3532
3533 rule[ widgetDef.styleableElements ] = {
3534 classes: classes,
3535 propertiesOnly: true
3536 };
3537 return rule;
3538 }
3539 if ( widgetDef.styleToAllowedContentRules )
3540 return widgetDef.styleToAllowedContentRules( this );
3541 return null;
3542 },
3543
3544 /**
3545 * Returns classes defined in the style in form of an array.
3546 *
3547 * @returns {String[]}
3548 */
3549 getClassesArray: function() {
3550 var classes = this._.definition.attributes && this._.definition.attributes[ 'class' ];
3551
3552 return classes ? CKEDITOR.tools.trim( classes ).split( /\s+/ ) : null;
3553 },
3554
3555 /**
3556 * Not implemented.
3557 *
3558 * @method applyToRange
3559 */
3560 applyToRange: notImplemented,
3561
3562 /**
3563 * Not implemented.
3564 *
3565 * @method removeFromRange
3566 */
3567 removeFromRange: notImplemented,
3568
3569 /**
3570 * Not implemented.
3571 *
3572 * @method applyToObject
3573 */
3574 applyToObject: notImplemented
3575 } );
3576
3577 function notImplemented() {}
3578
3579 // @context style
3580 function checkElementMatch( element, fullMatch, editor ) {
3581 // Before CKEditor 4.4 wasn't a required argument, so we need to
3582 // handle a case when it wasn't provided.
3583 if ( !editor )
3584 return false;
3585
3586 if ( !this.checkElement( element ) )
3587 return false;
3588
3589 var widget = editor.widgets.getByElement( element, true );
3590 return widget && widget.checkStyleActive( this );
3591 }
3592
3593 } )();
3594
3595 //
3596 // EXPOSE PUBLIC API ------------------------------------------------------
3597 //
3598
3599 CKEDITOR.plugins.widget = Widget;
3600 Widget.repository = Repository;
3601 Widget.nestedEditable = NestedEditable;
3602} )();
3603
3604/**
3605 * An event fired when a widget definition is registered by the {@link CKEDITOR.plugins.widget.repository#add} method.
3606 * It is possible to modify the definition being registered.
3607 *
3608 * @event widgetDefinition
3609 * @member CKEDITOR.editor
3610 * @param {CKEDITOR.plugins.widget.definition} data Widget definition.
3611 */
3612
3613/**
3614 * This is an abstract class that describes the definition of a widget.
3615 * It is a type of {@link CKEDITOR.plugins.widget.repository#add} method's second argument.
3616 *
3617 * Widget instances inherit from registered widget definitions, although not in a prototypal way.
3618 * They are simply extended with corresponding widget definitions. Note that not all properties of
3619 * the widget definition become properties of a widget. Some, like {@link #data} or {@link #edit}, become
3620 * widget's events listeners.
3621 *
3622 * @class CKEDITOR.plugins.widget.definition
3623 * @abstract
3624 * @mixins CKEDITOR.feature
3625 */
3626
3627/**
3628 * Widget definition name. It is automatically set when the definition is
3629 * {@link CKEDITOR.plugins.widget.repository#add registered}.
3630 *
3631 * @property {String} name
3632 */
3633
3634/**
3635 * The method executed while initializing a widget, after a widget instance
3636 * is created, but before it is ready. It is executed before the first
3637 * {@link CKEDITOR.plugins.widget#event-data} is fired so it is common to
3638 * use the `init` method to populate widget data with information loaded from
3639 * the DOM, like for exmaple:
3640 *
3641 * init: function() {
3642 * this.setData( 'width', this.element.getStyle( 'width' ) );
3643 *
3644 * if ( this.parts.caption.getStyle( 'display' ) != 'none' )
3645 * this.setData( 'showCaption', true );
3646 * }
3647 *
3648 * @property {Function} init
3649 */
3650
3651/**
3652 * The function to be used to upcast an element to this widget or a
3653 * comma-separated list of upcast methods from the {@link #upcasts} object.
3654 *
3655 * The upcast function **is not** executed in the widget context (because the widget
3656 * does not exist yet) and two arguments are passed:
3657 *
3658 * * `element` ({@link CKEDITOR.htmlParser.element}) &ndash; The element to be checked.
3659 * * `data` (`Object`) &ndash; The object which can be extended with data which will then be passed to the widget.
3660 *
3661 * An element will be upcasted if a function returned `true` or an instance of
3662 * a {@link CKEDITOR.htmlParser.element} if upcasting meant DOM structure changes
3663 * (in this case the widget will be initialized on the returned element).
3664 *
3665 * @property {String/Function} upcast
3666 */
3667
3668/**
3669 * The object containing functions which can be used to upcast this widget.
3670 * Only those pointed by the {@link #upcast} property will be used.
3671 *
3672 * In most cases it is appropriate to use {@link #upcast} directly,
3673 * because majority of widgets need just one method.
3674 * However, in some cases the widget author may want to expose more than one variant
3675 * and then this property may be used.
3676 *
3677 * upcasts: {
3678 * // This function may upcast only figure elements.
3679 * figure: function() {
3680 * // ...
3681 * },
3682 * // This function may upcast only image elements.
3683 * image: function() {
3684 * // ...
3685 * },
3686 * // More variants...
3687 * }
3688 *
3689 * // Then, widget user may choose which upcast methods will be enabled.
3690 * editor.on( 'widgetDefinition', function( evt ) {
3691 * if ( evt.data.name == 'image' )
3692 * evt.data.upcast = 'figure,image'; // Use both methods.
3693 * } );
3694 *
3695 * @property {Object} upcasts
3696 */
3697
3698/**
3699 * The {@link #upcast} method(s) priority. The upcast with a lower priority number will be called before
3700 * the one with a higher number. The default priority is `10`.
3701 *
3702 * @since 4.5
3703 * @property {Number} [upcastPriority=10]
3704 */
3705
3706/**
3707 * The function to be used to downcast this widget or
3708 * a name of the downcast option from the {@link #downcasts} object.
3709 *
3710 * The downcast funciton will be executed in the {@link CKEDITOR.plugins.widget} context
3711 * and with `widgetElement` ({@link CKEDITOR.htmlParser.element}) argument which is
3712 * the widget's main element.
3713 *
3714 * The function may return an instance of the {@link CKEDITOR.htmlParser.node} class if the widget
3715 * needs to be downcasted to a different node than the widget's main element.
3716 *
3717 * @property {String/Function} downcast
3718 */
3719
3720/**
3721 * The object containing functions which can be used to downcast this widget.
3722 * Only the one pointed by the {@link #downcast} property will be used.
3723 *
3724 * In most cases it is appropriate to use {@link #downcast} directly,
3725 * because majority of widgets have just one variant of downcasting (or none at all).
3726 * However, in some cases the widget author may want to expose more than one variant
3727 * and then this property may be used.
3728 *
3729 * downcasts: {
3730 * // This downcast may transform the widget into the figure element.
3731 * figure: function() {
3732 * // ...
3733 * },
3734 * // This downcast may transform the widget into the image element with data-* attributes.
3735 * image: function() {
3736 * // ...
3737 * }
3738 * }
3739 *
3740 * // Then, the widget user may choose one of the downcast options when setting up his editor.
3741 * editor.on( 'widgetDefinition', function( evt ) {
3742 * if ( evt.data.name == 'image' )
3743 * evt.data.downcast = 'figure';
3744 * } );
3745 *
3746 * @property downcasts
3747 */
3748
3749/**
3750 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-edit} event listener.
3751 * This means that it will be executed when a widget is being edited.
3752 * See the {@link CKEDITOR.plugins.widget#method-edit} method.
3753 *
3754 * @property {Function} edit
3755 */
3756
3757/**
3758 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-data} event listener.
3759 * This means that it will be executed every time the {@link CKEDITOR.plugins.widget#property-data widget data} changes.
3760 *
3761 * @property {Function} data
3762 */
3763
3764/**
3765 * The method to be executed when the widget's command is executed in order to insert a new widget
3766 * (widget of this type is not focused). If not defined, then the default action will be
3767 * performed which means that:
3768 *
3769 * * An instance of the widget will be created in a detached {@link CKEDITOR.dom.documentFragment document fragment},
3770 * * The {@link CKEDITOR.plugins.widget#method-edit} method will be called to trigger widget editing,
3771 * * The widget element will be inserted into DOM.
3772 *
3773 * @property {Function} insert
3774 */
3775
3776/**
3777 * The name of a dialog window which will be opened on {@link CKEDITOR.plugins.widget#method-edit}.
3778 * If not defined, then the {@link CKEDITOR.plugins.widget#method-edit} method will not perform any action and
3779 * widget's command will insert a new widget without opening a dialog window first.
3780 *
3781 * @property {String} dialog
3782 */
3783
3784/**
3785 * The template which will be used to create a new widget element (when the widget's command is executed).
3786 * This string is populated with {@link #defaults default values} by using the {@link CKEDITOR.template} format.
3787 * Therefore it has to be a valid {@link CKEDITOR.template} argument.
3788 *
3789 * @property {String} template
3790 */
3791
3792/**
3793 * The data object which will be used to populate the data of a newly created widget.
3794 * See {@link CKEDITOR.plugins.widget#property-data}.
3795 *
3796 * defaults: {
3797 * showCaption: true,
3798 * align: 'none'
3799 * }
3800 *
3801 * @property defaults
3802 */
3803
3804/**
3805 * An object containing definitions of widget components (part name => CSS selector).
3806 *
3807 * parts: {
3808 * image: 'img',
3809 * caption: 'div.caption'
3810 * }
3811 *
3812 * @property parts
3813 */
3814
3815/**
3816 * An object containing definitions of nested editables (editable name => {@link CKEDITOR.plugins.widget.nestedEditable.definition}).
3817 * Note that editables *have to* be defined in the same order as they are in DOM / {@link CKEDITOR.plugins.widget.definition#template template}.
3818 * Otherwise errors will occur when nesting widgets inside each other.
3819 *
3820 * editables: {
3821 * header: 'h1',
3822 * content: {
3823 * selector: 'div.content',
3824 * allowedContent: 'p strong em; a[!href]'
3825 * }
3826 * }
3827 *
3828 * @property editables
3829 */
3830
3831/**
3832 * Widget name displayed in elements path.
3833 *
3834 * @property {String} pathName
3835 */
3836
3837/**
3838 * If set to `true`, the widget's element will be covered with a transparent mask.
3839 * This will prevent its content from being clickable, which matters in case
3840 * of special elements like embedded Flash or iframes that generate a separate "context".
3841 *
3842 * @property {Boolean} mask
3843 */
3844
3845/**
3846 * If set to `true/false`, it will force the widget to be either an inline or a block widget.
3847 * If not set, the widget type will be determined from the widget element.
3848 *
3849 * Widget type influences whether a block (`div`) or an inline (`span`) element is used
3850 * for the wrapper.
3851 *
3852 * @property {Boolean} inline
3853 */
3854
3855/**
3856 * The label for the widget toolbar button.
3857 *
3858 * editor.widgets.add( 'simplebox', {
3859 * button: 'Create a simple box'
3860 * } );
3861 *
3862 * editor.widgets.add( 'simplebox', {
3863 * button: editor.lang.simplebox.title
3864 * } );
3865 *
3866 * @property {String} button
3867 */
3868
3869/**
3870 * Whether widget should be draggable. Defaults to `true`.
3871 * If set to `false` drag handler will not be displayed when hovering widget.
3872 *
3873 * @property {Boolean} draggable
3874 */
3875
3876/**
3877 * Names of element(s) (separated by spaces) for which the {@link CKEDITOR.filter} should allow classes
3878 * defined in the widget styles. For example if your widget is upcasted from a simple `<div>`
3879 * element, then in order to make it styleable you can set:
3880 *
3881 * editor.widgets.add( 'customWidget', {
3882 * upcast: function( element ) {
3883 * return element.name == 'div';
3884 * },
3885 *
3886 * // ...
3887 *
3888 * styleableElements: 'div'
3889 * } );
3890 *
3891 * Then, when the following style is defined:
3892 *
3893 * {
3894 * name: 'Thick border', type: 'widget', widget: 'customWidget',
3895 * attributes: { 'class': 'thickBorder' }
3896 * }
3897 *
3898 * a rule allowing the `thickBorder` class for `div` elements will be registered in the {@link CKEDITOR.filter}.
3899 *
3900 * If you need to have more freedom when transforming widget style to allowed content rules,
3901 * you can use the {@link #styleToAllowedContentRules} callback.
3902 *
3903 * @since 4.4
3904 * @property {String} styleableElements
3905 */
3906
3907/**
3908 * Function transforming custom widget's {@link CKEDITOR.style} instance into
3909 * {@link CKEDITOR.filter.allowedContentRules}. It may be used when a static
3910 * {@link #styleableElements} property is not enough to inform the {@link CKEDITOR.filter}
3911 * what HTML features should be enabled when allowing the given style.
3912 *
3913 * In most cases, when style's classes just have to be added to element name(s) used by
3914 * the widget element, it is recommended to use simpler {@link #styleableElements} property.
3915 *
3916 * In order to get parsed classes from the style definition you can use
3917 * {@link CKEDITOR.style.customHandlers.widget#getClassesArray}.
3918 *
3919 * For example, if you want to use the [object format of allowed content rules](#!/guide/dev_allowed_content_rules-section-object-format),
3920 * to specify `match` validator, your implementation could look like this:
3921 *
3922 * editor.widgets.add( 'customWidget', {
3923 * // ...
3924 *
3925 * styleToAllowedContentRules: funciton( style ) {
3926 * // Retrieve classes defined in the style.
3927 * var classes = style.getClassesArray();
3928 *
3929 * // Do something crazy - for example return allowed content rules in object format,
3930 * // with custom match property and propertiesOnly flag.
3931 * return {
3932 * h1: {
3933 * match: isWidgetElement,
3934 * propertiesOnly: true,
3935 * classes: classes
3936 * }
3937 * };
3938 * }
3939 * } );
3940 *
3941 * @since 4.4
3942 * @property {Function} styleToAllowedContentRules
3943 * @param {CKEDITOR.style.customHandlers.widget} style The style to be transformed.
3944 * @returns {CKEDITOR.filter.allowedContentRules}
3945 */
3946
3947/**
3948 * This is an abstract class that describes the definition of a widget's nested editable.
3949 * It is a type of values in the {@link CKEDITOR.plugins.widget.definition#editables} object.
3950 *
3951 * In the simplest case the definition is a string which is a CSS selector used to
3952 * find an element that will become a nested editable inside the widget. Note that
3953 * the widget element can be a nested editable, too.
3954 *
3955 * In the more advanced case a definition is an object with a required `selector` property.
3956 *
3957 * editables: {
3958 * header: 'h1',
3959 * content: {
3960 * selector: 'div.content',
3961 * allowedContent: 'p strong em; a[!href]'
3962 * }
3963 * }
3964 *
3965 * @class CKEDITOR.plugins.widget.nestedEditable.definition
3966 * @abstract
3967 */
3968
3969/**
3970 * The CSS selector used to find an element which will become a nested editable.
3971 *
3972 * @property {String} selector
3973 */
3974
3975/**
3976 * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
3977 * which will be used to limit the content allowed in this nested editable.
3978 * This option is similar to {@link CKEDITOR.config#allowedContent} and one can
3979 * use it to limit the editor features available in the nested editable.
3980 *
3981 * @property {CKEDITOR.filter.allowedContentRules} allowedContent
3982 */
3983
3984/**
3985 * Nested editable name displayed in elements path.
3986 *
3987 * @property {String} pathName
3988 */