aboutsummaryrefslogtreecommitdiff
path: root/sources/plugins/lineutils
diff options
context:
space:
mode:
Diffstat (limited to 'sources/plugins/lineutils')
-rw-r--r--sources/plugins/lineutils/dev/dnd.html172
-rw-r--r--sources/plugins/lineutils/dev/magicfinger.html285
-rw-r--r--sources/plugins/lineutils/plugin.js1018
3 files changed, 1475 insertions, 0 deletions
diff --git a/sources/plugins/lineutils/dev/dnd.html b/sources/plugins/lineutils/dev/dnd.html
new file mode 100644
index 00000000..2967c4b8
--- /dev/null
+++ b/sources/plugins/lineutils/dev/dnd.html
@@ -0,0 +1,172 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2016, 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>Widget Drag &amp; Drop with Lineutils &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <script>
12 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 )
13 CKEDITOR.tools.enableHtml5Elements( document );
14 </script>
15 <link href="../../../samples/old/sample.css" rel="stylesheet">
16 <link href="../../image2/samples/contents.css" rel="stylesheet">
17</head>
18<body>
19 <h1 class="samples">
20 <a href="../../../samples/old/index.html">CKEditor Samples</a> &raquo; Widget Drag &amp; Drop with Lineutils
21 </h1>
22
23 <h3>Classic (iframe-based) Editor</h3>
24
25 <textarea id="editor1" cols="10" rows="10">
26 <h1>Apollo 11</h1>
27
28 <figure class="caption" style="float:right"><img alt="Saturn V" src="../../image2/samples/assets/image1.jpg" width="200" />
29 <figcaption>Roll out of Saturn V on launch pad</figcaption>
30 </figure>
31
32 <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>
33
34 <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>
35
36 <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
37
38 <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
39
40 <blockquote>
41 <p>One small step for [a] man, one giant leap for mankind.</p>
42 </blockquote>
43
44 <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>
45
46 <div style="text-align:center">
47 <figure class="caption" style="display:inline-block"><img alt="The Eagle" height="123" src="../../image2/samples/assets/image2.jpg" width="136" />
48 <figcaption>The Eagle in lunar orbit</figcaption>
49 </figure>
50 </div>
51
52 <blockquote>
53 <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
54 </blockquote>
55
56 <figure class="caption" style="float:right"><img alt="The Eagle" src="../../image2/samples/assets/image2.jpg" width="200" />
57 <figcaption>The Eagle in lunar orbit</figcaption>
58 </figure>
59
60 <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
61
62 <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>
63
64 <ol>
65 <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
66 <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
67 <li><strong>Lunar Module</strong> for landing on the Moon.</li>
68 </ol>
69
70 <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>
71
72 <figure class="caption"><img alt="Saturn V" height="129" src="../../image2/samples/assets/image1.jpg" width="101" />
73 <figcaption>Roll out of Saturn V on launch pad</figcaption>
74 </figure>
75
76 <hr />
77 <p style="text-align:right"><small>Source: <a href="http://en.wikipedia.org/wiki/Apollo_11">Wikipedia.org</a></small></p>
78
79 </textarea>
80
81 <h3>Inline Editor</h3>
82
83 <div id="editor2" contenteditable="true" style="outline: 2px solid #ccc">
84 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
85 <tbody>
86 <tr>
87 <td>This table</td>
88 <td>is the</td>
89 <td>very first</td>
90 <td>element of the document.</td>
91 </tr>
92 <tr>
93 <td>We are still</td>
94 <td>able to acces</td>
95 <td>the space before it.</td>
96 <td style="padding: 25px">
97 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
98 <tbody>
99 <tr>
100 <td>This table is inside of a cell of another table.</td>
101 </tr>
102 <tr>
103 <td>We can type&nbsp;either before or after it though.</td>
104 </tr>
105 </tbody>
106 </table>
107 </td>
108 </tr>
109 </tbody>
110 </table>
111
112 <hr />
113 <hr />
114 <ol style="width: 300px">
115 <li>This numbered list...</li>
116 <li>...is a neighbour of a horizontal line...</li>
117 <li style="padding: 20px;">
118 <ol>
119 <li>Nested list!</li>
120 </ol>
121 </li>
122 </ol>
123
124 <figure class="caption"><img alt="Saturn V" src="../../image2/samples/assets/image1.jpg" width="100" />
125 <figcaption>Roll out of Saturn V on launch pad</figcaption>
126 </figure>
127
128 <ul style="width: 450px">
129 <li>We can type between the lists...</li>
130 <li>...thanks to <strong>Magicline</strong>.</li>
131 </ul>
132
133 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
134
135 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
136
137 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
138
139 <div id="last" style="padding: 10px; text-align: center;">
140 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
141 </div>
142 </div>
143
144 <script>
145
146 CKEDITOR.replace( 'editor1', {
147 extraPlugins: 'image2',
148 height: 450,
149 removePlugins: 'image,forms',
150 contentsCss: [ '../../../contents.css', '../../image2/samples/contents.css' ]
151 } );
152
153 CKEDITOR.inline( 'editor2', {
154 extraPlugins: 'image2',
155 height: 450,
156 removePlugins: 'image,forms'
157 } );
158
159 </script>
160
161 <div id="footer">
162 <hr>
163 <p>
164 CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
165 </p>
166 <p id="copy">
167 Copyright &copy; 2003-2016, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
168 Knabben. All rights reserved.
169 </p>
170 </div>
171</body>
172</html>
diff --git a/sources/plugins/lineutils/dev/magicfinger.html b/sources/plugins/lineutils/dev/magicfinger.html
new file mode 100644
index 00000000..079b4584
--- /dev/null
+++ b/sources/plugins/lineutils/dev/magicfinger.html
@@ -0,0 +1,285 @@
1<!DOCTYPE html>
2<!--
3Copyright (c) 2003-2016, 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>Lineutils &mdash; CKEditor Sample</title>
10 <script src="../../../ckeditor.js"></script>
11 <link href="../../../samples/old/sample.css" rel="stylesheet">
12</head>
13<body>
14 <h1 class="samples">
15 <a href="../../../samples/old/index.html">CKEditor Samples</a> &raquo; Lineutils
16 </h1>
17
18 <h3>Classic (iframe-based) Editor</h3>
19
20 <textarea id="editor1" cols="10" rows="10">
21 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
22 <tbody>
23 <tr>
24 <td>This table</td>
25 <td>is the</td>
26 <td>very first</td>
27 <td>element of the document.</td>
28 </tr>
29 <tr>
30 <td>We are still</td>
31 <td>able to acces</td>
32 <td>the space before it.</td>
33 <td style="padding: 25px">
34 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
35 <tbody>
36 <tr>
37 <td>This table is inside of a cell of another table.</td>
38 </tr>
39 <tr>
40 <td>We can type&nbsp;either before or after it though.</td>
41 </tr>
42 </tbody>
43 </table>
44 </td>
45 </tr>
46 </tbody>
47 </table>
48
49 <p>Two succesive horizontal lines (<tt>HR</tt> tags). We can access the space in between:</p>
50
51 <hr />
52 <hr />
53 <ol style="width: 300px">
54 <li>This numbered list...</li>
55 <li>...is a neighbour of a horizontal line...</li>
56 <li style="padding: 20px;">
57 <ol>
58 <li>Nested list!</li>
59 </ol>
60 </li>
61 </ol>
62
63 <ul style="width: 450px">
64 <li>We can type between the lists...</li>
65 <li>...thanks to <strong>Magicline</strong>.</li>
66 </ul>
67
68 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
69
70 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
71
72 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
73
74 <div id="last" style="padding: 10px; text-align: center;">
75 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
76 </div>
77 </textarea>
78
79 <h3>Inline Editor</h3>
80
81 <div id="editor2" contenteditable="true" style="outline: 2px solid #ccc">
82 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
83 <tbody>
84 <tr>
85 <td>This table</td>
86 <td>is the</td>
87 <td>very first</td>
88 <td>element of the document.</td>
89 </tr>
90 <tr>
91 <td>We are still</td>
92 <td>able to acces</td>
93 <td>the space before it.</td>
94 <td style="padding: 25px">
95 <table border="0" cellpadding="1" cellspacing="1" style="width: 100%; ">
96 <tbody>
97 <tr>
98 <td>This table is inside of a cell of another table.</td>
99 </tr>
100 <tr>
101 <td>We can type&nbsp;either before or after it though.</td>
102 </tr>
103 </tbody>
104 </table>
105 </td>
106 </tr>
107 </tbody>
108 </table>
109
110 <p>Two succesive horizontal lines (<tt>HR</tt> tags). We can access the space in between:</p>
111
112 <hr />
113 <hr />
114 <ol style="width: 300px">
115 <li>This numbered list...</li>
116 <li>...is a neighbour of a horizontal line...</li>
117 <li style="padding: 20px;">
118 <ol>
119 <li>Nested list!</li>
120 </ol>
121 </li>
122 </ol>
123
124 <ul style="width: 450px">
125 <li>We can type between the lists...</li>
126 <li>...thanks to <strong>Magicline</strong>.</li>
127 </ul>
128
129 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
130
131 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
132
133 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
134
135 <div id="last" style="padding: 10px; text-align: center;">
136 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
137 </div>
138 </div>
139
140 <h3>Extreme inline</h3>
141
142 <div id="editor3" contenteditable="true" style="left: 123px; outline: 1px solid red; border: 15px solid green; position: relative; top: 30; left: 30px;">
143 <div style="padding: 20px; background: gray; width: 300px" class="1">Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim.</div>
144 <div style="background: violet; padding: 30px;" class="static">
145 Position static
146 <div style="background: green; padding: 30px; border: 14px solid orange">foo</div>
147 </div>
148 <dl class="2">
149 <dt>Key</dt><dd>Value</dd>
150 </dl>
151 <div>Whatever</div>
152 <hr id="hr">
153 <p>Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies</p>
154 <hr>
155 <hr>
156 <p>Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies</p>
157 <div style="background: green; padding: 30px; width: 200px">foo</div>
158 </div>
159
160 <h3>Classic (iframe-based) Editor, H-scroll</h3>
161
162 <textarea id="editor4" cols="10" rows="10">
163 <hr />
164 <hr />
165 <ol style="width: 1500px">
166 <li>This numbered list...</li>
167 <li>...is a neighbour of a horizontal line...</li>
168 <li style="padding: 20px;">
169 <ol>
170 <li>Nested list!</li>
171 </ol>
172 </li>
173 </ol>
174
175 <ul style="width: 450px">
176 <li>We can type between the lists...</li>
177 <li>...thanks to <strong>Magicline</strong>.</li>
178 </ul>
179
180 <p>Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.</p>
181
182 <p>Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.</p>
183
184 <p>Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.</p>
185
186 <div id="last" style="padding: 10px; text-align: center;">
187 <p>This text is wrapped in a&nbsp;<tt>DIV</tt>&nbsp;element. We can type after this element though.</p>
188 </div>
189 </textarea>
190
191 <script>
192
193 CKEDITOR.addCss(
194 '.cke_editable * { outline: 1px solid #BCEBFF }'
195 );
196
197 function callback() {
198 var helpers = CKEDITOR.plugins.lineutils;
199 var liner = new helpers.liner( this );
200 var locator = new helpers.locator( this );
201 var finder = new helpers.finder( this, {
202 lookups: {
203 'is block and first child': function( el ) {
204 if ( el.is( CKEDITOR.dtd.$listItem ) )
205 return;
206
207 if ( el.is( CKEDITOR.dtd.$block ) )
208 return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER;
209 }
210 }
211 } ).start( function( relations, x, y ) {
212 locator.locate( relations );
213
214 var locations = locator.locations,
215 uid, type;
216
217 liner.prepare( relations, locations );
218
219 for ( uid in locations ) {
220 for ( type in locations[ uid ] )
221 liner.placeLine( { uid: uid, type: type } );
222 }
223
224 liner.cleanup();
225 } );
226 }
227
228 CKEDITOR.disableAutoInline = true;
229
230 CKEDITOR.replace( 'editor1', {
231 extraPlugins: 'lineutils',
232 height: 450,
233 removePlugins: 'magicline',
234 allowedContent: true,
235 contentsCss: [ '../../../contents.css' ],
236 on: {
237 contentDom: callback
238 }
239 } );
240
241 CKEDITOR.inline( 'editor2', {
242 extraPlugins: 'lineutils',
243 removePlugins: 'magicline',
244 allowedContent: true,
245 contentsCss: [ '../../../contents.css' ],
246 on: {
247 contentDom: callback
248 }
249 } );
250
251 CKEDITOR.inline( 'editor3', {
252 extraPlugins: 'lineutils',
253 removePlugins: 'magicline',
254 allowedContent: true,
255 contentsCss: [ '../../../contents.css' ],
256 on: {
257 contentDom: callback
258 }
259 } );
260
261 CKEDITOR.replace( 'editor4', {
262 extraPlugins: 'lineutils',
263 removePlugins: 'magicline',
264 allowedContent: true,
265 contentsCss: [ '../../../contents.css' ],
266 on: {
267 contentDom: callback
268 }
269 } );
270
271
272 </script>
273
274 <div id="footer">
275 <hr>
276 <p>
277 CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
278 </p>
279 <p id="copy">
280 Copyright &copy; 2003-2016, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
281 Knabben. All rights reserved.
282 </p>
283 </div>
284</body>
285</html>
diff --git a/sources/plugins/lineutils/plugin.js b/sources/plugins/lineutils/plugin.js
new file mode 100644
index 00000000..bb54fa07
--- /dev/null
+++ b/sources/plugins/lineutils/plugin.js
@@ -0,0 +1,1018 @@
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 A set of utilities to find and create horizontal spaces in edited content.
8 */
9
10'use strict';
11
12( function() {
13
14 CKEDITOR.plugins.add( 'lineutils' );
15
16 /**
17 * Determines a position relative to an element in DOM (before).
18 *
19 * @readonly
20 * @property {Number} [=0]
21 * @member CKEDITOR
22 */
23 CKEDITOR.LINEUTILS_BEFORE = 1;
24
25 /**
26 * Determines a position relative to an element in DOM (after).
27 *
28 * @readonly
29 * @property {Number} [=2]
30 * @member CKEDITOR
31 */
32 CKEDITOR.LINEUTILS_AFTER = 2;
33
34 /**
35 * Determines a position relative to an element in DOM (inside).
36 *
37 * @readonly
38 * @property {Number} [=4]
39 * @member CKEDITOR
40 */
41 CKEDITOR.LINEUTILS_INSIDE = 4;
42
43 /**
44 * A utility that traverses the DOM tree and discovers elements
45 * (relations) matching user-defined lookups.
46 *
47 * @private
48 * @class CKEDITOR.plugins.lineutils.finder
49 * @constructor Creates a Finder class instance.
50 * @param {CKEDITOR.editor} editor Editor instance that the Finder belongs to.
51 * @param {Object} def Finder's definition.
52 * @since 4.3
53 */
54 function Finder( editor, def ) {
55 CKEDITOR.tools.extend( this, {
56 editor: editor,
57 editable: editor.editable(),
58 doc: editor.document,
59 win: editor.window
60 }, def, true );
61
62 this.inline = this.editable.isInline();
63
64 if ( !this.inline ) {
65 this.frame = this.win.getFrame();
66 }
67
68 this.target = this[ this.inline ? 'editable' : 'doc' ];
69 }
70
71 Finder.prototype = {
72 /**
73 * Initializes searching for elements with every mousemove event fired.
74 * To stop searching use {@link #stop}.
75 *
76 * @param {Function} [callback] Function executed on every iteration.
77 */
78 start: function( callback ) {
79 var that = this,
80 editor = this.editor,
81 doc = this.doc,
82 el, elfp, x, y;
83
84 var moveBuffer = CKEDITOR.tools.eventsBuffer( 50, function() {
85 if ( editor.readOnly || editor.mode != 'wysiwyg' )
86 return;
87
88 that.relations = {};
89
90 // Sometimes it happens that elementFromPoint returns null (especially on IE).
91 // Any further traversal makes no sense if there's no start point. Abort.
92 // Note: In IE8 elementFromPoint may return zombie nodes of undefined nodeType,
93 // so rejecting those as well.
94 if ( !( elfp = doc.$.elementFromPoint( x, y ) ) || !elfp.nodeType ) {
95 return;
96 }
97
98 el = new CKEDITOR.dom.element( elfp );
99
100 that.traverseSearch( el );
101
102 if ( !isNaN( x + y ) ) {
103 that.pixelSearch( el, x, y );
104 }
105
106 callback && callback( that.relations, x, y );
107 } );
108
109 // Searching starting from element from point on mousemove.
110 this.listener = this.editable.attachListener( this.target, 'mousemove', function( evt ) {
111 x = evt.data.$.clientX;
112 y = evt.data.$.clientY;
113
114 moveBuffer.input();
115 } );
116
117 this.editable.attachListener( this.inline ? this.editable : this.frame, 'mouseout', function() {
118 moveBuffer.reset();
119 } );
120 },
121
122 /**
123 * Stops observing mouse events attached by {@link #start}.
124 */
125 stop: function() {
126 if ( this.listener ) {
127 this.listener.removeListener();
128 }
129 },
130
131 /**
132 * Returns a range representing the relation, according to its element
133 * and type.
134 *
135 * @param {Object} location Location containing a unique identifier and type.
136 * @returns {CKEDITOR.dom.range} Range representing the relation.
137 */
138 getRange: ( function() {
139 var where = {};
140
141 where[ CKEDITOR.LINEUTILS_BEFORE ] = CKEDITOR.POSITION_BEFORE_START;
142 where[ CKEDITOR.LINEUTILS_AFTER ] = CKEDITOR.POSITION_AFTER_END;
143 where[ CKEDITOR.LINEUTILS_INSIDE ] = CKEDITOR.POSITION_AFTER_START;
144
145 return function( location ) {
146 var range = this.editor.createRange();
147
148 range.moveToPosition( this.relations[ location.uid ].element, where[ location.type ] );
149
150 return range;
151 };
152 } )(),
153
154 /**
155 * Stores given relation in a {@link #relations} object. Processes the relation
156 * to normalize and avoid duplicates.
157 *
158 * @param {CKEDITOR.dom.element} el Element of the relation.
159 * @param {Number} type Relation, one of `CKEDITOR.LINEUTILS_AFTER`, `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_INSIDE`.
160 */
161 store: ( function() {
162 function merge( el, type, relations ) {
163 var uid = el.getUniqueId();
164
165 if ( uid in relations ) {
166 relations[ uid ].type |= type;
167 } else {
168 relations[ uid ] = { element: el, type: type };
169 }
170 }
171
172 return function( el, type ) {
173 var alt;
174
175 // Normalization to avoid duplicates:
176 // CKEDITOR.LINEUTILS_AFTER becomes CKEDITOR.LINEUTILS_BEFORE of el.getNext().
177 if ( is( type, CKEDITOR.LINEUTILS_AFTER ) && isStatic( alt = el.getNext() ) && alt.isVisible() ) {
178 merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations );
179 type ^= CKEDITOR.LINEUTILS_AFTER;
180 }
181
182 // Normalization to avoid duplicates:
183 // CKEDITOR.LINEUTILS_INSIDE becomes CKEDITOR.LINEUTILS_BEFORE of el.getFirst().
184 if ( is( type, CKEDITOR.LINEUTILS_INSIDE ) && isStatic( alt = el.getFirst() ) && alt.isVisible() ) {
185 merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations );
186 type ^= CKEDITOR.LINEUTILS_INSIDE;
187 }
188
189 merge( el, type, this.relations );
190 };
191 } )(),
192
193 /**
194 * Traverses the DOM tree towards root, checking all ancestors
195 * with lookup rules, avoiding duplicates. Stores positive relations
196 * in the {@link #relations} object.
197 *
198 * @param {CKEDITOR.dom.element} el Element which is the starting point.
199 */
200 traverseSearch: function( el ) {
201 var l, type, uid;
202
203 // Go down DOM towards root (or limit).
204 do {
205 uid = el.$[ 'data-cke-expando' ];
206
207 // This element was already visited and checked.
208 if ( uid && uid in this.relations ) {
209 continue;
210 }
211
212 if ( el.equals( this.editable ) ) {
213 return;
214 }
215
216 if ( isStatic( el ) ) {
217 // Collect all addresses yielded by lookups for that element.
218 for ( l in this.lookups ) {
219
220 if ( ( type = this.lookups[ l ]( el ) ) ) {
221 this.store( el, type );
222 }
223 }
224 }
225 } while ( !isLimit( el ) && ( el = el.getParent() ) );
226 },
227
228 /**
229 * Iterates vertically pixel-by-pixel within a given element starting
230 * from given coordinates, searching for elements in the neighborhood.
231 * Once an element is found it is processed by {@link #traverseSearch}.
232 *
233 * @param {CKEDITOR.dom.element} el Element which is the starting point.
234 * @param {Number} [x] Horizontal mouse coordinate relative to the viewport.
235 * @param {Number} [y] Vertical mouse coordinate relative to the viewport.
236 */
237 pixelSearch: ( function() {
238 var contains = CKEDITOR.env.ie || CKEDITOR.env.webkit ?
239 function( el, found ) {
240 return el.contains( found );
241 } : function( el, found ) {
242 return !!( el.compareDocumentPosition( found ) & 16 );
243 };
244
245 // Iterates pixel-by-pixel from starting coordinates, moving by defined
246 // step and getting elementFromPoint in every iteration. Iteration stops when:
247 // * A valid element is found.
248 // * Condition function returns `false` (i.e. reached boundaries of viewport).
249 // * No element is found (i.e. coordinates out of viewport).
250 // * Element found is ascendant of starting element.
251 //
252 // @param {Object} doc Native DOM document.
253 // @param {Object} el Native DOM element.
254 // @param {Number} xStart Horizontal starting coordinate to use.
255 // @param {Number} yStart Vertical starting coordinate to use.
256 // @param {Number} step Step of the algorithm.
257 // @param {Function} condition A condition relative to current vertical coordinate.
258 function iterate( el, xStart, yStart, step, condition ) {
259 var y = yStart,
260 tryouts = 0,
261 found;
262
263 while ( condition( y ) ) {
264 y += step;
265
266 // If we try and we try, and still nothing's found, let's end
267 // that party.
268 if ( ++tryouts == 25 ) {
269 return;
270 }
271
272 found = this.doc.$.elementFromPoint( xStart, y );
273
274 // Nothing found. This is crazy... but...
275 // It might be that a line, which is in different document,
276 // covers that pixel (elementFromPoint is doc-sensitive).
277 // Better let's have another try.
278 if ( !found ) {
279 continue;
280 }
281
282 // Still in the same element.
283 else if ( found == el ) {
284 tryouts = 0;
285 continue;
286 }
287
288 // Reached the edge of an element and found an ancestor or...
289 // A line, that covers that pixel. Better let's have another try.
290 else if ( !contains( el, found ) ) {
291 continue;
292 }
293
294 tryouts = 0;
295
296 // Found a valid element. Stop iterating.
297 if ( isStatic( ( found = new CKEDITOR.dom.element( found ) ) ) ) {
298 return found;
299 }
300 }
301 }
302
303 return function( el, x, y ) {
304 var paneHeight = this.win.getViewPaneSize().height,
305
306 // Try to find an element iterating *up* from the starting point.
307 neg = iterate.call( this, el.$, x, y, -1, function( y ) {
308 return y > 0;
309 } ),
310
311 // Try to find an element iterating *down* from the starting point.
312 pos = iterate.call( this, el.$, x, y, 1, function( y ) {
313 return y < paneHeight;
314 } );
315
316 if ( neg ) {
317 this.traverseSearch( neg );
318
319 // Iterate towards DOM root until neg is a direct child of el.
320 while ( !neg.getParent().equals( el ) ) {
321 neg = neg.getParent();
322 }
323 }
324
325 if ( pos ) {
326 this.traverseSearch( pos );
327
328 // Iterate towards DOM root until pos is a direct child of el.
329 while ( !pos.getParent().equals( el ) ) {
330 pos = pos.getParent();
331 }
332 }
333
334 // Iterate forwards starting from neg and backwards from
335 // pos to harvest all children of el between those elements.
336 // Stop when neg and pos meet each other or there's none of them.
337 // TODO (?) reduce number of hops forwards/backwards.
338 while ( neg || pos ) {
339 if ( neg ) {
340 neg = neg.getNext( isStatic );
341 }
342
343 if ( !neg || neg.equals( pos ) ) {
344 break;
345 }
346
347 this.traverseSearch( neg );
348
349 if ( pos ) {
350 pos = pos.getPrevious( isStatic );
351 }
352
353 if ( !pos || pos.equals( neg ) ) {
354 break;
355 }
356
357 this.traverseSearch( pos );
358 }
359 };
360 } )(),
361
362 /**
363 * Unlike {@link #traverseSearch}, it collects **all** elements from editable's DOM tree
364 * and runs lookups for every one of them, collecting relations.
365 *
366 * @returns {Object} {@link #relations}.
367 */
368 greedySearch: function() {
369 this.relations = {};
370
371 var all = this.editable.getElementsByTag( '*' ),
372 i = 0,
373 el, type, l;
374
375 while ( ( el = all.getItem( i++ ) ) ) {
376 // Don't consider editable, as it might be inline,
377 // and i.e. checking it's siblings is pointless.
378 if ( el.equals( this.editable ) ) {
379 continue;
380 }
381
382 // On IE8 element.getElementsByTagName returns comments... sic! (#13176)
383 if ( el.type != CKEDITOR.NODE_ELEMENT ) {
384 continue;
385 }
386
387 // Don't visit non-editable internals, for example widget's
388 // guts (above wrapper, below nested). Still check editable limits,
389 // as they are siblings with editable contents.
390 if ( !el.hasAttribute( 'contenteditable' ) && el.isReadOnly() ) {
391 continue;
392 }
393
394 if ( isStatic( el ) && el.isVisible() ) {
395 // Collect all addresses yielded by lookups for that element.
396 for ( l in this.lookups ) {
397 if ( ( type = this.lookups[ l ]( el ) ) ) {
398 this.store( el, type );
399 }
400 }
401 }
402 }
403
404 return this.relations;
405 }
406
407 /**
408 * Relations express elements in DOM that match user-defined {@link #lookups}.
409 * Every relation has its own `type` that determines whether
410 * it refers to the space before, after or inside the `element`.
411 * This object stores relations found by {@link #traverseSearch} or {@link #greedySearch}, structured
412 * in the following way:
413 *
414 * relations: {
415 * // Unique identifier of the element.
416 * Number: {
417 * // Element of this relation.
418 * element: {@link CKEDITOR.dom.element}
419 * // Conjunction of CKEDITOR.LINEUTILS_BEFORE, CKEDITOR.LINEUTILS_AFTER and CKEDITOR.LINEUTILS_INSIDE.
420 * type: Number
421 * },
422 * ...
423 * }
424 *
425 * @property {Object} relations
426 * @readonly
427 */
428
429 /**
430 * A set of user-defined functions used by Finder to check if an element
431 * is a valid relation, belonging to {@link #relations}.
432 * When the criterion is met, lookup returns a logical conjunction of `CKEDITOR.LINEUTILS_BEFORE`,
433 * `CKEDITOR.LINEUTILS_AFTER` or `CKEDITOR.LINEUTILS_INSIDE`.
434 *
435 * Lookups are passed along with Finder's definition.
436 *
437 * lookups: {
438 * 'some lookup': function( el ) {
439 * if ( someCondition )
440 * return CKEDITOR.LINEUTILS_BEFORE;
441 * },
442 * ...
443 * }
444 *
445 * @property {Object} lookups
446 */
447 };
448
449
450 /**
451 * A utility that analyses relations found by
452 * CKEDITOR.plugins.lineutils.finder and locates them
453 * in the viewport as horizontal lines of specific coordinates.
454 *
455 * @private
456 * @class CKEDITOR.plugins.lineutils.locator
457 * @constructor Creates a Locator class instance.
458 * @param {CKEDITOR.editor} editor Editor instance that Locator belongs to.
459 * @since 4.3
460 */
461 function Locator( editor, def ) {
462 CKEDITOR.tools.extend( this, def, {
463 editor: editor
464 }, true );
465 }
466
467 Locator.prototype = {
468 /**
469 * Locates the Y coordinate for all types of every single relation and stores
470 * them in an object.
471 *
472 * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}.
473 * @returns {Object} {@link #locations}.
474 */
475 locate: ( function() {
476 function locateSibling( rel, type ) {
477 var sib = rel.element[ type === CKEDITOR.LINEUTILS_BEFORE ? 'getPrevious' : 'getNext' ]();
478
479 // Return the middle point between siblings.
480 if ( sib && isStatic( sib ) ) {
481 rel.siblingRect = sib.getClientRect();
482
483 if ( type == CKEDITOR.LINEUTILS_BEFORE ) {
484 return ( rel.siblingRect.bottom + rel.elementRect.top ) / 2;
485 } else {
486 return ( rel.elementRect.bottom + rel.siblingRect.top ) / 2;
487 }
488 }
489
490 // If there's no sibling, use the edge of an element.
491 else {
492 if ( type == CKEDITOR.LINEUTILS_BEFORE ) {
493 return rel.elementRect.top;
494 } else {
495 return rel.elementRect.bottom;
496 }
497 }
498 }
499
500 return function( relations ) {
501 var rel;
502
503 this.locations = {};
504
505 for ( var uid in relations ) {
506 rel = relations[ uid ];
507 rel.elementRect = rel.element.getClientRect();
508
509 if ( is( rel.type, CKEDITOR.LINEUTILS_BEFORE ) ) {
510 this.store( uid, CKEDITOR.LINEUTILS_BEFORE, locateSibling( rel, CKEDITOR.LINEUTILS_BEFORE ) );
511 }
512
513 if ( is( rel.type, CKEDITOR.LINEUTILS_AFTER ) ) {
514 this.store( uid, CKEDITOR.LINEUTILS_AFTER, locateSibling( rel, CKEDITOR.LINEUTILS_AFTER ) );
515 }
516
517 // The middle point of the element.
518 if ( is( rel.type, CKEDITOR.LINEUTILS_INSIDE ) ) {
519 this.store( uid, CKEDITOR.LINEUTILS_INSIDE, ( rel.elementRect.top + rel.elementRect.bottom ) / 2 );
520 }
521 }
522
523 return this.locations;
524 };
525 } )(),
526
527 /**
528 * Calculates distances from every location to given vertical coordinate
529 * and sorts locations according to that distance.
530 *
531 * @param {Number} y The vertical coordinate used for sorting, used as a reference.
532 * @param {Number} [howMany] Determines the number of "closest locations" to be returned.
533 * @returns {Array} Sorted, array representation of {@link #locations}.
534 */
535 sort: ( function() {
536 var locations, sorted,
537 dist, i;
538
539 function distance( y, uid, type ) {
540 return Math.abs( y - locations[ uid ][ type ] );
541 }
542
543 return function( y, howMany ) {
544 locations = this.locations;
545 sorted = [];
546
547 for ( var uid in locations ) {
548 for ( var type in locations[ uid ] ) {
549 dist = distance( y, uid, type );
550
551 // An array is empty.
552 if ( !sorted.length ) {
553 sorted.push( { uid: +uid, type: type, dist: dist } );
554 } else {
555 // Sort the array on fly when it's populated.
556 for ( i = 0; i < sorted.length; i++ ) {
557 if ( dist < sorted[ i ].dist ) {
558 sorted.splice( i, 0, { uid: +uid, type: type, dist: dist } );
559 break;
560 }
561 }
562
563 // Nothing was inserted, so the distance is bigger than
564 // any of already calculated: push to the end.
565 if ( i == sorted.length ) {
566 sorted.push( { uid: +uid, type: type, dist: dist } );
567 }
568 }
569 }
570 }
571
572 if ( typeof howMany != 'undefined' ) {
573 return sorted.slice( 0, howMany );
574 } else {
575 return sorted;
576 }
577 };
578 } )(),
579
580 /**
581 * Stores the location in a collection.
582 *
583 * @param {Number} uid Unique identifier of the relation.
584 * @param {Number} type One of `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_AFTER` and `CKEDITOR.LINEUTILS_INSIDE`.
585 * @param {Number} y Vertical position of the relation.
586 */
587 store: function( uid, type, y ) {
588 if ( !this.locations[ uid ] ) {
589 this.locations[ uid ] = {};
590 }
591
592 this.locations[ uid ][ type ] = y;
593 }
594
595 /**
596 * @readonly
597 * @property {Object} locations
598 */
599 };
600
601 var tipCss = {
602 display: 'block',
603 width: '0px',
604 height: '0px',
605 'border-color': 'transparent',
606 'border-style': 'solid',
607 position: 'absolute',
608 top: '-6px'
609 },
610
611 lineStyle = {
612 height: '0px',
613 'border-top': '1px dashed red',
614 position: 'absolute',
615 'z-index': 9999
616 },
617
618 lineTpl =
619 '<div data-cke-lineutils-line="1" class="cke_reset_all" style="{lineStyle}">' +
620 '<span style="{tipLeftStyle}">&nbsp;</span>' +
621 '<span style="{tipRightStyle}">&nbsp;</span>' +
622 '</div>';
623
624 /**
625 * A utility that draws horizontal lines in DOM according to locations
626 * returned by CKEDITOR.plugins.lineutils.locator.
627 *
628 * @private
629 * @class CKEDITOR.plugins.lineutils.liner
630 * @constructor Creates a Liner class instance.
631 * @param {CKEDITOR.editor} editor Editor instance that Liner belongs to.
632 * @param {Object} def Liner's definition.
633 * @since 4.3
634 */
635 function Liner( editor, def ) {
636 var editable = editor.editable();
637
638 CKEDITOR.tools.extend( this, {
639 editor: editor,
640 editable: editable,
641 inline: editable.isInline(),
642 doc: editor.document,
643 win: editor.window,
644 container: CKEDITOR.document.getBody(),
645 winTop: CKEDITOR.document.getWindow()
646 }, def, true );
647
648 this.hidden = {};
649 this.visible = {};
650
651 if ( !this.inline ) {
652 this.frame = this.win.getFrame();
653 }
654
655 this.queryViewport();
656
657 // Callbacks must be wrapped. Otherwise they're not attached
658 // to global DOM objects (i.e. topmost window) for every editor
659 // because they're treated as duplicates. They belong to the
660 // same prototype shared among Liner instances.
661 var queryViewport = CKEDITOR.tools.bind( this.queryViewport, this ),
662 hideVisible = CKEDITOR.tools.bind( this.hideVisible, this ),
663 removeAll = CKEDITOR.tools.bind( this.removeAll, this );
664
665 editable.attachListener( this.winTop, 'resize', queryViewport );
666 editable.attachListener( this.winTop, 'scroll', queryViewport );
667
668 editable.attachListener( this.winTop, 'resize', hideVisible );
669 editable.attachListener( this.win, 'scroll', hideVisible );
670
671 editable.attachListener( this.inline ? editable : this.frame, 'mouseout', function( evt ) {
672 var x = evt.data.$.clientX,
673 y = evt.data.$.clientY;
674
675 this.queryViewport();
676
677 // Check if mouse is out of the element (iframe/editable).
678 if ( x <= this.rect.left || x >= this.rect.right || y <= this.rect.top || y >= this.rect.bottom ) {
679 this.hideVisible();
680 }
681
682 // Check if mouse is out of the top-window vieport.
683 if ( x <= 0 || x >= this.winTopPane.width || y <= 0 || y >= this.winTopPane.height ) {
684 this.hideVisible();
685 }
686 }, this );
687
688 editable.attachListener( editor, 'resize', queryViewport );
689 editable.attachListener( editor, 'mode', removeAll );
690 editor.on( 'destroy', removeAll );
691
692 this.lineTpl = new CKEDITOR.template( lineTpl ).output( {
693 lineStyle: CKEDITOR.tools.writeCssText(
694 CKEDITOR.tools.extend( {}, lineStyle, this.lineStyle, true )
695 ),
696 tipLeftStyle: CKEDITOR.tools.writeCssText(
697 CKEDITOR.tools.extend( {}, tipCss, {
698 left: '0px',
699 'border-left-color': 'red',
700 'border-width': '6px 0 6px 6px'
701 }, this.tipCss, this.tipLeftStyle, true )
702 ),
703 tipRightStyle: CKEDITOR.tools.writeCssText(
704 CKEDITOR.tools.extend( {}, tipCss, {
705 right: '0px',
706 'border-right-color': 'red',
707 'border-width': '6px 6px 6px 0'
708 }, this.tipCss, this.tipRightStyle, true )
709 )
710 } );
711 }
712
713 Liner.prototype = {
714 /**
715 * Permanently removes all lines (both hidden and visible) from DOM.
716 */
717 removeAll: function() {
718 var l;
719
720 for ( l in this.hidden ) {
721 this.hidden[ l ].remove();
722 delete this.hidden[ l ];
723 }
724
725 for ( l in this.visible ) {
726 this.visible[ l ].remove();
727 delete this.visible[ l ];
728 }
729 },
730
731 /**
732 * Hides a given line.
733 *
734 * @param {CKEDITOR.dom.element} line The line to be hidden.
735 */
736 hideLine: function( line ) {
737 var uid = line.getUniqueId();
738
739 line.hide();
740
741 this.hidden[ uid ] = line;
742 delete this.visible[ uid ];
743 },
744
745 /**
746 * Shows a given line.
747 *
748 * @param {CKEDITOR.dom.element} line The line to be shown.
749 */
750 showLine: function( line ) {
751 var uid = line.getUniqueId();
752
753 line.show();
754
755 this.visible[ uid ] = line;
756 delete this.hidden[ uid ];
757 },
758
759 /**
760 * Hides all visible lines.
761 */
762 hideVisible: function() {
763 for ( var l in this.visible ) {
764 this.hideLine( this.visible[ l ] );
765 }
766 },
767
768 /**
769 * Shows a line at given location.
770 *
771 * @param {Object} location Location object containing the unique identifier of the relation
772 * and its type. Usually returned by {@link CKEDITOR.plugins.lineutils.locator#sort}.
773 * @param {Function} [callback] A callback to be called once the line is shown.
774 */
775 placeLine: function( location, callback ) {
776 var styles, line, l;
777
778 // No style means that line would be out of viewport.
779 if ( !( styles = this.getStyle( location.uid, location.type ) ) ) {
780 return;
781 }
782
783 // Search for any visible line of a different hash first.
784 // It's faster to re-position visible line than to show it.
785 for ( l in this.visible ) {
786 if ( this.visible[ l ].getCustomData( 'hash' ) !== this.hash ) {
787 line = this.visible[ l ];
788 break;
789 }
790 }
791
792 // Search for any hidden line of a different hash.
793 if ( !line ) {
794 for ( l in this.hidden ) {
795 if ( this.hidden[ l ].getCustomData( 'hash' ) !== this.hash ) {
796 this.showLine( ( line = this.hidden[ l ] ) );
797 break;
798 }
799 }
800 }
801
802 // If no line available, add the new one.
803 if ( !line ) {
804 this.showLine( ( line = this.addLine() ) );
805 }
806
807 // Mark the line with current hash.
808 line.setCustomData( 'hash', this.hash );
809
810 // Mark the line as visible.
811 this.visible[ line.getUniqueId() ] = line;
812
813 line.setStyles( styles );
814
815 callback && callback( line );
816 },
817
818 /**
819 * Creates a style set to be used by the line, representing a particular
820 * relation (location).
821 *
822 * @param {Number} uid Unique identifier of the relation.
823 * @param {Number} type Type of the relation.
824 * @returns {Object} An object containing styles.
825 */
826 getStyle: function( uid, type ) {
827 var rel = this.relations[ uid ],
828 loc = this.locations[ uid ][ type ],
829 styles = {},
830 hdiff;
831
832 // Line should be between two elements.
833 if ( rel.siblingRect ) {
834 styles.width = Math.max( rel.siblingRect.width, rel.elementRect.width );
835 }
836 // Line is relative to a single element.
837 else {
838 styles.width = rel.elementRect.width;
839 }
840
841 // Let's calculate the vertical position of the line.
842 if ( this.inline ) {
843 // (#13155)
844 styles.top = loc + this.winTopScroll.y - this.rect.relativeY;
845 } else {
846 styles.top = this.rect.top + this.winTopScroll.y + loc;
847 }
848
849 // Check if line would be vertically out of the viewport.
850 if ( styles.top - this.winTopScroll.y < this.rect.top || styles.top - this.winTopScroll.y > this.rect.bottom ) {
851 return false;
852 }
853
854 // Now let's calculate the horizontal alignment (left and width).
855 if ( this.inline ) {
856 // (#13155)
857 styles.left = rel.elementRect.left - this.rect.relativeX;
858 } else {
859 if ( rel.elementRect.left > 0 )
860 styles.left = this.rect.left + rel.elementRect.left;
861
862 // H-scroll case. Left edge of element may be out of viewport.
863 else {
864 styles.width += rel.elementRect.left;
865 styles.left = this.rect.left;
866 }
867
868 // H-scroll case. Right edge of element may be out of viewport.
869 if ( ( hdiff = styles.left + styles.width - ( this.rect.left + this.winPane.width ) ) > 0 ) {
870 styles.width -= hdiff;
871 }
872 }
873
874 // Finally include horizontal scroll of the global window.
875 styles.left += this.winTopScroll.x;
876
877 // Append 'px' to style values.
878 for ( var style in styles ) {
879 styles[ style ] = CKEDITOR.tools.cssLength( styles[ style ] );
880 }
881
882 return styles;
883 },
884
885 /**
886 * Adds a new line to DOM.
887 *
888 * @returns {CKEDITOR.dom.element} A brand-new line.
889 */
890 addLine: function() {
891 var line = CKEDITOR.dom.element.createFromHtml( this.lineTpl );
892
893 line.appendTo( this.container );
894
895 return line;
896 },
897
898 /**
899 * Assigns a unique hash to the instance that is later used
900 * to tell unwanted lines from new ones. This method **must** be called
901 * before a new set of relations is to be visualized so {@link #cleanup}
902 * eventually hides obsolete lines. This is because lines
903 * are re-used between {@link #placeLine} calls and the number of
904 * necessary ones may vary depending on the number of relations.
905 *
906 * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}.
907 * @param {Object} locations {@link CKEDITOR.plugins.lineutils.locator#locations}.
908 */
909 prepare: function( relations, locations ) {
910 this.relations = relations;
911 this.locations = locations;
912 this.hash = Math.random();
913 },
914
915 /**
916 * Hides all visible lines that do not belong to current hash
917 * and no longer represent relations (locations).
918 *
919 * See also: {@link #prepare}.
920 */
921 cleanup: function() {
922 var line;
923
924 for ( var l in this.visible ) {
925 line = this.visible[ l ];
926
927 if ( line.getCustomData( 'hash' ) !== this.hash ) {
928 this.hideLine( line );
929 }
930 }
931 },
932
933 /**
934 * Queries dimensions of the viewport, editable, frame etc.
935 * that are used for correct positioning of the line.
936 */
937 queryViewport: function() {
938 this.winPane = this.win.getViewPaneSize();
939 this.winTopScroll = this.winTop.getScrollPosition();
940 this.winTopPane = this.winTop.getViewPaneSize();
941
942 // (#13155)
943 this.rect = this.getClientRect( this.inline ? this.editable : this.frame );
944 },
945
946 /**
947 * Returns `boundingClientRect` of an element, shifted by the position
948 * of `container` when the container is not `static` (#13155).
949 *
950 * See also: {@link CKEDITOR.dom.element#getClientRect}.
951 *
952 * @param {CKEDITOR.dom.element} el A DOM element.
953 * @returns {Object} A shifted rect, extended by `relativeY` and `relativeX` properties.
954 */
955 getClientRect: function( el ) {
956 var rect = el.getClientRect(),
957 relativeContainerDocPosition = this.container.getDocumentPosition(),
958 relativeContainerComputedPosition = this.container.getComputedStyle( 'position' );
959
960 // Static or not, those values are used to offset the position of the line so they cannot be undefined.
961 rect.relativeX = rect.relativeY = 0;
962
963 if ( relativeContainerComputedPosition != 'static' ) {
964 // Remember the offset used to shift the clientRect.
965 rect.relativeY = relativeContainerDocPosition.y;
966 rect.relativeX = relativeContainerDocPosition.x;
967
968 rect.top -= rect.relativeY;
969 rect.bottom -= rect.relativeY;
970 rect.left -= rect.relativeX;
971 rect.right -= rect.relativeX;
972 }
973
974 return rect;
975 }
976 };
977
978 function is( type, flag ) {
979 return type & flag;
980 }
981
982 var floats = { left: 1, right: 1, center: 1 },
983 positions = { absolute: 1, fixed: 1 };
984
985 function isElement( node ) {
986 return node && node.type == CKEDITOR.NODE_ELEMENT;
987 }
988
989 function isFloated( el ) {
990 return !!( floats[ el.getComputedStyle( 'float' ) ] || floats[ el.getAttribute( 'align' ) ] );
991 }
992
993 function isPositioned( el ) {
994 return !!positions[ el.getComputedStyle( 'position' ) ];
995 }
996
997 function isLimit( node ) {
998 return isElement( node ) && node.getAttribute( 'contenteditable' ) == 'true';
999 }
1000
1001 function isStatic( node ) {
1002 return isElement( node ) && !isFloated( node ) && !isPositioned( node );
1003 }
1004
1005 /**
1006 * Global namespace storing definitions and global helpers for the Line Utilities plugin.
1007 *
1008 * @private
1009 * @class
1010 * @singleton
1011 * @since 4.3
1012 */
1013 CKEDITOR.plugins.lineutils = {
1014 finder: Finder,
1015 locator: Locator,
1016 liner: Liner
1017 };
1018} )();