tinymce-rails 3.5.4.1 → 3.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/lib/tinymce/rails/version.rb +2 -2
  2. data/vendor/assets/javascripts/tinymce/plugins/advimage/js/image.js +5 -3
  3. data/vendor/assets/javascripts/tinymce/plugins/autolink/editor_plugin.js +1 -1
  4. data/vendor/assets/javascripts/tinymce/plugins/autolink/editor_plugin_src.js +184 -181
  5. data/vendor/assets/javascripts/tinymce/plugins/autoresize/editor_plugin_src.js +119 -119
  6. data/vendor/assets/javascripts/tinymce/plugins/emotions/langs/en_dlg.js +1 -1
  7. data/vendor/assets/javascripts/tinymce/plugins/example_dependency/editor_plugin_src.js +50 -50
  8. data/vendor/assets/javascripts/tinymce/plugins/fullscreen/editor_plugin.js +1 -1
  9. data/vendor/assets/javascripts/tinymce/plugins/fullscreen/editor_plugin_src.js +4 -4
  10. data/vendor/assets/javascripts/tinymce/plugins/lists/editor_plugin.js +1 -1
  11. data/vendor/assets/javascripts/tinymce/plugins/lists/editor_plugin_src.js +956 -952
  12. data/vendor/assets/javascripts/tinymce/plugins/media/js/media.js +34 -1
  13. data/vendor/assets/javascripts/tinymce/plugins/media/langs/en_dlg.js +1 -1
  14. data/vendor/assets/javascripts/tinymce/plugins/noneditable/editor_plugin.js +1 -1
  15. data/vendor/assets/javascripts/tinymce/plugins/noneditable/editor_plugin_src.js +1 -4
  16. data/vendor/assets/javascripts/tinymce/plugins/style/langs/en_dlg.js +1 -1
  17. data/vendor/assets/javascripts/tinymce/plugins/style/props.htm +845 -845
  18. data/vendor/assets/javascripts/tinymce/plugins/style/readme.txt +19 -19
  19. data/vendor/assets/javascripts/tinymce/plugins/tabfocus/editor_plugin_src.js +122 -122
  20. data/vendor/assets/javascripts/tinymce/plugins/table/editor_plugin_src.js +1449 -1449
  21. data/vendor/assets/javascripts/tinymce/themes/advanced/editor_template.js +1 -1
  22. data/vendor/assets/javascripts/tinymce/themes/advanced/editor_template_src.js +2 -1
  23. data/vendor/assets/javascripts/tinymce/themes/advanced/js/color_picker.js +345 -345
  24. data/vendor/assets/javascripts/tinymce/themes/advanced/langs/en_dlg.js +1 -1
  25. data/vendor/assets/javascripts/tinymce/tiny_mce.js +1 -1
  26. data/vendor/assets/javascripts/tinymce/tiny_mce_jquery.js +1 -1
  27. data/vendor/assets/javascripts/tinymce/tiny_mce_jquery_src.js +277 -75
  28. data/vendor/assets/javascripts/tinymce/tiny_mce_src.js +277 -75
  29. metadata +5 -7
