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