Mercurial > public > sg101
comparison static/js/tiny_mce/plugins/table/editor_plugin_src.js @ 312:88b2b9cb8c1f
Fixing #142; cut over to the django.contrib.staticfiles app.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Thu, 27 Jan 2011 02:56:10 +0000 |
parents | |
children | 6c182ceb7147 |
comparison
equal
deleted
inserted
replaced
311:b1c39788e511 | 312:88b2b9cb8c1f |
---|---|
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 /** | |
29 * Table Grid class. | |
30 */ | |
31 function TableGrid(table, dom, selection) { | |
32 var grid, startPos, endPos, selectedCell; | |
33 | |
34 buildGrid(); | |
35 selectedCell = dom.getParent(selection.getStart(), 'th,td'); | |
36 if (selectedCell) { | |
37 startPos = getPos(selectedCell); | |
38 endPos = findEndPos(); | |
39 selectedCell = getCell(startPos.x, startPos.y); | |
40 } | |
41 | |
42 function cloneNode(node, children) { | |
43 node = node.cloneNode(children); | |
44 node.removeAttribute('id'); | |
45 | |
46 return node; | |
47 } | |
48 | |
49 function buildGrid() { | |
50 var startY = 0; | |
51 | |
52 grid = []; | |
53 | |
54 each(['thead', 'tbody', 'tfoot'], function(part) { | |
55 var rows = dom.select('> ' + part + ' tr', table); | |
56 | |
57 each(rows, function(tr, y) { | |
58 y += startY; | |
59 | |
60 each(dom.select('> td, > th', tr), function(td, x) { | |
61 var x2, y2, rowspan, colspan; | |
62 | |
63 // Skip over existing cells produced by rowspan | |
64 if (grid[y]) { | |
65 while (grid[y][x]) | |
66 x++; | |
67 } | |
68 | |
69 // Get col/rowspan from cell | |
70 rowspan = getSpanVal(td, 'rowspan'); | |
71 colspan = getSpanVal(td, 'colspan'); | |
72 | |
73 // Fill out rowspan/colspan right and down | |
74 for (y2 = y; y2 < y + rowspan; y2++) { | |
75 if (!grid[y2]) | |
76 grid[y2] = []; | |
77 | |
78 for (x2 = x; x2 < x + colspan; x2++) { | |
79 grid[y2][x2] = { | |
80 part : part, | |
81 real : y2 == y && x2 == x, | |
82 elm : td, | |
83 rowspan : rowspan, | |
84 colspan : colspan | |
85 }; | |
86 } | |
87 } | |
88 }); | |
89 }); | |
90 | |
91 startY += rows.length; | |
92 }); | |
93 }; | |
94 | |
95 function getCell(x, y) { | |
96 var row; | |
97 | |
98 row = grid[y]; | |
99 if (row) | |
100 return row[x]; | |
101 }; | |
102 | |
103 function getSpanVal(td, name) { | |
104 return parseInt(td.getAttribute(name) || 1); | |
105 }; | |
106 | |
107 function isCellSelected(cell) { | |
108 return dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell; | |
109 }; | |
110 | |
111 function getSelectedRows() { | |
112 var rows = []; | |
113 | |
114 each(table.rows, function(row) { | |
115 each(row.cells, function(cell) { | |
116 if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) { | |
117 rows.push(row); | |
118 return false; | |
119 } | |
120 }); | |
121 }); | |
122 | |
123 return rows; | |
124 }; | |
125 | |
126 function deleteTable() { | |
127 var rng = dom.createRng(); | |
128 | |
129 rng.setStartAfter(table); | |
130 rng.setEndAfter(table); | |
131 | |
132 selection.setRng(rng); | |
133 | |
134 dom.remove(table); | |
135 }; | |
136 | |
137 function cloneCell(cell) { | |
138 var formatNode; | |
139 | |
140 // Clone formats | |
141 tinymce.walk(cell, function(node) { | |
142 var curNode; | |
143 | |
144 if (node.nodeType == 3) { | |
145 each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) { | |
146 node = cloneNode(node, false); | |
147 | |
148 if (!formatNode) | |
149 formatNode = curNode = node; | |
150 else if (curNode) | |
151 curNode.appendChild(node); | |
152 | |
153 curNode = node; | |
154 }); | |
155 | |
156 // Add something to the inner node | |
157 if (curNode) | |
158 curNode.innerHTML = tinymce.isIE ? ' ' : '<br _mce_bogus="1" />'; | |
159 | |
160 return false; | |
161 } | |
162 }, 'childNodes'); | |
163 | |
164 cell = cloneNode(cell, false); | |
165 cell.rowSpan = cell.colSpan = 1; | |
166 | |
167 if (formatNode) { | |
168 cell.appendChild(formatNode); | |
169 } else { | |
170 if (!tinymce.isIE) | |
171 cell.innerHTML = '<br _mce_bogus="1" />'; | |
172 } | |
173 | |
174 return cell; | |
175 }; | |
176 | |
177 function cleanup() { | |
178 var rng = dom.createRng(); | |
179 | |
180 // Empty rows | |
181 each(dom.select('tr', table), function(tr) { | |
182 if (tr.cells.length == 0) | |
183 dom.remove(tr); | |
184 }); | |
185 | |
186 // Empty table | |
187 if (dom.select('tr', table).length == 0) { | |
188 rng.setStartAfter(table); | |
189 rng.setEndAfter(table); | |
190 selection.setRng(rng); | |
191 dom.remove(table); | |
192 return; | |
193 } | |
194 | |
195 // Empty header/body/footer | |
196 each(dom.select('thead,tbody,tfoot', table), function(part) { | |
197 if (part.rows.length == 0) | |
198 dom.remove(part); | |
199 }); | |
200 | |
201 // Restore selection to start position if it still exists | |
202 buildGrid(); | |
203 | |
204 // Restore the selection to the closest table position | |
205 row = grid[Math.min(grid.length - 1, startPos.y)]; | |
206 if (row) { | |
207 selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); | |
208 selection.collapse(true); | |
209 } | |
210 }; | |
211 | |
212 function fillLeftDown(x, y, rows, cols) { | |
213 var tr, x2, r, c, cell; | |
214 | |
215 tr = grid[y][x].elm.parentNode; | |
216 for (r = 1; r <= rows; r++) { | |
217 tr = dom.getNext(tr, 'tr'); | |
218 | |
219 if (tr) { | |
220 // Loop left to find real cell | |
221 for (x2 = x; x2 >= 0; x2--) { | |
222 cell = grid[y + r][x2].elm; | |
223 | |
224 if (cell.parentNode == tr) { | |
225 // Append clones after | |
226 for (c = 1; c <= cols; c++) | |
227 dom.insertAfter(cloneCell(cell), cell); | |
228 | |
229 break; | |
230 } | |
231 } | |
232 | |
233 if (x2 == -1) { | |
234 // Insert nodes before first cell | |
235 for (c = 1; c <= cols; c++) | |
236 tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); | |
237 } | |
238 } | |
239 } | |
240 }; | |
241 | |
242 function split() { | |
243 each(grid, function(row, y) { | |
244 each(row, function(cell, x) { | |
245 var colSpan, rowSpan, newCell, i; | |
246 | |
247 if (isCellSelected(cell)) { | |
248 cell = cell.elm; | |
249 colSpan = getSpanVal(cell, 'colspan'); | |
250 rowSpan = getSpanVal(cell, 'rowspan'); | |
251 | |
252 if (colSpan > 1 || rowSpan > 1) { | |
253 cell.colSpan = cell.rowSpan = 1; | |
254 | |
255 // Insert cells right | |
256 for (i = 0; i < colSpan - 1; i++) | |
257 dom.insertAfter(cloneCell(cell), cell); | |
258 | |
259 fillLeftDown(x, y, rowSpan - 1, colSpan); | |
260 } | |
261 } | |
262 }); | |
263 }); | |
264 }; | |
265 | |
266 function merge(cell, cols, rows) { | |
267 var startX, startY, endX, endY, x, y, startCell, endCell, cell, children; | |
268 | |
269 // Use specified cell and cols/rows | |
270 if (cell) { | |
271 pos = getPos(cell); | |
272 startX = pos.x; | |
273 startY = pos.y; | |
274 endX = startX + (cols - 1); | |
275 endY = startY + (rows - 1); | |
276 } else { | |
277 // Use selection | |
278 startX = startPos.x; | |
279 startY = startPos.y; | |
280 endX = endPos.x; | |
281 endY = endPos.y; | |
282 } | |
283 | |
284 // Find start/end cells | |
285 startCell = getCell(startX, startY); | |
286 endCell = getCell(endX, endY); | |
287 | |
288 // Check if the cells exists and if they are of the same part for example tbody = tbody | |
289 if (startCell && endCell && startCell.part == endCell.part) { | |
290 // Split and rebuild grid | |
291 split(); | |
292 buildGrid(); | |
293 | |
294 // Set row/col span to start cell | |
295 startCell = getCell(startX, startY).elm; | |
296 startCell.colSpan = (endX - startX) + 1; | |
297 startCell.rowSpan = (endY - startY) + 1; | |
298 | |
299 // Remove other cells and add it's contents to the start cell | |
300 for (y = startY; y <= endY; y++) { | |
301 for (x = startX; x <= endX; x++) { | |
302 cell = grid[y][x].elm; | |
303 | |
304 if (cell != startCell) { | |
305 // Move children to startCell | |
306 children = tinymce.grep(cell.childNodes); | |
307 each(children, function(node, i) { | |
308 // Jump over last BR element | |
309 if (node.nodeName != 'BR' || i != children.length - 1) | |
310 startCell.appendChild(node); | |
311 }); | |
312 | |
313 // Remove cell | |
314 dom.remove(cell); | |
315 } | |
316 } | |
317 } | |
318 | |
319 // Remove empty rows etc and restore caret location | |
320 cleanup(); | |
321 } | |
322 }; | |
323 | |
324 function insertRow(before) { | |
325 var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell; | |
326 | |
327 // Find first/last row | |
328 each(grid, function(row, y) { | |
329 each(row, function(cell, x) { | |
330 if (isCellSelected(cell)) { | |
331 cell = cell.elm; | |
332 rowElm = cell.parentNode; | |
333 newRow = cloneNode(rowElm, false); | |
334 posY = y; | |
335 | |
336 if (before) | |
337 return false; | |
338 } | |
339 }); | |
340 | |
341 if (before) | |
342 return !posY; | |
343 }); | |
344 | |
345 for (x = 0; x < grid[0].length; x++) { | |
346 cell = grid[posY][x].elm; | |
347 | |
348 if (cell != lastCell) { | |
349 if (!before) { | |
350 rowSpan = getSpanVal(cell, 'rowspan'); | |
351 if (rowSpan > 1) { | |
352 cell.rowSpan = rowSpan + 1; | |
353 continue; | |
354 } | |
355 } else { | |
356 // Check if cell above can be expanded | |
357 if (posY > 0 && grid[posY - 1][x]) { | |
358 otherCell = grid[posY - 1][x].elm; | |
359 rowSpan = getSpanVal(otherCell, 'rowspan'); | |
360 if (rowSpan > 1) { | |
361 otherCell.rowSpan = rowSpan + 1; | |
362 continue; | |
363 } | |
364 } | |
365 } | |
366 | |
367 // Insert new cell into new row | |
368 newCell = cloneCell(cell) | |
369 newCell.colSpan = cell.colSpan; | |
370 newRow.appendChild(newCell); | |
371 | |
372 lastCell = cell; | |
373 } | |
374 } | |
375 | |
376 if (newRow.hasChildNodes()) { | |
377 if (!before) | |
378 dom.insertAfter(newRow, rowElm); | |
379 else | |
380 rowElm.parentNode.insertBefore(newRow, rowElm); | |
381 } | |
382 }; | |
383 | |
384 function insertCol(before) { | |
385 var posX, lastCell; | |
386 | |
387 // Find first/last column | |
388 each(grid, function(row, y) { | |
389 each(row, function(cell, x) { | |
390 if (isCellSelected(cell)) { | |
391 posX = x; | |
392 | |
393 if (before) | |
394 return false; | |
395 } | |
396 }); | |
397 | |
398 if (before) | |
399 return !posX; | |
400 }); | |
401 | |
402 each(grid, function(row, y) { | |
403 var cell = row[posX].elm, rowSpan, colSpan; | |
404 | |
405 if (cell != lastCell) { | |
406 colSpan = getSpanVal(cell, 'colspan'); | |
407 rowSpan = getSpanVal(cell, 'rowspan'); | |
408 | |
409 if (colSpan == 1) { | |
410 if (!before) { | |
411 dom.insertAfter(cloneCell(cell), cell); | |
412 fillLeftDown(posX, y, rowSpan - 1, colSpan); | |
413 } else { | |
414 cell.parentNode.insertBefore(cloneCell(cell), cell); | |
415 fillLeftDown(posX, y, rowSpan - 1, colSpan); | |
416 } | |
417 } else | |
418 cell.colSpan++; | |
419 | |
420 lastCell = cell; | |
421 } | |
422 }); | |
423 }; | |
424 | |
425 function deleteCols() { | |
426 var cols = []; | |
427 | |
428 // Get selected column indexes | |
429 each(grid, function(row, y) { | |
430 each(row, function(cell, x) { | |
431 if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) { | |
432 each(grid, function(row) { | |
433 var cell = row[x].elm, colSpan; | |
434 | |
435 colSpan = getSpanVal(cell, 'colspan'); | |
436 | |
437 if (colSpan > 1) | |
438 cell.colSpan = colSpan - 1; | |
439 else | |
440 dom.remove(cell); | |
441 }); | |
442 | |
443 cols.push(x); | |
444 } | |
445 }); | |
446 }); | |
447 | |
448 cleanup(); | |
449 }; | |
450 | |
451 function deleteRows() { | |
452 var rows; | |
453 | |
454 function deleteRow(tr) { | |
455 var nextTr, pos, lastCell; | |
456 | |
457 nextTr = dom.getNext(tr, 'tr'); | |
458 | |
459 // Move down row spanned cells | |
460 each(tr.cells, function(cell) { | |
461 var rowSpan = getSpanVal(cell, 'rowspan'); | |
462 | |
463 if (rowSpan > 1) { | |
464 cell.rowSpan = rowSpan - 1; | |
465 pos = getPos(cell); | |
466 fillLeftDown(pos.x, pos.y, 1, 1); | |
467 } | |
468 }); | |
469 | |
470 // Delete cells | |
471 pos = getPos(tr.cells[0]); | |
472 each(grid[pos.y], function(cell) { | |
473 var rowSpan; | |
474 | |
475 cell = cell.elm; | |
476 | |
477 if (cell != lastCell) { | |
478 rowSpan = getSpanVal(cell, 'rowspan'); | |
479 | |
480 if (rowSpan <= 1) | |
481 dom.remove(cell); | |
482 else | |
483 cell.rowSpan = rowSpan - 1; | |
484 | |
485 lastCell = cell; | |
486 } | |
487 }); | |
488 }; | |
489 | |
490 // Get selected rows and move selection out of scope | |
491 rows = getSelectedRows(); | |
492 | |
493 // Delete all selected rows | |
494 each(rows.reverse(), function(tr) { | |
495 deleteRow(tr); | |
496 }); | |
497 | |
498 cleanup(); | |
499 }; | |
500 | |
501 function cutRows() { | |
502 var rows = getSelectedRows(); | |
503 | |
504 dom.remove(rows); | |
505 cleanup(); | |
506 | |
507 return rows; | |
508 }; | |
509 | |
510 function copyRows() { | |
511 var rows = getSelectedRows(); | |
512 | |
513 each(rows, function(row, i) { | |
514 rows[i] = cloneNode(row, true); | |
515 }); | |
516 | |
517 return rows; | |
518 }; | |
519 | |
520 function pasteRows(rows, before) { | |
521 var selectedRows = getSelectedRows(), | |
522 targetRow = selectedRows[before ? 0 : selectedRows.length - 1], | |
523 targetCellCount = targetRow.cells.length; | |
524 | |
525 // Calc target cell count | |
526 each(grid, function(row) { | |
527 var match; | |
528 | |
529 targetCellCount = 0; | |
530 each(row, function(cell, x) { | |
531 if (cell.real) | |
532 targetCellCount += cell.colspan; | |
533 | |
534 if (cell.elm.parentNode == targetRow) | |
535 match = 1; | |
536 }); | |
537 | |
538 if (match) | |
539 return false; | |
540 }); | |
541 | |
542 if (!before) | |
543 rows.reverse(); | |
544 | |
545 each(rows, function(row) { | |
546 var cellCount = row.cells.length, cell; | |
547 | |
548 // Remove col/rowspans | |
549 for (i = 0; i < cellCount; i++) { | |
550 cell = row.cells[i]; | |
551 cell.colSpan = cell.rowSpan = 1; | |
552 } | |
553 | |
554 // Needs more cells | |
555 for (i = cellCount; i < targetCellCount; i++) | |
556 row.appendChild(cloneCell(row.cells[cellCount - 1])); | |
557 | |
558 // Needs less cells | |
559 for (i = targetCellCount; i < cellCount; i++) | |
560 dom.remove(row.cells[i]); | |
561 | |
562 // Add before/after | |
563 if (before) | |
564 targetRow.parentNode.insertBefore(row, targetRow); | |
565 else | |
566 dom.insertAfter(row, targetRow); | |
567 }); | |
568 }; | |
569 | |
570 function getPos(target) { | |
571 var pos; | |
572 | |
573 each(grid, function(row, y) { | |
574 each(row, function(cell, x) { | |
575 if (cell.elm == target) { | |
576 pos = {x : x, y : y}; | |
577 return false; | |
578 } | |
579 }); | |
580 | |
581 return !pos; | |
582 }); | |
583 | |
584 return pos; | |
585 }; | |
586 | |
587 function setStartCell(cell) { | |
588 startPos = getPos(cell); | |
589 }; | |
590 | |
591 function findEndPos() { | |
592 var pos, maxX, maxY; | |
593 | |
594 maxX = maxY = 0; | |
595 | |
596 each(grid, function(row, y) { | |
597 each(row, function(cell, x) { | |
598 var colSpan, rowSpan; | |
599 | |
600 if (isCellSelected(cell)) { | |
601 cell = grid[y][x]; | |
602 | |
603 if (x > maxX) | |
604 maxX = x; | |
605 | |
606 if (y > maxY) | |
607 maxY = y; | |
608 | |
609 if (cell.real) { | |
610 colSpan = cell.colspan - 1; | |
611 rowSpan = cell.rowspan - 1; | |
612 | |
613 if (colSpan) { | |
614 if (x + colSpan > maxX) | |
615 maxX = x + colSpan; | |
616 } | |
617 | |
618 if (rowSpan) { | |
619 if (y + rowSpan > maxY) | |
620 maxY = y + rowSpan; | |
621 } | |
622 } | |
623 } | |
624 }); | |
625 }); | |
626 | |
627 return {x : maxX, y : maxY}; | |
628 }; | |
629 | |
630 function setEndCell(cell) { | |
631 var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan; | |
632 | |
633 endPos = getPos(cell); | |
634 | |
635 if (startPos && endPos) { | |
636 // Get start/end positions | |
637 startX = Math.min(startPos.x, endPos.x); | |
638 startY = Math.min(startPos.y, endPos.y); | |
639 endX = Math.max(startPos.x, endPos.x); | |
640 endY = Math.max(startPos.y, endPos.y); | |
641 | |
642 // Expand end positon to include spans | |
643 maxX = endX; | |
644 maxY = endY; | |
645 | |
646 // Expand startX | |
647 for (y = startY; y <= maxY; y++) { | |
648 cell = grid[y][startX]; | |
649 | |
650 if (!cell.real) { | |
651 if (startX - (cell.colspan - 1) < startX) | |
652 startX -= cell.colspan - 1; | |
653 } | |
654 } | |
655 | |
656 // Expand startY | |
657 for (x = startX; x <= maxX; x++) { | |
658 cell = grid[startY][x]; | |
659 | |
660 if (!cell.real) { | |
661 if (startY - (cell.rowspan - 1) < startY) | |
662 startY -= cell.rowspan - 1; | |
663 } | |
664 } | |
665 | |
666 // Find max X, Y | |
667 for (y = startY; y <= endY; y++) { | |
668 for (x = startX; x <= endX; x++) { | |
669 cell = grid[y][x]; | |
670 | |
671 if (cell.real) { | |
672 colSpan = cell.colspan - 1; | |
673 rowSpan = cell.rowspan - 1; | |
674 | |
675 if (colSpan) { | |
676 if (x + colSpan > maxX) | |
677 maxX = x + colSpan; | |
678 } | |
679 | |
680 if (rowSpan) { | |
681 if (y + rowSpan > maxY) | |
682 maxY = y + rowSpan; | |
683 } | |
684 } | |
685 } | |
686 } | |
687 | |
688 // Remove current selection | |
689 dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); | |
690 | |
691 // Add new selection | |
692 for (y = startY; y <= maxY; y++) { | |
693 for (x = startX; x <= maxX; x++) | |
694 dom.addClass(grid[y][x].elm, 'mceSelected'); | |
695 } | |
696 } | |
697 }; | |
698 | |
699 // Expose to public | |
700 tinymce.extend(this, { | |
701 deleteTable : deleteTable, | |
702 split : split, | |
703 merge : merge, | |
704 insertRow : insertRow, | |
705 insertCol : insertCol, | |
706 deleteCols : deleteCols, | |
707 deleteRows : deleteRows, | |
708 cutRows : cutRows, | |
709 copyRows : copyRows, | |
710 pasteRows : pasteRows, | |
711 getPos : getPos, | |
712 setStartCell : setStartCell, | |
713 setEndCell : setEndCell | |
714 }); | |
715 }; | |
716 | |
717 tinymce.create('tinymce.plugins.TablePlugin', { | |
718 init : function(ed, url) { | |
719 var winMan, clipboardRows; | |
720 | |
721 function createTableGrid(node) { | |
722 var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table'); | |
723 | |
724 if (tblElm) | |
725 return new TableGrid(tblElm, ed.dom, selection); | |
726 }; | |
727 | |
728 function cleanup() { | |
729 // Restore selection possibilities | |
730 ed.getBody().style.webkitUserSelect = ''; | |
731 ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); | |
732 }; | |
733 | |
734 // Register buttons | |
735 each([ | |
736 ['table', 'table.desc', 'mceInsertTable', true], | |
737 ['delete_table', 'table.del', 'mceTableDelete'], | |
738 ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'], | |
739 ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'], | |
740 ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'], | |
741 ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'], | |
742 ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'], | |
743 ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'], | |
744 ['row_props', 'table.row_desc', 'mceTableRowProps', true], | |
745 ['cell_props', 'table.cell_desc', 'mceTableCellProps', true], | |
746 ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true], | |
747 ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true] | |
748 ], function(c) { | |
749 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]}); | |
750 }); | |
751 | |
752 // Select whole table is a table border is clicked | |
753 if (!tinymce.isIE) { | |
754 ed.onClick.add(function(ed, e) { | |
755 e = e.target; | |
756 | |
757 if (e.nodeName === 'TABLE') | |
758 ed.selection.select(e); | |
759 }); | |
760 } | |
761 | |
762 // Handle node change updates | |
763 ed.onNodeChange.add(function(ed, cm, n) { | |
764 var p; | |
765 | |
766 n = ed.selection.getStart(); | |
767 p = ed.dom.getParent(n, 'td,th,caption'); | |
768 cm.setActive('table', n.nodeName === 'TABLE' || !!p); | |
769 | |
770 // Disable table tools if we are in caption | |
771 if (p && p.nodeName === 'CAPTION') | |
772 p = 0; | |
773 | |
774 cm.setDisabled('delete_table', !p); | |
775 cm.setDisabled('delete_col', !p); | |
776 cm.setDisabled('delete_table', !p); | |
777 cm.setDisabled('delete_row', !p); | |
778 cm.setDisabled('col_after', !p); | |
779 cm.setDisabled('col_before', !p); | |
780 cm.setDisabled('row_after', !p); | |
781 cm.setDisabled('row_before', !p); | |
782 cm.setDisabled('row_props', !p); | |
783 cm.setDisabled('cell_props', !p); | |
784 cm.setDisabled('split_cells', !p); | |
785 cm.setDisabled('merge_cells', !p); | |
786 }); | |
787 | |
788 ed.onInit.add(function(ed) { | |
789 var startTable, startCell, dom = ed.dom, tableGrid; | |
790 | |
791 winMan = ed.windowManager; | |
792 | |
793 // Add cell selection logic | |
794 ed.onMouseDown.add(function(ed, e) { | |
795 if (e.button != 2) { | |
796 cleanup(); | |
797 | |
798 startCell = dom.getParent(e.target, 'td,th'); | |
799 startTable = dom.getParent(startCell, 'table'); | |
800 } | |
801 }); | |
802 | |
803 dom.bind(ed.getDoc(), 'mouseover', function(e) { | |
804 var sel, table, target = e.target; | |
805 | |
806 if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) { | |
807 table = dom.getParent(target, 'table'); | |
808 if (table == startTable) { | |
809 if (!tableGrid) { | |
810 tableGrid = createTableGrid(table); | |
811 tableGrid.setStartCell(startCell); | |
812 | |
813 ed.getBody().style.webkitUserSelect = 'none'; | |
814 } | |
815 | |
816 tableGrid.setEndCell(target); | |
817 } | |
818 | |
819 // Remove current selection | |
820 sel = ed.selection.getSel(); | |
821 | |
822 if (sel.removeAllRanges) | |
823 sel.removeAllRanges(); | |
824 else | |
825 sel.empty(); | |
826 | |
827 e.preventDefault(); | |
828 } | |
829 }); | |
830 | |
831 ed.onMouseUp.add(function(ed, e) { | |
832 var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode; | |
833 | |
834 // Move selection to startCell | |
835 if (startCell) { | |
836 if (tableGrid) | |
837 ed.getBody().style.webkitUserSelect = ''; | |
838 | |
839 function setPoint(node, start) { | |
840 var walker = new tinymce.dom.TreeWalker(node, node); | |
841 | |
842 do { | |
843 // Text node | |
844 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { | |
845 if (start) | |
846 rng.setStart(node, 0); | |
847 else | |
848 rng.setEnd(node, node.nodeValue.length); | |
849 | |
850 return; | |
851 } | |
852 | |
853 // BR element | |
854 if (node.nodeName == 'BR') { | |
855 if (start) | |
856 rng.setStartBefore(node); | |
857 else | |
858 rng.setEndBefore(node); | |
859 | |
860 return; | |
861 } | |
862 } while (node = (start ? walker.next() : walker.prev())); | |
863 }; | |
864 | |
865 // Try to expand text selection as much as we can only Gecko supports cell selection | |
866 selectedCells = dom.select('td.mceSelected,th.mceSelected'); | |
867 if (selectedCells.length > 0) { | |
868 rng = dom.createRng(); | |
869 node = selectedCells[0]; | |
870 endNode = selectedCells[selectedCells.length - 1]; | |
871 | |
872 setPoint(node, 1); | |
873 walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table')); | |
874 | |
875 do { | |
876 if (node.nodeName == 'TD' || node.nodeName == 'TH') { | |
877 if (!dom.hasClass(node, 'mceSelected')) | |
878 break; | |
879 | |
880 lastNode = node; | |
881 } | |
882 } while (node = walker.next()); | |
883 | |
884 setPoint(lastNode); | |
885 | |
886 sel.setRng(rng); | |
887 } | |
888 | |
889 ed.nodeChanged(); | |
890 startCell = tableGrid = startTable = null; | |
891 } | |
892 }); | |
893 | |
894 ed.onKeyUp.add(function(ed, e) { | |
895 cleanup(); | |
896 }); | |
897 | |
898 // Add context menu | |
899 if (ed && ed.plugins.contextmenu) { | |
900 ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) { | |
901 var sm, se = ed.selection, el = se.getNode() || ed.getBody(); | |
902 | |
903 if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) { | |
904 m.removeAll(); | |
905 | |
906 if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) { | |
907 m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); | |
908 m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); | |
909 m.addSeparator(); | |
910 } | |
911 | |
912 if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) { | |
913 m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); | |
914 m.addSeparator(); | |
915 } | |
916 | |
917 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}}); | |
918 m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'}); | |
919 m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'}); | |
920 m.addSeparator(); | |
921 | |
922 // Cell menu | |
923 sm = m.addMenu({title : 'table.cell'}); | |
924 sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'}); | |
925 sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'}); | |
926 sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'}); | |
927 | |
928 // Row menu | |
929 sm = m.addMenu({title : 'table.row'}); | |
930 sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'}); | |
931 sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'}); | |
932 sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'}); | |
933 sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'}); | |
934 sm.addSeparator(); | |
935 sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'}); | |
936 sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'}); | |
937 sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows); | |
938 sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows); | |
939 | |
940 // Column menu | |
941 sm = m.addMenu({title : 'table.col'}); | |
942 sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'}); | |
943 sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'}); | |
944 sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'}); | |
945 } else | |
946 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'}); | |
947 }); | |
948 } | |
949 | |
950 // Fixes an issue on Gecko where it's impossible to place the caret behind a table | |
951 // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled | |
952 if (!tinymce.isIE) { | |
953 function fixTableCaretPos() { | |
954 var last; | |
955 | |
956 // Skip empty text nodes form the end | |
957 for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ; | |
958 | |
959 if (last && last.nodeName == 'TABLE') | |
960 ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />'); | |
961 }; | |
962 | |
963 // Fixes an bug where it's impossible to place the caret before a table in Gecko | |
964 // this fix solves it by detecting when the caret is at the beginning of such a table | |
965 // and then manually moves the caret infront of the table | |
966 if (tinymce.isGecko) { | |
967 ed.onKeyDown.add(function(ed, e) { | |
968 var rng, table, dom = ed.dom; | |
969 | |
970 // On gecko it's not possible to place the caret before a table | |
971 if (e.keyCode == 37 || e.keyCode == 38) { | |
972 rng = ed.selection.getRng(); | |
973 table = dom.getParent(rng.startContainer, 'table'); | |
974 | |
975 if (table && ed.getBody().firstChild == table) { | |
976 if (isAtStart(rng, table)) { | |
977 rng = dom.createRng(); | |
978 | |
979 rng.setStartBefore(table); | |
980 rng.setEndBefore(table); | |
981 | |
982 ed.selection.setRng(rng); | |
983 | |
984 e.preventDefault(); | |
985 } | |
986 } | |
987 } | |
988 }); | |
989 } | |
990 | |
991 ed.onKeyUp.add(fixTableCaretPos); | |
992 ed.onSetContent.add(fixTableCaretPos); | |
993 ed.onVisualAid.add(fixTableCaretPos); | |
994 | |
995 ed.onPreProcess.add(function(ed, o) { | |
996 var last = o.node.lastChild; | |
997 | |
998 if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR') | |
999 ed.dom.remove(last); | |
1000 }); | |
1001 | |
1002 fixTableCaretPos(); | |
1003 } | |
1004 }); | |
1005 | |
1006 // Register action commands | |
1007 each({ | |
1008 mceTableSplitCells : function(grid) { | |
1009 grid.split(); | |
1010 }, | |
1011 | |
1012 mceTableMergeCells : function(grid) { | |
1013 var rowSpan, colSpan, cell; | |
1014 | |
1015 cell = ed.dom.getParent(ed.selection.getNode(), 'th,td'); | |
1016 if (cell) { | |
1017 rowSpan = cell.rowSpan; | |
1018 colSpan = cell.colSpan; | |
1019 } | |
1020 | |
1021 if (!ed.dom.select('td.mceSelected,th.mceSelected').length) { | |
1022 winMan.open({ | |
1023 url : url + '/merge_cells.htm', | |
1024 width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)), | |
1025 height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)), | |
1026 inline : 1 | |
1027 }, { | |
1028 rows : rowSpan, | |
1029 cols : colSpan, | |
1030 onaction : function(data) { | |
1031 grid.merge(cell, data.cols, data.rows); | |
1032 }, | |
1033 plugin_url : url | |
1034 }); | |
1035 } else | |
1036 grid.merge(); | |
1037 }, | |
1038 | |
1039 mceTableInsertRowBefore : function(grid) { | |
1040 grid.insertRow(true); | |
1041 }, | |
1042 | |
1043 mceTableInsertRowAfter : function(grid) { | |
1044 grid.insertRow(); | |
1045 }, | |
1046 | |
1047 mceTableInsertColBefore : function(grid) { | |
1048 grid.insertCol(true); | |
1049 }, | |
1050 | |
1051 mceTableInsertColAfter : function(grid) { | |
1052 grid.insertCol(); | |
1053 }, | |
1054 | |
1055 mceTableDeleteCol : function(grid) { | |
1056 grid.deleteCols(); | |
1057 }, | |
1058 | |
1059 mceTableDeleteRow : function(grid) { | |
1060 grid.deleteRows(); | |
1061 }, | |
1062 | |
1063 mceTableCutRow : function(grid) { | |
1064 clipboardRows = grid.cutRows(); | |
1065 }, | |
1066 | |
1067 mceTableCopyRow : function(grid) { | |
1068 clipboardRows = grid.copyRows(); | |
1069 }, | |
1070 | |
1071 mceTablePasteRowBefore : function(grid) { | |
1072 grid.pasteRows(clipboardRows, true); | |
1073 }, | |
1074 | |
1075 mceTablePasteRowAfter : function(grid) { | |
1076 grid.pasteRows(clipboardRows); | |
1077 }, | |
1078 | |
1079 mceTableDelete : function(grid) { | |
1080 grid.deleteTable(); | |
1081 } | |
1082 }, function(func, name) { | |
1083 ed.addCommand(name, function() { | |
1084 var grid = createTableGrid(); | |
1085 | |
1086 if (grid) { | |
1087 func(grid); | |
1088 ed.execCommand('mceRepaint'); | |
1089 cleanup(); | |
1090 } | |
1091 }); | |
1092 }); | |
1093 | |
1094 // Register dialog commands | |
1095 each({ | |
1096 mceInsertTable : function(val) { | |
1097 winMan.open({ | |
1098 url : url + '/table.htm', | |
1099 width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)), | |
1100 height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)), | |
1101 inline : 1 | |
1102 }, { | |
1103 plugin_url : url, | |
1104 action : val ? val.action : 0 | |
1105 }); | |
1106 }, | |
1107 | |
1108 mceTableRowProps : function() { | |
1109 winMan.open({ | |
1110 url : url + '/row.htm', | |
1111 width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)), | |
1112 height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)), | |
1113 inline : 1 | |
1114 }, { | |
1115 plugin_url : url | |
1116 }); | |
1117 }, | |
1118 | |
1119 mceTableCellProps : function() { | |
1120 winMan.open({ | |
1121 url : url + '/cell.htm', | |
1122 width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)), | |
1123 height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)), | |
1124 inline : 1 | |
1125 }, { | |
1126 plugin_url : url | |
1127 }); | |
1128 } | |
1129 }, function(func, name) { | |
1130 ed.addCommand(name, function(ui, val) { | |
1131 func(val); | |
1132 }); | |
1133 }); | |
1134 } | |
1135 }); | |
1136 | |
1137 // Register plugin | |
1138 tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin); | |
1139 })(tinymce); |