PageRenderTime 57ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/BlogEngine.NET/editors/tiny_mce_3_4_3_1/plugins/table/editor_plugin_src.js

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