@@ -1,19 +1,19 @@
1
- Edit CSS Style plug-in notes
2
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
- Unlike WYSIWYG editor functionality that operates only on the selected text,
4
- typically by inserting new HTML elements with the specified styles.
5
- This plug-in operates on the HTML blocks surrounding the selected text.
6
- No new HTML elements are created.
7
-
8
- This plug-in only operates on the surrounding blocks and not the nearest
9
- parent node. This means that if a block encapsulates a node,
10
- e.g <p><span>text</span></p>, then only the styles in the block are
11
- recognized, not those in the span.
12
-
13
- When selecting text that includes multiple blocks at the same level (peers),
14
- this plug-in accumulates the specified styles in all of the surrounding blocks
15
- and populates the dialogue checkboxes accordingly. There is no differentiation
16
- between styles set in all the blocks versus styles set in some of the blocks.
17
-
18
- When the [Update] or [Apply] buttons are pressed, the styles selected in the
19
- checkboxes are applied to all blocks that surround the selected text.
1
+ Edit CSS Style plug-in notes
2
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
+ Unlike WYSIWYG editor functionality that operates only on the selected text,
4
+ typically by inserting new HTML elements with the specified styles.
5
+ This plug-in operates on the HTML blocks surrounding the selected text.
6
+ No new HTML elements are created.
7
+
8
+ This plug-in only operates on the surrounding blocks and not the nearest
9
+ parent node. This means that if a block encapsulates a node,
10
+ e.g <p><span>text</span></p>, then only the styles in the block are
11
+ recognized, not those in the span.
12
+
13
+ When selecting text that includes multiple blocks at the same level (peers),
14
+ this plug-in accumulates the specified styles in all of the surrounding blocks
15
+ and populates the dialogue checkboxes accordingly. There is no differentiation
16
+ between styles set in all the blocks versus styles set in some of the blocks.
17
+
18
+ When the [Update] or [Apply] buttons are pressed, the styles selected in the
19
+ checkboxes are applied to all blocks that surround the selected text.
@@ -1,122 +1,122 @@
1
- /**
2
- * editor_plugin_src.js
3
- *
4
- * Copyright 2009, Moxiecode Systems AB
5
- * Released under LGPL License.
6
- *
7
- * License: http://tinymce.moxiecode.com/license
8
- * Contributing: http://tinymce.moxiecode.com/contributing
9
- */
10
-
11
- (function() {
12
- var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, explode = tinymce.explode;
13
-
14
- tinymce.create('tinymce.plugins.TabFocusPlugin', {
15
- init : function(ed, url) {
16
- function tabCancel(ed, e) {
17
- if (e.keyCode === 9)
18
- return Event.cancel(e);
19
- }
20
-
21
- function tabHandler(ed, e) {
22
- var x, i, f, el, v;
23
-
24
- function find(d) {
25
- el = DOM.select(':input:enabled,*[tabindex]:not(iframe)');
26
-
27
- function canSelectRecursive(e) {
28
- return e.nodeName==="BODY" || (e.type != 'hidden' &&
29
- !(e.style.display == "none") &&
30
- !(e.style.visibility == "hidden") && canSelectRecursive(e.parentNode));
31
- }
32
- function canSelectInOldIe(el) {
33
- return el.attributes["tabIndex"].specified || el.nodeName == "INPUT" || el.nodeName == "TEXTAREA";
34
- }
35
- function isOldIe() {
36
- return tinymce.isIE6 || tinymce.isIE7;
37
- }
38
- function canSelect(el) {
39
- return ((!isOldIe() || canSelectInOldIe(el))) && el.getAttribute("tabindex") != '-1' && canSelectRecursive(el);
40
- }
41
-
42
- each(el, function(e, i) {
43
- if (e.id == ed.id) {
44
- x = i;
45
- return false;
46
- }
47
- });
48
- if (d > 0) {
49
- for (i = x + 1; i < el.length; i++) {
50
- if (canSelect(el[i]))
51
- return el[i];
52
- }
53
- } else {
54
- for (i = x - 1; i >= 0; i--) {
55
- if (canSelect(el[i]))
56
- return el[i];
57
- }
58
- }
59
-
60
- return null;
61
- }
62
-
63
- if (e.keyCode === 9) {
64
- v = explode(ed.getParam('tab_focus', ed.getParam('tabfocus_elements', ':prev,:next')));
65
-
66
- if (v.length == 1) {
67
- v[1] = v[0];
68
- v[0] = ':prev';
69
- }
70
-
71
- // Find element to focus
72
- if (e.shiftKey) {
73
- if (v[0] == ':prev')
74
- el = find(-1);
75
- else
76
- el = DOM.get(v[0]);
77
- } else {
78
- if (v[1] == ':next')
79
- el = find(1);
80
- else
81
- el = DOM.get(v[1]);
82
- }
83
-
84
- if (el) {
85
- if (el.id && (ed = tinymce.get(el.id || el.name)))
86
- ed.focus();
87
- else
88
- window.setTimeout(function() {
89
- if (!tinymce.isWebKit)
90
- window.focus();
91
- el.focus();
92
- }, 10);
93
-
94
- return Event.cancel(e);
95
- }
96
- }
97
- }
98
-
99
- ed.onKeyUp.add(tabCancel);
100
-
101
- if (tinymce.isGecko) {
102
- ed.onKeyPress.add(tabHandler);
103
- ed.onKeyDown.add(tabCancel);
104
- } else
105
- ed.onKeyDown.add(tabHandler);
106
-
107
- },
108
-
109
- getInfo : function() {
110
- return {
111
- longname : 'Tabfocus',
112
- author : 'Moxiecode Systems AB',
113
- authorurl : 'http://tinymce.moxiecode.com',
114
- infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus',
115
- version : tinymce.majorVersion + "." + tinymce.minorVersion
116
- };
117
- }
118
- });
119
-
120
- // Register plugin
121
- tinymce.PluginManager.add('tabfocus', tinymce.plugins.TabFocusPlugin);
122
- })();
1
+ /**
2
+ * editor_plugin_src.js
3
+ *
4
+ * Copyright 2009, Moxiecode Systems AB
5
+ * Released under LGPL License.
6
+ *
7
+ * License: http://tinymce.moxiecode.com/license
8
+ * Contributing: http://tinymce.moxiecode.com/contributing
9
+ */
10
+
11
+ (function() {
12
+ var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, explode = tinymce.explode;
13
+
14
+ tinymce.create('tinymce.plugins.TabFocusPlugin', {
15
+ init : function(ed, url) {
16
+ function tabCancel(ed, e) {
17
+ if (e.keyCode === 9)
18
+ return Event.cancel(e);
19
+ }
20
+
21
+ function tabHandler(ed, e) {
22
+ var x, i, f, el, v;
23
+
24
+ function find(d) {
25
+ el = DOM.select(':input:enabled,*[tabindex]:not(iframe)');
26
+
27
+ function canSelectRecursive(e) {
28
+ return e.nodeName==="BODY" || (e.type != 'hidden' &&
29
+ !(e.style.display == "none") &&
30
+ !(e.style.visibility == "hidden") && canSelectRecursive(e.parentNode));
31
+ }
32
+ function canSelectInOldIe(el) {
33
+ return el.attributes["tabIndex"].specified || el.nodeName == "INPUT" || el.nodeName == "TEXTAREA";
34
+ }
35
+ function isOldIe() {
36
+ return tinymce.isIE6 || tinymce.isIE7;
37
+ }
38
+ function canSelect(el) {
39
+ return ((!isOldIe() || canSelectInOldIe(el))) && el.getAttribute("tabindex") != '-1' && canSelectRecursive(el);
40
+ }
41
+
42
+ each(el, function(e, i) {
43
+ if (e.id == ed.id) {
44
+ x = i;
45
+ return false;
46
+ }
47
+ });
48
+ if (d > 0) {
49
+ for (i = x + 1; i < el.length; i++) {
50
+ if (canSelect(el[i]))
51
+ return el[i];
52
+ }
53
+ } else {
54
+ for (i = x - 1; i >= 0; i--) {
55
+ if (canSelect(el[i]))
56
+ return el[i];
57
+ }
58
+ }
59
+
60
+ return null;
61
+ }
62
+
63
+ if (e.keyCode === 9) {
64
+ v = explode(ed.getParam('tab_focus', ed.getParam('tabfocus_elements', ':prev,:next')));
65
+
66
+ if (v.length == 1) {
67
+ v[1] = v[0];
68
+ v[0] = ':prev';
69
+ }
70
+
71
+ // Find element to focus
72
+ if (e.shiftKey) {
73
+ if (v[0] == ':prev')
74
+ el = find(-1);
75
+ else
76
+ el = DOM.get(v[0]);
77
+ } else {
78
+ if (v[1] == ':next')
79
+ el = find(1);
80
+ else
81
+ el = DOM.get(v[1]);
82
+ }
83
+
84
+ if (el) {
85
+ if (el.id && (ed = tinymce.get(el.id || el.name)))
86
+ ed.focus();
87
+ else
88
+ window.setTimeout(function() {
89
+ if (!tinymce.isWebKit)
90
+ window.focus();
91
+ el.focus();
92
+ }, 10);
93
+
94
+ return Event.cancel(e);
95
+ }
96
+ }
97
+ }
98
+
99
+ ed.onKeyUp.add(tabCancel);
100
+
101
+ if (tinymce.isGecko) {
102
+ ed.onKeyPress.add(tabHandler);
103
+ ed.onKeyDown.add(tabCancel);
104
+ } else
105
+ ed.onKeyDown.add(tabHandler);
106
+
107
+ },
108
+
109
+ getInfo : function() {
110
+ return {
111
+ longname : 'Tabfocus',
112
+ author : 'Moxiecode Systems AB',
113
+ authorurl : 'http://tinymce.moxiecode.com',
114
+ infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus',
115
+ version : tinymce.majorVersion + "." + tinymce.minorVersion
116
+ };
117
+ }
118
+ });
119
+
120
+ // Register plugin
121
+ tinymce.PluginManager.add('tabfocus', tinymce.plugins.TabFocusPlugin);
122
+ })();
@@ -1,1449 +1,1449 @@
1
- /**
2
- * editor_plugin_src.js
3
- *
4
- * Copyright 2009, Moxiecode Systems AB
5
- * Released under LGPL License.
6
- *
7
- * License: http://tinymce.moxiecode.com/license
8
- * Contributing: http://tinymce.moxiecode.com/contributing
9
- */
10
-
11
- (function(tinymce) {
12
- var each = tinymce.each;
13
-
14
- // Checks if the selection/caret is at the start of the specified block element
15
- function isAtStart(rng, par) {
16
- var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
17
-
18
- rng2.setStartBefore(par);
19
- rng2.setEnd(rng.endContainer, rng.endOffset);
20
-
21
- elm = doc.createElement('body');
22
- elm.appendChild(rng2.cloneContents());
23
-
24
- // Check for text characters of other elements that should be treated as content
25
- return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
26
- };
27
-
28
- function getSpanVal(td, name) {
29
- return parseInt(td.getAttribute(name) || 1);
30
- }
31
-
32
- /**
33
- * Table Grid class.
34
- */
35
- function TableGrid(table, dom, selection) {
36
- var grid, startPos, endPos, selectedCell;
37
-
38
- buildGrid();
39
- selectedCell = dom.getParent(selection.getStart(), 'th,td');
40
- if (selectedCell) {
41
- startPos = getPos(selectedCell);
42
- endPos = findEndPos();
43
- selectedCell = getCell(startPos.x, startPos.y);
44
- }
45
-
46
- function cloneNode(node, children) {
47
- node = node.cloneNode(children);
48
- node.removeAttribute('id');
49
-
50
- return node;
51
- }
52
-
53
- function buildGrid() {
54
- var startY = 0;
55
-
56
- grid = [];
57
-
58
- each(['thead', 'tbody', 'tfoot'], function(part) {
59
- var rows = dom.select('> ' + part + ' tr', table);
60
-
61
- each(rows, function(tr, y) {
62
- y += startY;
63
-
64
- each(dom.select('> td, > th', tr), function(td, x) {
65
- var x2, y2, rowspan, colspan;
66
-
67
- // Skip over existing cells produced by rowspan
68
- if (grid[y]) {
69
- while (grid[y][x])
70
- x++;
71
- }
72
-
73
- // Get col/rowspan from cell
74
- rowspan = getSpanVal(td, 'rowspan');
75
- colspan = getSpanVal(td, 'colspan');
76
-
77
- // Fill out rowspan/colspan right and down
78
- for (y2 = y; y2 < y + rowspan; y2++) {
79
- if (!grid[y2])
80
- grid[y2] = [];
81
-
82
- for (x2 = x; x2 < x + colspan; x2++) {
83
- grid[y2][x2] = {
84
- part : part,
85
- real : y2 == y && x2 == x,
86
- elm : td,
87
- rowspan : rowspan,
88
- colspan : colspan
89
- };
90
- }
91
- }
92
- });
93
- });
94
-
95
- startY += rows.length;
96
- });
97
- };
98
-
99
- function getCell(x, y) {
100
- var row;
101
-
102
- row = grid[y];
103
- if (row)
104
- return row[x];
105
- };
106
-
107
- function setSpanVal(td, name, val) {
108
- if (td) {
109
- val = parseInt(val);
110
-
111
- if (val === 1)
112
- td.removeAttribute(name, 1);
113
- else
114
- td.setAttribute(name, val, 1);
115
- }
116
- }
117
-
118
- function isCellSelected(cell) {
119
- return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell);
120
- };
121
-
122
- function getSelectedRows() {
123
- var rows = [];
124
-
125
- each(table.rows, function(row) {
126
- each(row.cells, function(cell) {
127
- if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
128
- rows.push(row);
129
- return false;
130
- }
131
- });
132
- });
133
-
134
- return rows;
135
- };
136
-
137
- function deleteTable() {
138
- var rng = dom.createRng();
139
-
140
- rng.setStartAfter(table);
141
- rng.setEndAfter(table);
142
-
143
- selection.setRng(rng);
144
-
145
- dom.remove(table);
146
- };
147
-
148
- function cloneCell(cell) {
149
- var formatNode;
150
-
151
- // Clone formats
152
- tinymce.walk(cell, function(node) {
153
- var curNode;
154
-
155
- if (node.nodeType == 3) {
156
- each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
157
- node = cloneNode(node, false);
158
-
159
- if (!formatNode)
160
- formatNode = curNode = node;
161
- else if (curNode)
162
- curNode.appendChild(node);
163
-
164
- curNode = node;
165
- });
166
-
167
- // Add something to the inner node
168
- if (curNode)
169
- curNode.innerHTML = tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />';
170
-
171
- return false;
172
- }
173
- }, 'childNodes');
174
-
175
- cell = cloneNode(cell, false);
176
- setSpanVal(cell, 'rowSpan', 1);
177
- setSpanVal(cell, 'colSpan', 1);
178
-
179
- if (formatNode) {
180
- cell.appendChild(formatNode);
181
- } else {
182
- if (!tinymce.isIE)
183
- cell.innerHTML = '<br data-mce-bogus="1" />';
184
- }
185
-
186
- return cell;
187
- };
188
-
189
- function cleanup() {
190
- var rng = dom.createRng();
191
-
192
- // Empty rows
193
- each(dom.select('tr', table), function(tr) {
194
- if (tr.cells.length == 0)
195
- dom.remove(tr);
196
- });
197
-
198
- // Empty table
199
- if (dom.select('tr', table).length == 0) {
200
- rng.setStartAfter(table);
201
- rng.setEndAfter(table);
202
- selection.setRng(rng);
203
- dom.remove(table);
204
- return;
205
- }
206
-
207
- // Empty header/body/footer
208
- each(dom.select('thead,tbody,tfoot', table), function(part) {
209
- if (part.rows.length == 0)
210
- dom.remove(part);
211
- });
212
-
213
- // Restore selection to start position if it still exists
214
- buildGrid();
215
-
216
- // Restore the selection to the closest table position
217
- row = grid[Math.min(grid.length - 1, startPos.y)];
218
- if (row) {
219
- selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
220
- selection.collapse(true);
221
- }
222
- };
223
-
224
- function fillLeftDown(x, y, rows, cols) {
225
- var tr, x2, r, c, cell;
226
-
227
- tr = grid[y][x].elm.parentNode;
228
- for (r = 1; r <= rows; r++) {
229
- tr = dom.getNext(tr, 'tr');
230
-
231
- if (tr) {
232
- // Loop left to find real cell
233
- for (x2 = x; x2 >= 0; x2--) {
234
- cell = grid[y + r][x2].elm;
235
-
236
- if (cell.parentNode == tr) {
237
- // Append clones after
238
- for (c = 1; c <= cols; c++)
239
- dom.insertAfter(cloneCell(cell), cell);
240
-
241
- break;
242
- }
243
- }
244
-
245
- if (x2 == -1) {
246
- // Insert nodes before first cell
247
- for (c = 1; c <= cols; c++)
248
- tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
249
- }
250
- }
251
- }
252
- };
253
-
254
- function split() {
255
- each(grid, function(row, y) {
256
- each(row, function(cell, x) {
257
- var colSpan, rowSpan, newCell, i;
258
-
259
- if (isCellSelected(cell)) {
260
- cell = cell.elm;
261
- colSpan = getSpanVal(cell, 'colspan');
262
- rowSpan = getSpanVal(cell, 'rowspan');
263
-
264
- if (colSpan > 1 || rowSpan > 1) {
265
- setSpanVal(cell, 'rowSpan', 1);
266
- setSpanVal(cell, 'colSpan', 1);
267
-
268
- // Insert cells right
269
- for (i = 0; i < colSpan - 1; i++)
270
- dom.insertAfter(cloneCell(cell), cell);
271
-
272
- fillLeftDown(x, y, rowSpan - 1, colSpan);
273
- }
274
- }
275
- });
276
- });
277
- };
278
-
279
- function merge(cell, cols, rows) {
280
- var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count;
281
-
282
- // Use specified cell and cols/rows
283
- if (cell) {
284
- pos = getPos(cell);
285
- startX = pos.x;
286
- startY = pos.y;
287
- endX = startX + (cols - 1);
288
- endY = startY + (rows - 1);
289
- } else {
290
- startPos = endPos = null;
291
-
292
- // Calculate start/end pos by checking for selected cells in grid works better with context menu
293
- each(grid, function(row, y) {
294
- each(row, function(cell, x) {
295
- if (isCellSelected(cell)) {
296
- if (!startPos) {
297
- startPos = {x: x, y: y};
298
- }
299
-
300
- endPos = {x: x, y: y};
301
- }
302
- });
303
- });
304
-
305
- // Use selection
306
- startX = startPos.x;
307
- startY = startPos.y;
308
- endX = endPos.x;
309
- endY = endPos.y;
310
- }
311
-
312
- // Find start/end cells
313
- startCell = getCell(startX, startY);
314
- endCell = getCell(endX, endY);
315
-
316
- // Check if the cells exists and if they are of the same part for example tbody = tbody
317
- if (startCell && endCell && startCell.part == endCell.part) {
318
- // Split and rebuild grid
319
- split();
320
- buildGrid();
321
-
322
- // Set row/col span to start cell
323
- startCell = getCell(startX, startY).elm;
324
- setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
325
- setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
326
-
327
- // Remove other cells and add it's contents to the start cell
328
- for (y = startY; y <= endY; y++) {
329
- for (x = startX; x <= endX; x++) {
330
- if (!grid[y] || !grid[y][x])
331
- continue;
332
-
333
- cell = grid[y][x].elm;
334
-
335
- if (cell != startCell) {
336
- // Move children to startCell
337
- children = tinymce.grep(cell.childNodes);
338
- each(children, function(node) {
339
- startCell.appendChild(node);
340
- });
341
-
342
- // Remove bogus nodes if there is children in the target cell
343
- if (children.length) {
344
- children = tinymce.grep(startCell.childNodes);
345
- count = 0;
346
- each(children, function(node) {
347
- if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1)
348
- startCell.removeChild(node);
349
- });
350
- }
351
-
352
- // Remove cell
353
- dom.remove(cell);
354
- }
355
- }
356
- }
357
-
358
- // Remove empty rows etc and restore caret location
359
- cleanup();
360
- }
361
- };
362
-
363
- function insertRow(before) {
364
- var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
365
-
366
- // Find first/last row
367
- each(grid, function(row, y) {
368
- each(row, function(cell, x) {
369
- if (isCellSelected(cell)) {
370
- cell = cell.elm;
371
- rowElm = cell.parentNode;
372
- newRow = cloneNode(rowElm, false);
373
- posY = y;
374
-
375
- if (before)
376
- return false;
377
- }
378
- });
379
-
380
- if (before)
381
- return !posY;
382
- });
383
-
384
- for (x = 0; x < grid[0].length; x++) {
385
- // Cell not found could be because of an invalid table structure
386
- if (!grid[posY][x])
387
- continue;
388
-
389
- cell = grid[posY][x].elm;
390
-
391
- if (cell != lastCell) {
392
- if (!before) {
393
- rowSpan = getSpanVal(cell, 'rowspan');
394
- if (rowSpan > 1) {
395
- setSpanVal(cell, 'rowSpan', rowSpan + 1);
396
- continue;
397
- }
398
- } else {
399
- // Check if cell above can be expanded
400
- if (posY > 0 && grid[posY - 1][x]) {
401
- otherCell = grid[posY - 1][x].elm;
402
- rowSpan = getSpanVal(otherCell, 'rowSpan');
403
- if (rowSpan > 1) {
404
- setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
405
- continue;
406
- }
407
- }
408
- }
409
-
410
- // Insert new cell into new row
411
- newCell = cloneCell(cell);
412
- setSpanVal(newCell, 'colSpan', cell.colSpan);
413
-
414
- newRow.appendChild(newCell);
415
-
416
- lastCell = cell;
417
- }
418
- }
419
-
420
- if (newRow.hasChildNodes()) {
421
- if (!before)
422
- dom.insertAfter(newRow, rowElm);
423
- else
424
- rowElm.parentNode.insertBefore(newRow, rowElm);
425
- }
426
- };
427
-
428
- function insertCol(before) {
429
- var posX, lastCell;
430
-
431
- // Find first/last column
432
- each(grid, function(row, y) {
433
- each(row, function(cell, x) {
434
- if (isCellSelected(cell)) {
435
- posX = x;
436
-
437
- if (before)
438
- return false;
439
- }
440
- });
441
-
442
- if (before)
443
- return !posX;
444
- });
445
-
446
- each(grid, function(row, y) {
447
- var cell, rowSpan, colSpan;
448
-
449
- if (!row[posX])
450
- return;
451
-
452
- cell = row[posX].elm;
453
- if (cell != lastCell) {
454
- colSpan = getSpanVal(cell, 'colspan');
455
- rowSpan = getSpanVal(cell, 'rowspan');
456
-
457
- if (colSpan == 1) {
458
- if (!before) {
459
- dom.insertAfter(cloneCell(cell), cell);
460
- fillLeftDown(posX, y, rowSpan - 1, colSpan);
461
- } else {
462
- cell.parentNode.insertBefore(cloneCell(cell), cell);
463
- fillLeftDown(posX, y, rowSpan - 1, colSpan);
464
- }
465
- } else
466
- setSpanVal(cell, 'colSpan', cell.colSpan + 1);
467
-
468
- lastCell = cell;
469
- }
470
- });
471
- };
472
-
473
- function deleteCols() {
474
- var cols = [];
475
-
476
- // Get selected column indexes
477
- each(grid, function(row, y) {
478
- each(row, function(cell, x) {
479
- if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
480
- each(grid, function(row) {
481
- var cell = row[x].elm, colSpan;
482
-
483
- colSpan = getSpanVal(cell, 'colSpan');
484
-
485
- if (colSpan > 1)
486
- setSpanVal(cell, 'colSpan', colSpan - 1);
487
- else
488
- dom.remove(cell);
489
- });
490
-
491
- cols.push(x);
492
- }
493
- });
494
- });
495
-
496
- cleanup();
497
- };
498
-
499
- function deleteRows() {
500
- var rows;
501
-
502
- function deleteRow(tr) {
503
- var nextTr, pos, lastCell;
504
-
505
- nextTr = dom.getNext(tr, 'tr');
506
-
507
- // Move down row spanned cells
508
- each(tr.cells, function(cell) {
509
- var rowSpan = getSpanVal(cell, 'rowSpan');
510
-
511
- if (rowSpan > 1) {
512
- setSpanVal(cell, 'rowSpan', rowSpan - 1);
513
- pos = getPos(cell);
514
- fillLeftDown(pos.x, pos.y, 1, 1);
515
- }
516
- });
517
-
518
- // Delete cells
519
- pos = getPos(tr.cells[0]);
520
- each(grid[pos.y], function(cell) {
521
- var rowSpan;
522
-
523
- cell = cell.elm;
524
-
525
- if (cell != lastCell) {
526
- rowSpan = getSpanVal(cell, 'rowSpan');
527
-
528
- if (rowSpan <= 1)
529
- dom.remove(cell);
530
- else
531
- setSpanVal(cell, 'rowSpan', rowSpan - 1);
532
-
533
- lastCell = cell;
534
- }
535
- });
536
- };
537
-
538
- // Get selected rows and move selection out of scope
539
- rows = getSelectedRows();
540
-
541
- // Delete all selected rows
542
- each(rows.reverse(), function(tr) {
543
- deleteRow(tr);
544
- });
545
-
546
- cleanup();
547
- };
548
-
549
- function cutRows() {
550
- var rows = getSelectedRows();
551
-
552
- dom.remove(rows);
553
- cleanup();
554
-
555
- return rows;
556
- };
557
-
558
- function copyRows() {
559
- var rows = getSelectedRows();
560
-
561
- each(rows, function(row, i) {
562
- rows[i] = cloneNode(row, true);
563
- });
564
-
565
- return rows;
566
- };
567
-
568
- function pasteRows(rows, before) {
569
- var selectedRows = getSelectedRows(),
570
- targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
571
- targetCellCount = targetRow.cells.length;
572
-
573
- // Calc target cell count
574
- each(grid, function(row) {
575
- var match;
576
-
577
- targetCellCount = 0;
578
- each(row, function(cell, x) {
579
- if (cell.real)
580
- targetCellCount += cell.colspan;
581
-
582
- if (cell.elm.parentNode == targetRow)
583
- match = 1;
584
- });
585
-
586
- if (match)
587
- return false;
588
- });
589
-
590
- if (!before)
591
- rows.reverse();
592
-
593
- each(rows, function(row) {
594
- var cellCount = row.cells.length, cell;
595
-
596
- // Remove col/rowspans
597
- for (i = 0; i < cellCount; i++) {
598
- cell = row.cells[i];
599
- setSpanVal(cell, 'colSpan', 1);
600
- setSpanVal(cell, 'rowSpan', 1);
601
- }
602
-
603
- // Needs more cells
604
- for (i = cellCount; i < targetCellCount; i++)
605
- row.appendChild(cloneCell(row.cells[cellCount - 1]));
606
-
607
- // Needs less cells
608
- for (i = targetCellCount; i < cellCount; i++)
609
- dom.remove(row.cells[i]);
610
-
611
- // Add before/after
612
- if (before)
613
- targetRow.parentNode.insertBefore(row, targetRow);
614
- else
615
- dom.insertAfter(row, targetRow);
616
- });
617
-
618
- // Remove current selection
619
- dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
620
- };
621
-
622
- function getPos(target) {
623
- var pos;
624
-
625
- each(grid, function(row, y) {
626
- each(row, function(cell, x) {
627
- if (cell.elm == target) {
628
- pos = {x : x, y : y};
629
- return false;
630
- }
631
- });
632
-
633
- return !pos;
634
- });
635
-
636
- return pos;
637
- };
638
-
639
- function setStartCell(cell) {
640
- startPos = getPos(cell);
641
- };
642
-
643
- function findEndPos() {
644
- var pos, maxX, maxY;
645
-
646
- maxX = maxY = 0;
647
-
648
- each(grid, function(row, y) {
649
- each(row, function(cell, x) {
650
- var colSpan, rowSpan;
651
-
652
- if (isCellSelected(cell)) {
653
- cell = grid[y][x];
654
-
655
- if (x > maxX)
656
- maxX = x;
657
-
658
- if (y > maxY)
659
- maxY = y;
660
-
661
- if (cell.real) {
662
- colSpan = cell.colspan - 1;
663
- rowSpan = cell.rowspan - 1;
664
-
665
- if (colSpan) {
666
- if (x + colSpan > maxX)
667
- maxX = x + colSpan;
668
- }
669
-
670
- if (rowSpan) {
671
- if (y + rowSpan > maxY)
672
- maxY = y + rowSpan;
673
- }
674
- }
675
- }
676
- });
677
- });
678
-
679
- return {x : maxX, y : maxY};
680
- };
681
-
682
- function setEndCell(cell) {
683
- var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
684
-
685
- endPos = getPos(cell);
686
-
687
- if (startPos && endPos) {
688
- // Get start/end positions
689
- startX = Math.min(startPos.x, endPos.x);
690
- startY = Math.min(startPos.y, endPos.y);
691
- endX = Math.max(startPos.x, endPos.x);
692
- endY = Math.max(startPos.y, endPos.y);
693
-
694
- // Expand end positon to include spans
695
- maxX = endX;
696
- maxY = endY;
697
-
698
- // Expand startX
699
- for (y = startY; y <= maxY; y++) {
700
- cell = grid[y][startX];
701
-
702
- if (!cell.real) {
703
- if (startX - (cell.colspan - 1) < startX)
704
- startX -= cell.colspan - 1;
705
- }
706
- }
707
-
708
- // Expand startY
709
- for (x = startX; x <= maxX; x++) {
710
- cell = grid[startY][x];
711
-
712
- if (!cell.real) {
713
- if (startY - (cell.rowspan - 1) < startY)
714
- startY -= cell.rowspan - 1;
715
- }
716
- }
717
-
718
- // Find max X, Y
719
- for (y = startY; y <= endY; y++) {
720
- for (x = startX; x <= endX; x++) {
721
- cell = grid[y][x];
722
-
723
- if (cell.real) {
724
- colSpan = cell.colspan - 1;
725
- rowSpan = cell.rowspan - 1;
726
-
727
- if (colSpan) {
728
- if (x + colSpan > maxX)
729
- maxX = x + colSpan;
730
- }
731
-
732
- if (rowSpan) {
733
- if (y + rowSpan > maxY)
734
- maxY = y + rowSpan;
735
- }
736
- }
737
- }
738
- }
739
-
740
- // Remove current selection
741
- dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
742
-
743
- // Add new selection
744
- for (y = startY; y <= maxY; y++) {
745
- for (x = startX; x <= maxX; x++) {
746
- if (grid[y][x])
747
- dom.addClass(grid[y][x].elm, 'mceSelected');
748
- }
749
- }
750
- }
751
- };
752
-
753
- // Expose to public
754
- tinymce.extend(this, {
755
- deleteTable : deleteTable,
756
- split : split,
757
- merge : merge,
758
- insertRow : insertRow,
759
- insertCol : insertCol,
760
- deleteCols : deleteCols,
761
- deleteRows : deleteRows,
762
- cutRows : cutRows,
763
- copyRows : copyRows,
764
- pasteRows : pasteRows,
765
- getPos : getPos,
766
- setStartCell : setStartCell,
767
- setEndCell : setEndCell
768
- });
769
- };
770
-
771
- tinymce.create('tinymce.plugins.TablePlugin', {
772
- init : function(ed, url) {
773
- var winMan, clipboardRows, hasCellSelection = true; // Might be selected cells on reload
774
-
775
- function createTableGrid(node) {
776
- var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
777
-
778
- if (tblElm)
779
- return new TableGrid(tblElm, ed.dom, selection);
780
- };
781
-
782
- function cleanup() {
783
- // Restore selection possibilities
784
- ed.getBody().style.webkitUserSelect = '';
785
-
786
- if (hasCellSelection) {
787
- ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
788
- hasCellSelection = false;
789
- }
790
- };
791
-
792
- // Register buttons
793
- each([
794
- ['table', 'table.desc', 'mceInsertTable', true],
795
- ['delete_table', 'table.del', 'mceTableDelete'],
796
- ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
797
- ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
798
- ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
799
- ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
800
- ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
801
- ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
802
- ['row_props', 'table.row_desc', 'mceTableRowProps', true],
803
- ['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
804
- ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
805
- ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
806
- ], function(c) {
807
- ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
808
- });
809
-
810
- // Select whole table is a table border is clicked
811
- if (!tinymce.isIE) {
812
- ed.onClick.add(function(ed, e) {
813
- e = e.target;
814
-
815
- if (e.nodeName === 'TABLE') {
816
- ed.selection.select(e);
817
- ed.nodeChanged();
818
- }
819
- });
820
- }
821
-
822
- ed.onPreProcess.add(function(ed, args) {
823
- var nodes, i, node, dom = ed.dom, value;
824
-
825
- nodes = dom.select('table', args.node);
826
- i = nodes.length;
827
- while (i--) {
828
- node = nodes[i];
829
- dom.setAttrib(node, 'data-mce-style', '');
830
-
831
- if ((value = dom.getAttrib(node, 'width'))) {
832
- dom.setStyle(node, 'width', value);
833
- dom.setAttrib(node, 'width', '');
834
- }
835
-
836
- if ((value = dom.getAttrib(node, 'height'))) {
837
- dom.setStyle(node, 'height', value);
838
- dom.setAttrib(node, 'height', '');
839
- }
840
- }
841
- });
842
-
843
- // Handle node change updates
844
- ed.onNodeChange.add(function(ed, cm, n) {
845
- var p;
846
-
847
- n = ed.selection.getStart();
848
- p = ed.dom.getParent(n, 'td,th,caption');
849
- cm.setActive('table', n.nodeName === 'TABLE' || !!p);
850
-
851
- // Disable table tools if we are in caption
852
- if (p && p.nodeName === 'CAPTION')
853
- p = 0;
854
-
855
- cm.setDisabled('delete_table', !p);
856
- cm.setDisabled('delete_col', !p);
857
- cm.setDisabled('delete_table', !p);
858
- cm.setDisabled('delete_row', !p);
859
- cm.setDisabled('col_after', !p);
860
- cm.setDisabled('col_before', !p);
861
- cm.setDisabled('row_after', !p);
862
- cm.setDisabled('row_before', !p);
863
- cm.setDisabled('row_props', !p);
864
- cm.setDisabled('cell_props', !p);
865
- cm.setDisabled('split_cells', !p);
866
- cm.setDisabled('merge_cells', !p);
867
- });
868
-
869
- ed.onInit.add(function(ed) {
870
- var startTable, startCell, dom = ed.dom, tableGrid;
871
-
872
- winMan = ed.windowManager;
873
-
874
- // Add cell selection logic
875
- ed.onMouseDown.add(function(ed, e) {
876
- if (e.button != 2) {
877
- cleanup();
878
-
879
- startCell = dom.getParent(e.target, 'td,th');
880
- startTable = dom.getParent(startCell, 'table');
881
- }
882
- });
883
-
884
- dom.bind(ed.getDoc(), 'mouseover', function(e) {
885
- var sel, table, target = e.target;
886
-
887
- if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
888
- table = dom.getParent(target, 'table');
889
- if (table == startTable) {
890
- if (!tableGrid) {
891
- tableGrid = createTableGrid(table);
892
- tableGrid.setStartCell(startCell);
893
-
894
- ed.getBody().style.webkitUserSelect = 'none';
895
- }
896
-
897
- tableGrid.setEndCell(target);
898
- hasCellSelection = true;
899
- }
900
-
901
- // Remove current selection
902
- sel = ed.selection.getSel();
903
-
904
- try {
905
- if (sel.removeAllRanges)
906
- sel.removeAllRanges();
907
- else
908
- sel.empty();
909
- } catch (ex) {
910
- // IE9 might throw errors here
911
- }
912
-
913
- e.preventDefault();
914
- }
915
- });
916
-
917
- ed.onMouseUp.add(function(ed, e) {
918
- var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
919
-
920
- // Move selection to startCell
921
- if (startCell) {
922
- if (tableGrid)
923
- ed.getBody().style.webkitUserSelect = '';
924
-
925
- function setPoint(node, start) {
926
- var walker = new tinymce.dom.TreeWalker(node, node);
927
-
928
- do {
929
- // Text node
930
- if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
931
- if (start)
932
- rng.setStart(node, 0);
933
- else
934
- rng.setEnd(node, node.nodeValue.length);
935
-
936
- return;
937
- }
938
-
939
- // BR element
940
- if (node.nodeName == 'BR') {
941
- if (start)
942
- rng.setStartBefore(node);
943
- else
944
- rng.setEndBefore(node);
945
-
946
- return;
947
- }
948
- } while (node = (start ? walker.next() : walker.prev()));
949
- }
950
-
951
- // Try to expand text selection as much as we can only Gecko supports cell selection
952
- selectedCells = dom.select('td.mceSelected,th.mceSelected');
953
- if (selectedCells.length > 0) {
954
- rng = dom.createRng();
955
- node = selectedCells[0];
956
- endNode = selectedCells[selectedCells.length - 1];
957
- rng.setStartBefore(node);
958
- rng.setEndAfter(node);
959
-
960
- setPoint(node, 1);
961
- walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
962
-
963
- do {
964
- if (node.nodeName == 'TD' || node.nodeName == 'TH') {
965
- if (!dom.hasClass(node, 'mceSelected'))
966
- break;
967
-
968
- lastNode = node;
969
- }
970
- } while (node = walker.next());
971
-
972
- setPoint(lastNode);
973
-
974
- sel.setRng(rng);
975
- }
976
-
977
- ed.nodeChanged();
978
- startCell = tableGrid = startTable = null;
979
- }
980
- });
981
-
982
- ed.onKeyUp.add(function(ed, e) {
983
- cleanup();
984
- });
985
-
986
- ed.onKeyDown.add(function (ed, e) {
987
- fixTableCellSelection(ed);
988
- });
989
-
990
- ed.onMouseDown.add(function (ed, e) {
991
- if (e.button != 2) {
992
- fixTableCellSelection(ed);
993
- }
994
- });
995
- function tableCellSelected(ed, rng, n, currentCell) {
996
- // The decision of when a table cell is selected is somewhat involved. The fact that this code is
997
- // required is actually a pointer to the root cause of this bug. A cell is selected when the start
998
- // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases)
999
- // or the parent of the table (in the case of the selection containing the last cell of a table).
1000
- var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'),
1001
- tableParent, allOfCellSelected, tableCellSelection;
1002
- if (table)
1003
- tableParent = table.parentNode;
1004
- allOfCellSelected =rng.startContainer.nodeType == TEXT_NODE &&
1005
- rng.startOffset == 0 &&
1006
- rng.endOffset == 0 &&
1007
- currentCell &&
1008
- (n.nodeName=="TR" || n==tableParent);
1009
- tableCellSelection = (n.nodeName=="TD"||n.nodeName=="TH")&& !currentCell;
1010
- return allOfCellSelected || tableCellSelection;
1011
- // return false;
1012
- }
1013
-
1014
- // this nasty hack is here to work around some WebKit selection bugs.
1015
- function fixTableCellSelection(ed) {
1016
- if (!tinymce.isWebKit)
1017
- return;
1018
-
1019
- var rng = ed.selection.getRng();
1020
- var n = ed.selection.getNode();
1021
- var currentCell = ed.dom.getParent(rng.startContainer, 'TD,TH');
1022
-
1023
- if (!tableCellSelected(ed, rng, n, currentCell))
1024
- return;
1025
- if (!currentCell) {
1026
- currentCell=n;
1027
- }
1028
-
1029
- // Get the very last node inside the table cell
1030
- var end = currentCell.lastChild;
1031
- while (end.lastChild)
1032
- end = end.lastChild;
1033
-
1034
- // Select the entire table cell. Nothing outside of the table cell should be selected.
1035
- rng.setEnd(end, end.nodeValue.length);
1036
- ed.selection.setRng(rng);
1037
- }
1038
- ed.plugins.table.fixTableCellSelection=fixTableCellSelection;
1039
-
1040
- // Add context menu
1041
- if (ed && ed.plugins.contextmenu) {
1042
- ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
1043
- var sm, se = ed.selection, el = se.getNode() || ed.getBody();
1044
-
1045
- if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
1046
- m.removeAll();
1047
-
1048
- if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
1049
- m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
1050
- m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
1051
- m.addSeparator();
1052
- }
1053
-
1054
- if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
1055
- m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
1056
- m.addSeparator();
1057
- }
1058
-
1059
- m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
1060
- m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
1061
- m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
1062
- m.addSeparator();
1063
-
1064
- // Cell menu
1065
- sm = m.addMenu({title : 'table.cell'});
1066
- sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
1067
- sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
1068
- sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
1069
-
1070
- // Row menu
1071
- sm = m.addMenu({title : 'table.row'});
1072
- sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
1073
- sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
1074
- sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
1075
- sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
1076
- sm.addSeparator();
1077
- sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
1078
- sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
1079
- sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
1080
- sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
1081
-
1082
- // Column menu
1083
- sm = m.addMenu({title : 'table.col'});
1084
- sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
1085
- sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
1086
- sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
1087
- } else
1088
- m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
1089
- });
1090
- }
1091
-
1092
- // Fix to allow navigating up and down in a table in WebKit browsers.
1093
- if (tinymce.isWebKit) {
1094
- function moveSelection(ed, e) {
1095
- var VK = tinymce.VK;
1096
- var key = e.keyCode;
1097
-
1098
- function handle(upBool, sourceNode, event) {
1099
- var siblingDirection = upBool ? 'previousSibling' : 'nextSibling';
1100
- var currentRow = ed.dom.getParent(sourceNode, 'tr');
1101
- var siblingRow = currentRow[siblingDirection];
1102
-
1103
- if (siblingRow) {
1104
- moveCursorToRow(ed, sourceNode, siblingRow, upBool);
1105
- tinymce.dom.Event.cancel(event);
1106
- return true;
1107
- } else {
1108
- var tableNode = ed.dom.getParent(currentRow, 'table');
1109
- var middleNode = currentRow.parentNode;
1110
- var parentNodeName = middleNode.nodeName.toLowerCase();
1111
- if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) {
1112
- var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody');
1113
- if (targetParent !== null) {
1114
- return moveToRowInTarget(upBool, targetParent, sourceNode, event);
1115
- }
1116
- }
1117
- return escapeTable(upBool, currentRow, siblingDirection, tableNode, event);
1118
- }
1119
- }
1120
-
1121
- function getTargetParent(upBool, topNode, secondNode, nodeName) {
1122
- var tbodies = ed.dom.select('>' + nodeName, topNode);
1123
- var position = tbodies.indexOf(secondNode);
1124
- if (upBool && position === 0 || !upBool && position === tbodies.length - 1) {
1125
- return getFirstHeadOrFoot(upBool, topNode);
1126
- } else if (position === -1) {
1127
- var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1;
1128
- return tbodies[topOrBottom];
1129
- } else {
1130
- return tbodies[position + (upBool ? -1 : 1)];
1131
- }
1132
- }
1133
-
1134
- function getFirstHeadOrFoot(upBool, parent) {
1135
- var tagName = upBool ? 'thead' : 'tfoot';
1136
- var headOrFoot = ed.dom.select('>' + tagName, parent);
1137
- return headOrFoot.length !== 0 ? headOrFoot[0] : null;
1138
- }
1139
-
1140
- function moveToRowInTarget(upBool, targetParent, sourceNode, event) {
1141
- var targetRow = getChildForDirection(targetParent, upBool);
1142
- targetRow && moveCursorToRow(ed, sourceNode, targetRow, upBool);
1143
- tinymce.dom.Event.cancel(event);
1144
- return true;
1145
- }
1146
-
1147
- function escapeTable(upBool, currentRow, siblingDirection, table, event) {
1148
- var tableSibling = table[siblingDirection];
1149
- if (tableSibling) {
1150
- moveCursorToStartOfElement(tableSibling);
1151
- return true;
1152
- } else {
1153
- var parentCell = ed.dom.getParent(table, 'td,th');
1154
- if (parentCell) {
1155
- return handle(upBool, parentCell, event);
1156
- } else {
1157
- var backUpSibling = getChildForDirection(currentRow, !upBool);
1158
- moveCursorToStartOfElement(backUpSibling);
1159
- return tinymce.dom.Event.cancel(event);
1160
- }
1161
- }
1162
- }
1163
-
1164
- function getChildForDirection(parent, up) {
1165
- var child = parent && parent[up ? 'lastChild' : 'firstChild'];
1166
- // BR is not a valid table child to return in this case we return the table cell
1167
- return child && child.nodeName === 'BR' ? ed.dom.getParent(child, 'td,th') : child;
1168
- }
1169
-
1170
- function moveCursorToStartOfElement(n) {
1171
- ed.selection.setCursorLocation(n, 0);
1172
- }
1173
-
1174
- function isVerticalMovement() {
1175
- return key == VK.UP || key == VK.DOWN;
1176
- }
1177
-
1178
- function isInTable(ed) {
1179
- var node = ed.selection.getNode();
1180
- var currentRow = ed.dom.getParent(node, 'tr');
1181
- return currentRow !== null;
1182
- }
1183
-
1184
- function columnIndex(column) {
1185
- var colIndex = 0;
1186
- var c = column;
1187
- while (c.previousSibling) {
1188
- c = c.previousSibling;
1189
- colIndex = colIndex + getSpanVal(c, "colspan");
1190
- }
1191
- return colIndex;
1192
- }
1193
-
1194
- function findColumn(rowElement, columnIndex) {
1195
- var c = 0;
1196
- var r = 0;
1197
- each(rowElement.children, function(cell, i) {
1198
- c = c + getSpanVal(cell, "colspan");
1199
- r = i;
1200
- if (c > columnIndex)
1201
- return false;
1202
- });
1203
- return r;
1204
- }
1205
-
1206
- function moveCursorToRow(ed, node, row, upBool) {
1207
- var srcColumnIndex = columnIndex(ed.dom.getParent(node, 'td,th'));
1208
- var tgtColumnIndex = findColumn(row, srcColumnIndex);
1209
- var tgtNode = row.childNodes[tgtColumnIndex];
1210
- var rowCellTarget = getChildForDirection(tgtNode, upBool);
1211
- moveCursorToStartOfElement(rowCellTarget || tgtNode);
1212
- }
1213
-
1214
- function shouldFixCaret(preBrowserNode) {
1215
- var newNode = ed.selection.getNode();
1216
- var newParent = ed.dom.getParent(newNode, 'td,th');
1217
- var oldParent = ed.dom.getParent(preBrowserNode, 'td,th');
1218
- return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent)
1219
- }
1220
-
1221
- function checkSameParentTable(nodeOne, NodeTwo) {
1222
- return ed.dom.getParent(nodeOne, 'TABLE') === ed.dom.getParent(NodeTwo, 'TABLE');
1223
- }
1224
-
1225
- if (isVerticalMovement() && isInTable(ed)) {
1226
- var preBrowserNode = ed.selection.getNode();
1227
- setTimeout(function() {
1228
- if (shouldFixCaret(preBrowserNode)) {
1229
- handle(!e.shiftKey && key === VK.UP, preBrowserNode, e);
1230
- }
1231
- }, 0);
1232
- }
1233
- }
1234
-
1235
- ed.onKeyDown.add(moveSelection);
1236
- }
1237
-
1238
- // Fixes an issue on Gecko where it's impossible to place the caret behind a table
1239
- // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
1240
- function fixTableCaretPos() {
1241
- var last;
1242
-
1243
- // Skip empty text nodes form the end
1244
- for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
1245
-
1246
- if (last && last.nodeName == 'TABLE') {
1247
- if (ed.settings.forced_root_block)
1248
- ed.dom.add(ed.getBody(), ed.settings.forced_root_block, null, tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />');
1249
- else
1250
- ed.dom.add(ed.getBody(), 'br', {'data-mce-bogus': '1'});
1251
- }
1252
- };
1253
-
1254
- // Fixes an bug where it's impossible to place the caret before a table in Gecko
1255
- // this fix solves it by detecting when the caret is at the beginning of such a table
1256
- // and then manually moves the caret infront of the table
1257
- if (tinymce.isGecko) {
1258
- ed.onKeyDown.add(function(ed, e) {
1259
- var rng, table, dom = ed.dom;
1260
-
1261
- // On gecko it's not possible to place the caret before a table
1262
- if (e.keyCode == 37 || e.keyCode == 38) {
1263
- rng = ed.selection.getRng();
1264
- table = dom.getParent(rng.startContainer, 'table');
1265
-
1266
- if (table && ed.getBody().firstChild == table) {
1267
- if (isAtStart(rng, table)) {
1268
- rng = dom.createRng();
1269
-
1270
- rng.setStartBefore(table);
1271
- rng.setEndBefore(table);
1272
-
1273
- ed.selection.setRng(rng);
1274
-
1275
- e.preventDefault();
1276
- }
1277
- }
1278
- }
1279
- });
1280
- }
1281
-
1282
- ed.onKeyUp.add(fixTableCaretPos);
1283
- ed.onSetContent.add(fixTableCaretPos);
1284
- ed.onVisualAid.add(fixTableCaretPos);
1285
-
1286
- ed.onPreProcess.add(function(ed, o) {
1287
- var last = o.node.lastChild;
1288
-
1289
- if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 && (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) && last.previousSibling && last.previousSibling.nodeName == "TABLE") {
1290
- ed.dom.remove(last);
1291
- }
1292
- });
1293
-
1294
-
1295
- /**
1296
- * Fixes bug in Gecko where shift-enter in table cell does not place caret on new line
1297
- */
1298
- if (tinymce.isGecko) {
1299
- ed.onKeyDown.add(function(ed, e) {
1300
- if (e.keyCode === tinymce.VK.ENTER && e.shiftKey) {
1301
- var node = ed.selection.getRng().startContainer;
1302
- var tableCell = dom.getParent(node, 'td,th');
1303
- if (tableCell) {
1304
- var zeroSizedNbsp = ed.getDoc().createTextNode("\uFEFF");
1305
- dom.insertAfter(zeroSizedNbsp, node);
1306
- }
1307
- }
1308
- });
1309
- }
1310
-
1311
-
1312
- fixTableCaretPos();
1313
- ed.startContent = ed.getContent({format : 'raw'});
1314
- });
1315
-
1316
- // Register action commands
1317
- each({
1318
- mceTableSplitCells : function(grid) {
1319
- grid.split();
1320
- },
1321
-
1322
- mceTableMergeCells : function(grid) {
1323
- var rowSpan, colSpan, cell;
1324
-
1325
- cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
1326
- if (cell) {
1327
- rowSpan = cell.rowSpan;
1328
- colSpan = cell.colSpan;
1329
- }
1330
-
1331
- if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
1332
- winMan.open({
1333
- url : url + '/merge_cells.htm',
1334
- width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
1335
- height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
1336
- inline : 1
1337
- }, {
1338
- rows : rowSpan,
1339
- cols : colSpan,
1340
- onaction : function(data) {
1341
- grid.merge(cell, data.cols, data.rows);
1342
- },
1343
- plugin_url : url
1344
- });
1345
- } else
1346
- grid.merge();
1347
- },
1348
-
1349
- mceTableInsertRowBefore : function(grid) {
1350
- grid.insertRow(true);
1351
- },
1352
-
1353
- mceTableInsertRowAfter : function(grid) {
1354
- grid.insertRow();
1355
- },
1356
-
1357
- mceTableInsertColBefore : function(grid) {
1358
- grid.insertCol(true);
1359
- },
1360
-
1361
- mceTableInsertColAfter : function(grid) {
1362
- grid.insertCol();
1363
- },
1364
-
1365
- mceTableDeleteCol : function(grid) {
1366
- grid.deleteCols();
1367
- },
1368
-
1369
- mceTableDeleteRow : function(grid) {
1370
- grid.deleteRows();
1371
- },
1372
-
1373
- mceTableCutRow : function(grid) {
1374
- clipboardRows = grid.cutRows();
1375
- },
1376
-
1377
- mceTableCopyRow : function(grid) {
1378
- clipboardRows = grid.copyRows();
1379
- },
1380
-
1381
- mceTablePasteRowBefore : function(grid) {
1382
- grid.pasteRows(clipboardRows, true);
1383
- },
1384
-
1385
- mceTablePasteRowAfter : function(grid) {
1386
- grid.pasteRows(clipboardRows);
1387
- },
1388
-
1389
- mceTableDelete : function(grid) {
1390
- grid.deleteTable();
1391
- }
1392
- }, function(func, name) {
1393
- ed.addCommand(name, function() {
1394
- var grid = createTableGrid();
1395
-
1396
- if (grid) {
1397
- func(grid);
1398
- ed.execCommand('mceRepaint');
1399
- cleanup();
1400
- }
1401
- });
1402
- });
1403
-
1404
- // Register dialog commands
1405
- each({
1406
- mceInsertTable : function(val) {
1407
- winMan.open({
1408
- url : url + '/table.htm',
1409
- width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
1410
- height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
1411
- inline : 1
1412
- }, {
1413
- plugin_url : url,
1414
- action : val ? val.action : 0
1415
- });
1416
- },
1417
-
1418
- mceTableRowProps : function() {
1419
- winMan.open({
1420
- url : url + '/row.htm',
1421
- width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
1422
- height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
1423
- inline : 1
1424
- }, {
1425
- plugin_url : url
1426
- });
1427
- },
1428
-
1429
- mceTableCellProps : function() {
1430
- winMan.open({
1431
- url : url + '/cell.htm',
1432
- width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
1433
- height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
1434
- inline : 1
1435
- }, {
1436
- plugin_url : url
1437
- });
1438
- }
1439
- }, function(func, name) {
1440
- ed.addCommand(name, function(ui, val) {
1441
- func(val);
1442
- });
1443
- });
1444
- }
1445
- });
1446
-
1447
- // Register plugin
1448
- tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
1449
- })(tinymce);
1
+ /**
2
+ * editor_plugin_src.js
3
+ *
4
+ * Copyright 2009, Moxiecode Systems AB
5
+ * Released under LGPL License.
6
+ *
7
+ * License: http://tinymce.moxiecode.com/license
8
+ * Contributing: http://tinymce.moxiecode.com/contributing
9
+ */
10
+
11
+ (function(tinymce) {
12
+ var each = tinymce.each;
13
+
14
+ // Checks if the selection/caret is at the start of the specified block element
15
+ function isAtStart(rng, par) {
16
+ var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
17
+
18
+ rng2.setStartBefore(par);
19
+ rng2.setEnd(rng.endContainer, rng.endOffset);
20
+
21
+ elm = doc.createElement('body');
22
+ elm.appendChild(rng2.cloneContents());
23
+
24
+ // Check for text characters of other elements that should be treated as content
25
+ return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
26
+ };
27
+
28
+ function getSpanVal(td, name) {
29
+ return parseInt(td.getAttribute(name) || 1);
30
+ }
31
+
32
+ /**
33
+ * Table Grid class.
34
+ */
35
+ function TableGrid(table, dom, selection) {
36
+ var grid, startPos, endPos, selectedCell;
37
+
38
+ buildGrid();
39
+ selectedCell = dom.getParent(selection.getStart(), 'th,td');
40
+ if (selectedCell) {
41
+ startPos = getPos(selectedCell);
42
+ endPos = findEndPos();
43
+ selectedCell = getCell(startPos.x, startPos.y);
44
+ }
45
+
46
+ function cloneNode(node, children) {
47
+ node = node.cloneNode(children);
48
+ node.removeAttribute('id');
49
+
50
+ return node;
51
+ }
52
+
53
+ function buildGrid() {
54
+ var startY = 0;
55
+
56
+ grid = [];
57
+
58
+ each(['thead', 'tbody', 'tfoot'], function(part) {
59
+ var rows = dom.select('> ' + part + ' tr', table);
60
+
61
+ each(rows, function(tr, y) {
62
+ y += startY;
63
+
64
+ each(dom.select('> td, > th', tr), function(td, x) {
65
+ var x2, y2, rowspan, colspan;
66
+
67
+ // Skip over existing cells produced by rowspan
68
+ if (grid[y]) {
69
+ while (grid[y][x])
70
+ x++;
71
+ }
72
+
73
+ // Get col/rowspan from cell
74
+ rowspan = getSpanVal(td, 'rowspan');
75
+ colspan = getSpanVal(td, 'colspan');
76
+
77
+ // Fill out rowspan/colspan right and down
78
+ for (y2 = y; y2 < y + rowspan; y2++) {
79
+ if (!grid[y2])
80
+ grid[y2] = [];
81
+
82
+ for (x2 = x; x2 < x + colspan; x2++) {
83
+ grid[y2][x2] = {
84
+ part : part,
85
+ real : y2 == y && x2 == x,
86
+ elm : td,
87
+ rowspan : rowspan,
88
+ colspan : colspan
89
+ };
90
+ }
91
+ }
92
+ });
93
+ });
94
+
95
+ startY += rows.length;
96
+ });
97
+ };
98
+
99
+ function getCell(x, y) {
100
+ var row;
101
+
102
+ row = grid[y];
103
+ if (row)
104
+ return row[x];
105
+ };
106
+
107
+ function setSpanVal(td, name, val) {
108
+ if (td) {
109
+ val = parseInt(val);
110
+
111
+ if (val === 1)
112
+ td.removeAttribute(name, 1);
113
+ else
114
+ td.setAttribute(name, val, 1);
115
+ }
116
+ }
117
+
118
+ function isCellSelected(cell) {
119
+ return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell);
120
+ };
121
+
122
+ function getSelectedRows() {
123
+ var rows = [];
124
+
125
+ each(table.rows, function(row) {
126
+ each(row.cells, function(cell) {
127
+ if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
128
+ rows.push(row);
129
+ return false;
130
+ }
131
+ });
132
+ });
133
+
134
+ return rows;
135
+ };
136
+
137
+ function deleteTable() {
138
+ var rng = dom.createRng();
139
+
140
+ rng.setStartAfter(table);
141
+ rng.setEndAfter(table);
142
+
143
+ selection.setRng(rng);
144
+
145
+ dom.remove(table);
146
+ };
147
+
148
+ function cloneCell(cell) {
149
+ var formatNode;
150
+
151
+ // Clone formats
152
+ tinymce.walk(cell, function(node) {
153
+ var curNode;
154
+
155
+ if (node.nodeType == 3) {
156
+ each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
157
+ node = cloneNode(node, false);
158
+
159
+ if (!formatNode)
160
+ formatNode = curNode = node;
161
+ else if (curNode)
162
+ curNode.appendChild(node);
163
+
164
+ curNode = node;
165
+ });
166
+
167
+ // Add something to the inner node
168
+ if (curNode)
169
+ curNode.innerHTML = tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />';
170
+
171
+ return false;
172
+ }
173
+ }, 'childNodes');
174
+
175
+ cell = cloneNode(cell, false);
176
+ setSpanVal(cell, 'rowSpan', 1);
177
+ setSpanVal(cell, 'colSpan', 1);
178
+
179
+ if (formatNode) {
180
+ cell.appendChild(formatNode);
181
+ } else {
182
+ if (!tinymce.isIE)
183
+ cell.innerHTML = '<br data-mce-bogus="1" />';
184
+ }
185
+
186
+ return cell;
187
+ };
188
+
189
+ function cleanup() {
190
+ var rng = dom.createRng();
191
+
192
+ // Empty rows
193
+ each(dom.select('tr', table), function(tr) {
194
+ if (tr.cells.length == 0)
195
+ dom.remove(tr);
196
+ });
197
+
198
+ // Empty table
199
+ if (dom.select('tr', table).length == 0) {
200
+ rng.setStartAfter(table);
201
+ rng.setEndAfter(table);
202
+ selection.setRng(rng);
203
+ dom.remove(table);
204
+ return;
205
+ }
206
+
207
+ // Empty header/body/footer
208
+ each(dom.select('thead,tbody,tfoot', table), function(part) {
209
+ if (part.rows.length == 0)
210
+ dom.remove(part);
211
+ });
212
+
213
+ // Restore selection to start position if it still exists
214
+ buildGrid();
215
+
216
+ // Restore the selection to the closest table position
217
+ row = grid[Math.min(grid.length - 1, startPos.y)];
218
+ if (row) {
219
+ selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
220
+ selection.collapse(true);
221
+ }
222
+ };
223
+
224
+ function fillLeftDown(x, y, rows, cols) {
225
+ var tr, x2, r, c, cell;
226
+
227
+ tr = grid[y][x].elm.parentNode;
228
+ for (r = 1; r <= rows; r++) {
229
+ tr = dom.getNext(tr, 'tr');
230
+
231
+ if (tr) {
232
+ // Loop left to find real cell
233
+ for (x2 = x; x2 >= 0; x2--) {
234
+ cell = grid[y + r][x2].elm;
235
+
236
+ if (cell.parentNode == tr) {
237
+ // Append clones after
238
+ for (c = 1; c <= cols; c++)
239
+ dom.insertAfter(cloneCell(cell), cell);
240
+
241
+ break;
242
+ }
243
+ }
244
+
245
+ if (x2 == -1) {
246
+ // Insert nodes before first cell
247
+ for (c = 1; c <= cols; c++)
248
+ tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
249
+ }
250
+ }
251
+ }
252
+ };
253
+
254
+ function split() {
255
+ each(grid, function(row, y) {
256
+ each(row, function(cell, x) {
257
+ var colSpan, rowSpan, newCell, i;
258
+
259
+ if (isCellSelected(cell)) {
260
+ cell = cell.elm;
261
+ colSpan = getSpanVal(cell, 'colspan');
262
+ rowSpan = getSpanVal(cell, 'rowspan');
263
+
264
+ if (colSpan > 1 || rowSpan > 1) {
265
+ setSpanVal(cell, 'rowSpan', 1);
266
+ setSpanVal(cell, 'colSpan', 1);
267
+
268
+ // Insert cells right
269
+ for (i = 0; i < colSpan - 1; i++)
270
+ dom.insertAfter(cloneCell(cell), cell);
271
+
272
+ fillLeftDown(x, y, rowSpan - 1, colSpan);
273
+ }
274
+ }
275
+ });
276
+ });
277
+ };
278
+
279
+ function merge(cell, cols, rows) {
280
+ var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count;
281
+
282
+ // Use specified cell and cols/rows
283
+ if (cell) {
284
+ pos = getPos(cell);
285
+ startX = pos.x;
286
+ startY = pos.y;
287
+ endX = startX + (cols - 1);
288
+ endY = startY + (rows - 1);
289
+ } else {
290
+ startPos = endPos = null;
291
+
292
+ // Calculate start/end pos by checking for selected cells in grid works better with context menu
293
+ each(grid, function(row, y) {
294
+ each(row, function(cell, x) {
295
+ if (isCellSelected(cell)) {
296
+ if (!startPos) {
297
+ startPos = {x: x, y: y};
298
+ }
299
+
300
+ endPos = {x: x, y: y};
301
+ }
302
+ });
303
+ });
304
+
305
+ // Use selection
306
+ startX = startPos.x;
307
+ startY = startPos.y;
308
+ endX = endPos.x;
309
+ endY = endPos.y;
310
+ }
311
+
312
+ // Find start/end cells
313
+ startCell = getCell(startX, startY);
314
+ endCell = getCell(endX, endY);
315
+
316
+ // Check if the cells exists and if they are of the same part for example tbody = tbody
317
+ if (startCell && endCell && startCell.part == endCell.part) {
318
+ // Split and rebuild grid
319
+ split();
320
+ buildGrid();
321
+
322
+ // Set row/col span to start cell
323
+ startCell = getCell(startX, startY).elm;
324
+ setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
325
+ setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
326
+
327
+ // Remove other cells and add it's contents to the start cell
328
+ for (y = startY; y <= endY; y++) {
329
+ for (x = startX; x <= endX; x++) {
330
+ if (!grid[y] || !grid[y][x])
331
+ continue;
332
+
333
+ cell = grid[y][x].elm;
334
+
335
+ if (cell != startCell) {
336
+ // Move children to startCell
337
+ children = tinymce.grep(cell.childNodes);
338
+ each(children, function(node) {
339
+ startCell.appendChild(node);
340
+ });
341
+
342
+ // Remove bogus nodes if there is children in the target cell
343
+ if (children.length) {
344
+ children = tinymce.grep(startCell.childNodes);
345
+ count = 0;
346
+ each(children, function(node) {
347
+ if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1)
348
+ startCell.removeChild(node);
349
+ });
350
+ }
351
+
352
+ // Remove cell
353
+ dom.remove(cell);
354
+ }
355
+ }
356
+ }
357
+
358
+ // Remove empty rows etc and restore caret location
359
+ cleanup();
360
+ }
361
+ };
362
+
363
+ function insertRow(before) {
364
+ var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
365
+
366
+ // Find first/last row
367
+ each(grid, function(row, y) {
368
+ each(row, function(cell, x) {
369
+ if (isCellSelected(cell)) {
370
+ cell = cell.elm;
371
+ rowElm = cell.parentNode;
372
+ newRow = cloneNode(rowElm, false);
373
+ posY = y;
374
+
375
+ if (before)
376
+ return false;
377
+ }
378
+ });
379
+
380
+ if (before)
381
+ return !posY;
382
+ });
383
+
384
+ for (x = 0; x < grid[0].length; x++) {
385
+ // Cell not found could be because of an invalid table structure
386
+ if (!grid[posY][x])
387
+ continue;
388
+
389
+ cell = grid[posY][x].elm;
390
+
391
+ if (cell != lastCell) {
392
+ if (!before) {
393
+ rowSpan = getSpanVal(cell, 'rowspan');
394
+ if (rowSpan > 1) {
395
+ setSpanVal(cell, 'rowSpan', rowSpan + 1);
396
+ continue;
397
+ }
398
+ } else {
399
+ // Check if cell above can be expanded
400
+ if (posY > 0 && grid[posY - 1][x]) {
401
+ otherCell = grid[posY - 1][x].elm;
402
+ rowSpan = getSpanVal(otherCell, 'rowSpan');
403
+ if (rowSpan > 1) {
404
+ setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
405
+ continue;
406
+ }
407
+ }
408
+ }
409
+
410
+ // Insert new cell into new row
411
+ newCell = cloneCell(cell);
412
+ setSpanVal(newCell, 'colSpan', cell.colSpan);
413
+
414
+ newRow.appendChild(newCell);
415
+
416
+ lastCell = cell;
417
+ }
418
+ }
419
+
420
+ if (newRow.hasChildNodes()) {
421
+ if (!before)
422
+ dom.insertAfter(newRow, rowElm);
423
+ else
424
+ rowElm.parentNode.insertBefore(newRow, rowElm);
425
+ }
426
+ };
427
+
428
+ function insertCol(before) {
429
+ var posX, lastCell;
430
+
431
+ // Find first/last column
432
+ each(grid, function(row, y) {
433
+ each(row, function(cell, x) {
434
+ if (isCellSelected(cell)) {
435
+ posX = x;
436
+
437
+ if (before)
438
+ return false;
439
+ }
440
+ });
441
+
442
+ if (before)
443
+ return !posX;
444
+ });
445
+
446
+ each(grid, function(row, y) {
447
+ var cell, rowSpan, colSpan;
448
+
449
+ if (!row[posX])
450
+ return;
451
+
452
+ cell = row[posX].elm;
453
+ if (cell != lastCell) {
454
+ colSpan = getSpanVal(cell, 'colspan');
455
+ rowSpan = getSpanVal(cell, 'rowspan');
456
+
457
+ if (colSpan == 1) {
458
+ if (!before) {
459
+ dom.insertAfter(cloneCell(cell), cell);
460
+ fillLeftDown(posX, y, rowSpan - 1, colSpan);
461
+ } else {
462
+ cell.parentNode.insertBefore(cloneCell(cell), cell);
463
+ fillLeftDown(posX, y, rowSpan - 1, colSpan);
464
+ }
465
+ } else
466
+ setSpanVal(cell, 'colSpan', cell.colSpan + 1);
467
+
468
+ lastCell = cell;
469
+ }
470
+ });
471
+ };
472
+
473
+ function deleteCols() {
474
+ var cols = [];
475
+
476
+ // Get selected column indexes
477
+ each(grid, function(row, y) {
478
+ each(row, function(cell, x) {
479
+ if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
480
+ each(grid, function(row) {
481
+ var cell = row[x].elm, colSpan;
482
+
483
+ colSpan = getSpanVal(cell, 'colSpan');
484
+
485
+ if (colSpan > 1)
486
+ setSpanVal(cell, 'colSpan', colSpan - 1);
487
+ else
488
+ dom.remove(cell);
489
+ });
490
+
491
+ cols.push(x);
492
+ }
493
+ });
494
+ });
495
+
496
+ cleanup();
497
+ };
498
+
499
+ function deleteRows() {
500
+ var rows;
501
+
502
+ function deleteRow(tr) {
503
+ var nextTr, pos, lastCell;
504
+
505
+ nextTr = dom.getNext(tr, 'tr');
506
+
507
+ // Move down row spanned cells
508
+ each(tr.cells, function(cell) {
509
+ var rowSpan = getSpanVal(cell, 'rowSpan');
510
+
511
+ if (rowSpan > 1) {
512
+ setSpanVal(cell, 'rowSpan', rowSpan - 1);
513
+ pos = getPos(cell);
514
+ fillLeftDown(pos.x, pos.y, 1, 1);
515
+ }
516
+ });
517
+
518
+ // Delete cells
519
+ pos = getPos(tr.cells[0]);
520
+ each(grid[pos.y], function(cell) {
521
+ var rowSpan;
522
+
523
+ cell = cell.elm;
524
+
525
+ if (cell != lastCell) {
526
+ rowSpan = getSpanVal(cell, 'rowSpan');
527
+
528
+ if (rowSpan <= 1)
529
+ dom.remove(cell);
530
+ else
531
+ setSpanVal(cell, 'rowSpan', rowSpan - 1);
532
+
533
+ lastCell = cell;
534
+ }
535
+ });
536
+ };
537
+
538
+ // Get selected rows and move selection out of scope
539
+ rows = getSelectedRows();
540
+
541
+ // Delete all selected rows
542
+ each(rows.reverse(), function(tr) {
543
+ deleteRow(tr);
544
+ });
545
+
546
+ cleanup();
547
+ };
548
+
549
+ function cutRows() {
550
+ var rows = getSelectedRows();
551
+
552
+ dom.remove(rows);
553
+ cleanup();
554
+
555
+ return rows;
556
+ };
557
+
558
+ function copyRows() {
559
+ var rows = getSelectedRows();
560
+
561
+ each(rows, function(row, i) {
562
+ rows[i] = cloneNode(row, true);
563
+ });
564
+
565
+ return rows;
566
+ };
567
+
568
+ function pasteRows(rows, before) {
569
+ var selectedRows = getSelectedRows(),
570
+ targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
571
+ targetCellCount = targetRow.cells.length;
572
+
573
+ // Calc target cell count
574
+ each(grid, function(row) {
575
+ var match;
576
+
577
+ targetCellCount = 0;
578
+ each(row, function(cell, x) {
579
+ if (cell.real)
580
+ targetCellCount += cell.colspan;
581
+
582
+ if (cell.elm.parentNode == targetRow)
583
+ match = 1;
584
+ });
585
+
586
+ if (match)
587
+ return false;
588
+ });
589
+
590
+ if (!before)
591
+ rows.reverse();
592
+
593
+ each(rows, function(row) {
594
+ var cellCount = row.cells.length, cell;
595
+
596
+ // Remove col/rowspans
597
+ for (i = 0; i < cellCount; i++) {
598
+ cell = row.cells[i];
599
+ setSpanVal(cell, 'colSpan', 1);
600
+ setSpanVal(cell, 'rowSpan', 1);
601
+ }
602
+
603
+ // Needs more cells
604
+ for (i = cellCount; i < targetCellCount; i++)
605
+ row.appendChild(cloneCell(row.cells[cellCount - 1]));
606
+
607
+ // Needs less cells
608
+ for (i = targetCellCount; i < cellCount; i++)
609
+ dom.remove(row.cells[i]);
610
+
611
+ // Add before/after
612
+ if (before)
613
+ targetRow.parentNode.insertBefore(row, targetRow);
614
+ else
615
+ dom.insertAfter(row, targetRow);
616
+ });
617
+
618
+ // Remove current selection
619
+ dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
620
+ };
621
+
622
+ function getPos(target) {
623
+ var pos;
624
+
625
+ each(grid, function(row, y) {
626
+ each(row, function(cell, x) {
627
+ if (cell.elm == target) {
628
+ pos = {x : x, y : y};
629
+ return false;
630
+ }
631
+ });
632
+
633
+ return !pos;
634
+ });
635
+
636
+ return pos;
637
+ };
638
+
639
+ function setStartCell(cell) {
640
+ startPos = getPos(cell);
641
+ };
642
+
643
+ function findEndPos() {
644
+ var pos, maxX, maxY;
645
+
646
+ maxX = maxY = 0;
647
+
648
+ each(grid, function(row, y) {
649
+ each(row, function(cell, x) {
650
+ var colSpan, rowSpan;
651
+
652
+ if (isCellSelected(cell)) {
653
+ cell = grid[y][x];
654
+
655
+ if (x > maxX)
656
+ maxX = x;
657
+
658
+ if (y > maxY)
659
+ maxY = y;
660
+
661
+ if (cell.real) {
662
+ colSpan = cell.colspan - 1;
663
+ rowSpan = cell.rowspan - 1;
664
+
665
+ if (colSpan) {
666
+ if (x + colSpan > maxX)
667
+ maxX = x + colSpan;
668
+ }
669
+
670
+ if (rowSpan) {
671
+ if (y + rowSpan > maxY)
672
+ maxY = y + rowSpan;
673
+ }
674
+ }
675
+ }
676
+ });
677
+ });
678
+
679
+ return {x : maxX, y : maxY};
680
+ };
681
+
682
+ function setEndCell(cell) {
683
+ var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
684
+
685
+ endPos = getPos(cell);
686
+
687
+ if (startPos && endPos) {
688
+ // Get start/end positions
689
+ startX = Math.min(startPos.x, endPos.x);
690
+ startY = Math.min(startPos.y, endPos.y);
691
+ endX = Math.max(startPos.x, endPos.x);
692
+ endY = Math.max(startPos.y, endPos.y);
693
+
694
+ // Expand end positon to include spans
695
+ maxX = endX;
696
+ maxY = endY;
697
+
698
+ // Expand startX
699
+ for (y = startY; y <= maxY; y++) {
700
+ cell = grid[y][startX];
701
+
702
+ if (!cell.real) {
703
+ if (startX - (cell.colspan - 1) < startX)
704
+ startX -= cell.colspan - 1;
705
+ }
706
+ }
707
+
708
+ // Expand startY
709
+ for (x = startX; x <= maxX; x++) {
710
+ cell = grid[startY][x];
711
+
712
+ if (!cell.real) {
713
+ if (startY - (cell.rowspan - 1) < startY)
714
+ startY -= cell.rowspan - 1;
715
+ }
716
+ }
717
+
718
+ // Find max X, Y
719
+ for (y = startY; y <= endY; y++) {
720
+ for (x = startX; x <= endX; x++) {
721
+ cell = grid[y][x];
722
+
723
+ if (cell.real) {
724
+ colSpan = cell.colspan - 1;
725
+ rowSpan = cell.rowspan - 1;
726
+
727
+ if (colSpan) {
728
+ if (x + colSpan > maxX)
729
+ maxX = x + colSpan;
730
+ }
731
+
732
+ if (rowSpan) {
733
+ if (y + rowSpan > maxY)
734
+ maxY = y + rowSpan;
735
+ }
736
+ }
737
+ }
738
+ }
739
+
740
+ // Remove current selection
741
+ dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
742
+
743
+ // Add new selection
744
+ for (y = startY; y <= maxY; y++) {
745
+ for (x = startX; x <= maxX; x++) {
746
+ if (grid[y][x])
747
+ dom.addClass(grid[y][x].elm, 'mceSelected');
748
+ }
749
+ }
750
+ }
751
+ };
752
+
753
+ // Expose to public
754
+ tinymce.extend(this, {
755
+ deleteTable : deleteTable,
756
+ split : split,
757
+ merge : merge,
758
+ insertRow : insertRow,
759
+ insertCol : insertCol,
760
+ deleteCols : deleteCols,
761
+ deleteRows : deleteRows,
762
+ cutRows : cutRows,
763
+ copyRows : copyRows,
764
+ pasteRows : pasteRows,
765
+ getPos : getPos,
766
+ setStartCell : setStartCell,
767
+ setEndCell : setEndCell
768
+ });
769
+ };
770
+
771
+ tinymce.create('tinymce.plugins.TablePlugin', {
772
+ init : function(ed, url) {
773
+ var winMan, clipboardRows, hasCellSelection = true; // Might be selected cells on reload
774
+
775
+ function createTableGrid(node) {
776
+ var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
777
+
778
+ if (tblElm)
779
+ return new TableGrid(tblElm, ed.dom, selection);
780
+ };
781
+
782
+ function cleanup() {
783
+ // Restore selection possibilities
784
+ ed.getBody().style.webkitUserSelect = '';
785
+
786
+ if (hasCellSelection) {
787
+ ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
788
+ hasCellSelection = false;
789
+ }
790
+ };
791
+
792
+ // Register buttons
793
+ each([
794
+ ['table', 'table.desc', 'mceInsertTable', true],
795
+ ['delete_table', 'table.del', 'mceTableDelete'],
796
+ ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
797
+ ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
798
+ ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
799
+ ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
800
+ ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
801
+ ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
802
+ ['row_props', 'table.row_desc', 'mceTableRowProps', true],
803
+ ['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
804
+ ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
805
+ ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
806
+ ], function(c) {
807
+ ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
808
+ });
809
+
810
+ // Select whole table is a table border is clicked
811
+ if (!tinymce.isIE) {
812
+ ed.onClick.add(function(ed, e) {
813
+ e = e.target;
814
+
815
+ if (e.nodeName === 'TABLE') {
816
+ ed.selection.select(e);
817
+ ed.nodeChanged();
818
+ }
819
+ });
820
+ }
821
+
822
+ ed.onPreProcess.add(function(ed, args) {
823
+ var nodes, i, node, dom = ed.dom, value;
824
+
825
+ nodes = dom.select('table', args.node);
826
+ i = nodes.length;
827
+ while (i--) {
828
+ node = nodes[i];
829
+ dom.setAttrib(node, 'data-mce-style', '');
830
+
831
+ if ((value = dom.getAttrib(node, 'width'))) {
832
+ dom.setStyle(node, 'width', value);
833
+ dom.setAttrib(node, 'width', '');
834
+ }
835
+
836
+ if ((value = dom.getAttrib(node, 'height'))) {
837
+ dom.setStyle(node, 'height', value);
838
+ dom.setAttrib(node, 'height', '');
839
+ }
840
+ }
841
+ });
842
+
843
+ // Handle node change updates
844
+ ed.onNodeChange.add(function(ed, cm, n) {
845
+ var p;
846
+
847
+ n = ed.selection.getStart();
848
+ p = ed.dom.getParent(n, 'td,th,caption');
849
+ cm.setActive('table', n.nodeName === 'TABLE' || !!p);
850
+
851
+ // Disable table tools if we are in caption
852
+ if (p && p.nodeName === 'CAPTION')
853
+ p = 0;
854
+
855
+ cm.setDisabled('delete_table', !p);
856
+ cm.setDisabled('delete_col', !p);
857
+ cm.setDisabled('delete_table', !p);
858
+ cm.setDisabled('delete_row', !p);
859
+ cm.setDisabled('col_after', !p);
860
+ cm.setDisabled('col_before', !p);
861
+ cm.setDisabled('row_after', !p);
862
+ cm.setDisabled('row_before', !p);
863
+ cm.setDisabled('row_props', !p);
864
+ cm.setDisabled('cell_props', !p);
865
+ cm.setDisabled('split_cells', !p);
866
+ cm.setDisabled('merge_cells', !p);
867
+ });
868
+
869
+ ed.onInit.add(function(ed) {
870
+ var startTable, startCell, dom = ed.dom, tableGrid;
871
+
872
+ winMan = ed.windowManager;
873
+
874
+ // Add cell selection logic
875
+ ed.onMouseDown.add(function(ed, e) {
876
+ if (e.button != 2) {
877
+ cleanup();
878
+
879
+ startCell = dom.getParent(e.target, 'td,th');
880
+ startTable = dom.getParent(startCell, 'table');
881
+ }
882
+ });
883
+
884
+ dom.bind(ed.getDoc(), 'mouseover', function(e) {
885
+ var sel, table, target = e.target;
886
+
887
+ if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
888
+ table = dom.getParent(target, 'table');
889
+ if (table == startTable) {
890
+ if (!tableGrid) {
891
+ tableGrid = createTableGrid(table);
892
+ tableGrid.setStartCell(startCell);
893
+
894
+ ed.getBody().style.webkitUserSelect = 'none';
895
+ }
896
+
897
+ tableGrid.setEndCell(target);
898
+ hasCellSelection = true;
899
+ }
900
+
901
+ // Remove current selection
902
+ sel = ed.selection.getSel();
903
+
904
+ try {
905
+ if (sel.removeAllRanges)
906
+ sel.removeAllRanges();
907
+ else
908
+ sel.empty();
909
+ } catch (ex) {
910
+ // IE9 might throw errors here
911
+ }
912
+
913
+ e.preventDefault();
914
+ }
915
+ });
916
+
917
+ ed.onMouseUp.add(function(ed, e) {
918
+ var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
919
+
920
+ // Move selection to startCell
921
+ if (startCell) {
922
+ if (tableGrid)
923
+ ed.getBody().style.webkitUserSelect = '';
924
+
925
+ function setPoint(node, start) {
926
+ var walker = new tinymce.dom.TreeWalker(node, node);
927
+
928
+ do {
929
+ // Text node
930
+ if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
931
+ if (start)
932
+ rng.setStart(node, 0);
933
+ else
934
+ rng.setEnd(node, node.nodeValue.length);
935
+
936
+ return;
937
+ }
938
+
939
+ // BR element
940
+ if (node.nodeName == 'BR') {
941
+ if (start)
942
+ rng.setStartBefore(node);
943
+ else
944
+ rng.setEndBefore(node);
945
+
946
+ return;
947
+ }
948
+ } while (node = (start ? walker.next() : walker.prev()));
949
+ }
950
+
951
+ // Try to expand text selection as much as we can only Gecko supports cell selection
952
+ selectedCells = dom.select('td.mceSelected,th.mceSelected');
953
+ if (selectedCells.length > 0) {
954
+ rng = dom.createRng();
955
+ node = selectedCells[0];
956
+ endNode = selectedCells[selectedCells.length - 1];
957
+ rng.setStartBefore(node);
958
+ rng.setEndAfter(node);
959
+
960
+ setPoint(node, 1);
961
+ walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
962
+
963
+ do {
964
+ if (node.nodeName == 'TD' || node.nodeName == 'TH') {
965
+ if (!dom.hasClass(node, 'mceSelected'))
966
+ break;
967
+
968
+ lastNode = node;
969
+ }
970
+ } while (node = walker.next());
971
+
972
+ setPoint(lastNode);
973
+
974
+ sel.setRng(rng);
975
+ }
976
+
977
+ ed.nodeChanged();
978
+ startCell = tableGrid = startTable = null;
979
+ }
980
+ });
981
+
982
+ ed.onKeyUp.add(function(ed, e) {
983
+ cleanup();
984
+ });
985
+
986
+ ed.onKeyDown.add(function (ed, e) {
987
+ fixTableCellSelection(ed);
988
+ });
989
+
990
+ ed.onMouseDown.add(function (ed, e) {
991
+ if (e.button != 2) {
992
+ fixTableCellSelection(ed);
993
+ }
994
+ });
995
+ function tableCellSelected(ed, rng, n, currentCell) {
996
+ // The decision of when a table cell is selected is somewhat involved. The fact that this code is
997
+ // required is actually a pointer to the root cause of this bug. A cell is selected when the start
998
+ // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases)
999
+ // or the parent of the table (in the case of the selection containing the last cell of a table).
1000
+ var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'),
1001
+ tableParent, allOfCellSelected, tableCellSelection;
1002
+ if (table)
1003
+ tableParent = table.parentNode;
1004
+ allOfCellSelected =rng.startContainer.nodeType == TEXT_NODE &&
1005
+ rng.startOffset == 0 &&
1006
+ rng.endOffset == 0 &&
1007
+ currentCell &&
1008
+ (n.nodeName=="TR" || n==tableParent);
1009
+ tableCellSelection = (n.nodeName=="TD"||n.nodeName=="TH")&& !currentCell;
1010
+ return allOfCellSelected || tableCellSelection;
1011
+ // return false;
1012
+ }
1013
+
1014
+ // this nasty hack is here to work around some WebKit selection bugs.
1015
+ function fixTableCellSelection(ed) {
1016
+ if (!tinymce.isWebKit)
1017
+ return;
1018
+
1019
+ var rng = ed.selection.getRng();
1020
+ var n = ed.selection.getNode();
1021
+ var currentCell = ed.dom.getParent(rng.startContainer, 'TD,TH');
1022
+
1023
+ if (!tableCellSelected(ed, rng, n, currentCell))
1024
+ return;
1025
+ if (!currentCell) {
1026
+ currentCell=n;
1027
+ }
1028
+
1029
+ // Get the very last node inside the table cell
1030
+ var end = currentCell.lastChild;
1031
+ while (end.lastChild)
1032
+ end = end.lastChild;
1033
+
1034
+ // Select the entire table cell. Nothing outside of the table cell should be selected.
1035
+ rng.setEnd(end, end.nodeValue.length);
1036
+ ed.selection.setRng(rng);
1037
+ }
1038
+ ed.plugins.table.fixTableCellSelection=fixTableCellSelection;
1039
+
1040
+ // Add context menu
1041
+ if (ed && ed.plugins.contextmenu) {
1042
+ ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
1043
+ var sm, se = ed.selection, el = se.getNode() || ed.getBody();
1044
+
1045
+ if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
1046
+ m.removeAll();
1047
+
1048
+ if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
1049
+ m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
1050
+ m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
1051
+ m.addSeparator();
1052
+ }
1053
+
1054
+ if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
1055
+ m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
1056
+ m.addSeparator();
1057
+ }
1058
+
1059
+ m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
1060
+ m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
1061
+ m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
1062
+ m.addSeparator();
1063
+
1064
+ // Cell menu
1065
+ sm = m.addMenu({title : 'table.cell'});
1066
+ sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
1067
+ sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
1068
+ sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
1069
+
1070
+ // Row menu
1071
+ sm = m.addMenu({title : 'table.row'});
1072
+ sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
1073
+ sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
1074
+ sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
1075
+ sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
1076
+ sm.addSeparator();
1077
+ sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
1078
+ sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
1079
+ sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
1080
+ sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
1081
+
1082
+ // Column menu
1083
+ sm = m.addMenu({title : 'table.col'});
1084
+ sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
1085
+ sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
1086
+ sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
1087
+ } else
1088
+ m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
1089
+ });
1090
+ }
1091
+
1092
+ // Fix to allow navigating up and down in a table in WebKit browsers.
1093
+ if (tinymce.isWebKit) {
1094
+ function moveSelection(ed, e) {
1095
+ var VK = tinymce.VK;
1096
+ var key = e.keyCode;
1097
+
1098
+ function handle(upBool, sourceNode, event) {
1099
+ var siblingDirection = upBool ? 'previousSibling' : 'nextSibling';
1100
+ var currentRow = ed.dom.getParent(sourceNode, 'tr');
1101
+ var siblingRow = currentRow[siblingDirection];
1102
+
1103
+ if (siblingRow) {
1104
+ moveCursorToRow(ed, sourceNode, siblingRow, upBool);
1105
+ tinymce.dom.Event.cancel(event);
1106
+ return true;
1107
+ } else {
1108
+ var tableNode = ed.dom.getParent(currentRow, 'table');
1109
+ var middleNode = currentRow.parentNode;
1110
+ var parentNodeName = middleNode.nodeName.toLowerCase();
1111
+ if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) {
1112
+ var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody');
1113
+ if (targetParent !== null) {
1114
+ return moveToRowInTarget(upBool, targetParent, sourceNode, event);
1115
+ }
1116
+ }
1117
+ return escapeTable(upBool, currentRow, siblingDirection, tableNode, event);
1118
+ }
1119
+ }
1120
+
1121
+ function getTargetParent(upBool, topNode, secondNode, nodeName) {
1122
+ var tbodies = ed.dom.select('>' + nodeName, topNode);
1123
+ var position = tbodies.indexOf(secondNode);
1124
+ if (upBool && position === 0 || !upBool && position === tbodies.length - 1) {
1125
+ return getFirstHeadOrFoot(upBool, topNode);
1126
+ } else if (position === -1) {
1127
+ var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1;
1128
+ return tbodies[topOrBottom];
1129
+ } else {
1130
+ return tbodies[position + (upBool ? -1 : 1)];
1131
+ }
1132
+ }
1133
+
1134
+ function getFirstHeadOrFoot(upBool, parent) {
1135
+ var tagName = upBool ? 'thead' : 'tfoot';
1136
+ var headOrFoot = ed.dom.select('>' + tagName, parent);
1137
+ return headOrFoot.length !== 0 ? headOrFoot[0] : null;
1138
+ }
1139
+
1140
+ function moveToRowInTarget(upBool, targetParent, sourceNode, event) {
1141
+ var targetRow = getChildForDirection(targetParent, upBool);
1142
+ targetRow && moveCursorToRow(ed, sourceNode, targetRow, upBool);
1143
+ tinymce.dom.Event.cancel(event);
1144
+ return true;
1145
+ }
1146
+
1147
+ function escapeTable(upBool, currentRow, siblingDirection, table, event) {
1148
+ var tableSibling = table[siblingDirection];
1149
+ if (tableSibling) {
1150
+ moveCursorToStartOfElement(tableSibling);
1151
+ return true;
1152
+ } else {
1153
+ var parentCell = ed.dom.getParent(table, 'td,th');
1154
+ if (parentCell) {
1155
+ return handle(upBool, parentCell, event);
1156
+ } else {
1157
+ var backUpSibling = getChildForDirection(currentRow, !upBool);
1158
+ moveCursorToStartOfElement(backUpSibling);
1159
+ return tinymce.dom.Event.cancel(event);
1160
+ }
1161
+ }
1162
+ }
1163
+
1164
+ function getChildForDirection(parent, up) {
1165
+ var child = parent && parent[up ? 'lastChild' : 'firstChild'];
1166
+ // BR is not a valid table child to return in this case we return the table cell
1167
+ return child && child.nodeName === 'BR' ? ed.dom.getParent(child, 'td,th') : child;
1168
+ }
1169
+
1170
+ function moveCursorToStartOfElement(n) {
1171
+ ed.selection.setCursorLocation(n, 0);
1172
+ }
1173
+
1174
+ function isVerticalMovement() {
1175
+ return key == VK.UP || key == VK.DOWN;
1176
+ }
1177
+
1178
+ function isInTable(ed) {
1179
+ var node = ed.selection.getNode();
1180
+ var currentRow = ed.dom.getParent(node, 'tr');
1181
+ return currentRow !== null;
1182
+ }
1183
+
1184
+ function columnIndex(column) {
1185
+ var colIndex = 0;
1186
+ var c = column;
1187
+ while (c.previousSibling) {
1188
+ c = c.previousSibling;
1189
+ colIndex = colIndex + getSpanVal(c, "colspan");
1190
+ }
1191
+ return colIndex;
1192
+ }
1193
+
1194
+ function findColumn(rowElement, columnIndex) {
1195
+ var c = 0;
1196
+ var r = 0;
1197
+ each(rowElement.children, function(cell, i) {
1198
+ c = c + getSpanVal(cell, "colspan");
1199
+ r = i;
1200
+ if (c > columnIndex)
1201
+ return false;
1202
+ });
1203
+ return r;
1204
+ }
1205
+
1206
+ function moveCursorToRow(ed, node, row, upBool) {
1207
+ var srcColumnIndex = columnIndex(ed.dom.getParent(node, 'td,th'));
1208
+ var tgtColumnIndex = findColumn(row, srcColumnIndex);
1209
+ var tgtNode = row.childNodes[tgtColumnIndex];
1210
+ var rowCellTarget = getChildForDirection(tgtNode, upBool);
1211
+ moveCursorToStartOfElement(rowCellTarget || tgtNode);
1212
+ }
1213
+
1214
+ function shouldFixCaret(preBrowserNode) {
1215
+ var newNode = ed.selection.getNode();
1216
+ var newParent = ed.dom.getParent(newNode, 'td,th');
1217
+ var oldParent = ed.dom.getParent(preBrowserNode, 'td,th');
1218
+ return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent)
1219
+ }
1220
+
1221
+ function checkSameParentTable(nodeOne, NodeTwo) {
1222
+ return ed.dom.getParent(nodeOne, 'TABLE') === ed.dom.getParent(NodeTwo, 'TABLE');
1223
+ }
1224
+
1225
+ if (isVerticalMovement() && isInTable(ed)) {
1226
+ var preBrowserNode = ed.selection.getNode();
1227
+ setTimeout(function() {
1228
+ if (shouldFixCaret(preBrowserNode)) {
1229
+ handle(!e.shiftKey && key === VK.UP, preBrowserNode, e);
1230
+ }
1231
+ }, 0);
1232
+ }
1233
+ }
1234
+
1235
+ ed.onKeyDown.add(moveSelection);
1236
+ }
1237
+
1238
+ // Fixes an issue on Gecko where it's impossible to place the caret behind a table
1239
+ // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
1240
+ function fixTableCaretPos() {
1241
+ var last;
1242
+
1243
+ // Skip empty text nodes form the end
1244
+ for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
1245
+
1246
+ if (last && last.nodeName == 'TABLE') {
1247
+ if (ed.settings.forced_root_block)
1248
+ ed.dom.add(ed.getBody(), ed.settings.forced_root_block, null, tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />');
1249
+ else
1250
+ ed.dom.add(ed.getBody(), 'br', {'data-mce-bogus': '1'});
1251
+ }
1252
+ };
1253
+
1254
+ // Fixes an bug where it's impossible to place the caret before a table in Gecko
1255
+ // this fix solves it by detecting when the caret is at the beginning of such a table
1256
+ // and then manually moves the caret infront of the table
1257
+ if (tinymce.isGecko) {
1258
+ ed.onKeyDown.add(function(ed, e) {
1259
+ var rng, table, dom = ed.dom;
1260
+
1261
+ // On gecko it's not possible to place the caret before a table
1262
+ if (e.keyCode == 37 || e.keyCode == 38) {
1263
+ rng = ed.selection.getRng();
1264
+ table = dom.getParent(rng.startContainer, 'table');
1265
+
1266
+ if (table && ed.getBody().firstChild == table) {
1267
+ if (isAtStart(rng, table)) {
1268
+ rng = dom.createRng();
1269
+
1270
+ rng.setStartBefore(table);
1271
+ rng.setEndBefore(table);
1272
+
1273
+ ed.selection.setRng(rng);
1274
+
1275
+ e.preventDefault();
1276
+ }
1277
+ }
1278
+ }
1279
+ });
1280
+ }
1281
+
1282
+ ed.onKeyUp.add(fixTableCaretPos);
1283
+ ed.onSetContent.add(fixTableCaretPos);
1284
+ ed.onVisualAid.add(fixTableCaretPos);
1285
+
1286
+ ed.onPreProcess.add(function(ed, o) {
1287
+ var last = o.node.lastChild;
1288
+
1289
+ if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 && (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) && last.previousSibling && last.previousSibling.nodeName == "TABLE") {
1290
+ ed.dom.remove(last);
1291
+ }
1292
+ });
1293
+
1294
+
1295
+ /**
1296
+ * Fixes bug in Gecko where shift-enter in table cell does not place caret on new line
1297
+ */
1298
+ if (tinymce.isGecko) {
1299
+ ed.onKeyDown.add(function(ed, e) {
1300
+ if (e.keyCode === tinymce.VK.ENTER && e.shiftKey) {
1301
+ var node = ed.selection.getRng().startContainer;
1302
+ var tableCell = dom.getParent(node, 'td,th');
1303
+ if (tableCell) {
1304
+ var zeroSizedNbsp = ed.getDoc().createTextNode("\uFEFF");
1305
+ dom.insertAfter(zeroSizedNbsp, node);
1306
+ }
1307
+ }
1308
+ });
1309
+ }
1310
+
1311
+
1312
+ fixTableCaretPos();
1313
+ ed.startContent = ed.getContent({format : 'raw'});
1314
+ });
1315
+
1316
+ // Register action commands
1317
+ each({
1318
+ mceTableSplitCells : function(grid) {
1319
+ grid.split();
1320
+ },
1321
+
1322
+ mceTableMergeCells : function(grid) {
1323
+ var rowSpan, colSpan, cell;
1324
+
1325
+ cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
1326
+ if (cell) {
1327
+ rowSpan = cell.rowSpan;
1328
+ colSpan = cell.colSpan;
1329
+ }
1330
+
1331
+ if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
1332
+ winMan.open({
1333
+ url : url + '/merge_cells.htm',
1334
+ width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
1335
+ height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
1336
+ inline : 1
1337
+ }, {
1338
+ rows : rowSpan,
1339
+ cols : colSpan,
1340
+ onaction : function(data) {
1341
+ grid.merge(cell, data.cols, data.rows);
1342
+ },
1343
+ plugin_url : url
1344
+ });
1345
+ } else
1346
+ grid.merge();
1347
+ },
1348
+
1349
+ mceTableInsertRowBefore : function(grid) {
1350
+ grid.insertRow(true);
1351
+ },
1352
+
1353
+ mceTableInsertRowAfter : function(grid) {
1354
+ grid.insertRow();
1355
+ },
1356
+
1357
+ mceTableInsertColBefore : function(grid) {
1358
+ grid.insertCol(true);
1359
+ },
1360
+
1361
+ mceTableInsertColAfter : function(grid) {
1362
+ grid.insertCol();
1363
+ },
1364
+
1365
+ mceTableDeleteCol : function(grid) {
1366
+ grid.deleteCols();
1367
+ },
1368
+
1369
+ mceTableDeleteRow : function(grid) {
1370
+ grid.deleteRows();
1371
+ },
1372
+
1373
+ mceTableCutRow : function(grid) {
1374
+ clipboardRows = grid.cutRows();
1375
+ },
1376
+
1377
+ mceTableCopyRow : function(grid) {
1378
+ clipboardRows = grid.copyRows();
1379
+ },
1380
+
1381
+ mceTablePasteRowBefore : function(grid) {
1382
+ grid.pasteRows(clipboardRows, true);
1383
+ },
1384
+
1385
+ mceTablePasteRowAfter : function(grid) {
1386
+ grid.pasteRows(clipboardRows);
1387
+ },
1388
+
1389
+ mceTableDelete : function(grid) {
1390
+ grid.deleteTable();
1391
+ }
1392
+ }, function(func, name) {
1393
+ ed.addCommand(name, function() {
1394
+ var grid = createTableGrid();
1395
+
1396
+ if (grid) {
1397
+ func(grid);
1398
+ ed.execCommand('mceRepaint');
1399
+ cleanup();
1400
+ }
1401
+ });
1402
+ });
1403
+
1404
+ // Register dialog commands
1405
+ each({
1406
+ mceInsertTable : function(val) {
1407
+ winMan.open({
1408
+ url : url + '/table.htm',
1409
+ width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
1410
+ height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
1411
+ inline : 1
1412
+ }, {
1413
+ plugin_url : url,
1414
+ action : val ? val.action : 0
1415
+ });
1416
+ },
1417
+
1418
+ mceTableRowProps : function() {
1419
+ winMan.open({
1420
+ url : url + '/row.htm',
1421
+ width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
1422
+ height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
1423
+ inline : 1
1424
+ }, {
1425
+ plugin_url : url
1426
+ });
1427
+ },
1428
+
1429
+ mceTableCellProps : function() {
1430
+ winMan.open({
1431
+ url : url + '/cell.htm',
1432
+ width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
1433
+ height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
1434
+ inline : 1
1435
+ }, {
1436
+ plugin_url : url
1437
+ });
1438
+ }
1439
+ }, function(func, name) {
1440
+ ed.addCommand(name, function(ui, val) {
1441
+ func(val);
1442
+ });
1443
+ });
1444
+ }
1445
+ });
1446
+
1447
+ // Register plugin
1448
+ tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
1449
+ })(tinymce);