editor_plugin_src.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452
  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. function getSpanVal(td, name) {
  23. return parseInt(td.getAttribute(name) || 1);
  24. }
  25. /**
  26. * Table Grid class.
  27. */
  28. function TableGrid(table, dom, selection) {
  29. var grid, startPos, endPos, selectedCell;
  30. buildGrid();
  31. selectedCell = dom.getParent(selection.getStart(), 'th,td');
  32. if (selectedCell) {
  33. startPos = getPos(selectedCell);
  34. endPos = findEndPos();
  35. selectedCell = getCell(startPos.x, startPos.y);
  36. }
  37. function cloneNode(node, children) {
  38. node = node.cloneNode(children);
  39. node.removeAttribute('id');
  40. return node;
  41. }
  42. function buildGrid() {
  43. var startY = 0;
  44. grid = [];
  45. each(['thead', 'tbody', 'tfoot'], function(part) {
  46. var rows = dom.select('> ' + part + ' tr', table);
  47. each(rows, function(tr, y) {
  48. y += startY;
  49. each(dom.select('> td, > th', tr), function(td, x) {
  50. var x2, y2, rowspan, colspan;
  51. // Skip over existing cells produced by rowspan
  52. if (grid[y]) {
  53. while (grid[y][x])
  54. x++;
  55. }
  56. // Get col/rowspan from cell
  57. rowspan = getSpanVal(td, 'rowspan');
  58. colspan = getSpanVal(td, 'colspan');
  59. // Fill out rowspan/colspan right and down
  60. for (y2 = y; y2 < y + rowspan; y2++) {
  61. if (!grid[y2])
  62. grid[y2] = [];
  63. for (x2 = x; x2 < x + colspan; x2++) {
  64. grid[y2][x2] = {
  65. part : part,
  66. real : y2 == y && x2 == x,
  67. elm : td,
  68. rowspan : rowspan,
  69. colspan : colspan
  70. };
  71. }
  72. }
  73. });
  74. });
  75. startY += rows.length;
  76. });
  77. };
  78. function getCell(x, y) {
  79. var row;
  80. row = grid[y];
  81. if (row)
  82. return row[x];
  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. startPos = endPos = null;
  230. // Calculate start/end pos by checking for selected cells in grid works better with context menu
  231. each(grid, function(row, y) {
  232. each(row, function(cell, x) {
  233. if (isCellSelected(cell)) {
  234. if (!startPos) {
  235. startPos = {x: x, y: y};
  236. }
  237. endPos = {x: x, y: y};
  238. }
  239. });
  240. });
  241. // Use selection
  242. startX = startPos.x;
  243. startY = startPos.y;
  244. endX = endPos.x;
  245. endY = endPos.y;
  246. }
  247. // Find start/end cells
  248. startCell = getCell(startX, startY);
  249. endCell = getCell(endX, endY);
  250. // Check if the cells exists and if they are of the same part for example tbody = tbody
  251. if (startCell && endCell && startCell.part == endCell.part) {
  252. // Split and rebuild grid
  253. split();
  254. buildGrid();
  255. // Set row/col span to start cell
  256. startCell = getCell(startX, startY).elm;
  257. setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
  258. setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
  259. // Remove other cells and add it's contents to the start cell
  260. for (y = startY; y <= endY; y++) {
  261. for (x = startX; x <= endX; x++) {
  262. if (!grid[y] || !grid[y][x])
  263. continue;
  264. cell = grid[y][x].elm;
  265. if (cell != startCell) {
  266. // Move children to startCell
  267. children = tinymce.grep(cell.childNodes);
  268. each(children, function(node) {
  269. startCell.appendChild(node);
  270. });
  271. // Remove bogus nodes if there is children in the target cell
  272. if (children.length) {
  273. children = tinymce.grep(startCell.childNodes);
  274. count = 0;
  275. each(children, function(node) {
  276. if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1)
  277. startCell.removeChild(node);
  278. });
  279. }
  280. // Remove cell
  281. dom.remove(cell);
  282. }
  283. }
  284. }
  285. // Remove empty rows etc and restore caret location
  286. cleanup();
  287. }
  288. };
  289. function insertRow(before) {
  290. var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
  291. // Find first/last row
  292. each(grid, function(row, y) {
  293. each(row, function(cell, x) {
  294. if (isCellSelected(cell)) {
  295. cell = cell.elm;
  296. rowElm = cell.parentNode;
  297. newRow = cloneNode(rowElm, false);
  298. posY = y;
  299. if (before)
  300. return false;
  301. }
  302. });
  303. if (before)
  304. return !posY;
  305. });
  306. for (x = 0; x < grid[0].length; x++) {
  307. // Cell not found could be because of an invalid table structure
  308. if (!grid[posY][x])
  309. continue;
  310. cell = grid[posY][x].elm;
  311. if (cell != lastCell) {
  312. if (!before) {
  313. rowSpan = getSpanVal(cell, 'rowspan');
  314. if (rowSpan > 1) {
  315. setSpanVal(cell, 'rowSpan', rowSpan + 1);
  316. continue;
  317. }
  318. } else {
  319. // Check if cell above can be expanded
  320. if (posY > 0 && grid[posY - 1][x]) {
  321. otherCell = grid[posY - 1][x].elm;
  322. rowSpan = getSpanVal(otherCell, 'rowSpan');
  323. if (rowSpan > 1) {
  324. setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
  325. continue;
  326. }
  327. }
  328. }
  329. // Insert new cell into new row
  330. newCell = cloneCell(cell);
  331. setSpanVal(newCell, 'colSpan', cell.colSpan);
  332. newRow.appendChild(newCell);
  333. lastCell = cell;
  334. }
  335. }
  336. if (newRow.hasChildNodes()) {
  337. if (!before)
  338. dom.insertAfter(newRow, rowElm);
  339. else
  340. rowElm.parentNode.insertBefore(newRow, rowElm);
  341. }
  342. };
  343. function insertCol(before) {
  344. var posX, lastCell;
  345. // Find first/last column
  346. each(grid, function(row, y) {
  347. each(row, function(cell, x) {
  348. if (isCellSelected(cell)) {
  349. posX = x;
  350. if (before)
  351. return false;
  352. }
  353. });
  354. if (before)
  355. return !posX;
  356. });
  357. each(grid, function(row, y) {
  358. var cell, rowSpan, colSpan;
  359. if (!row[posX])
  360. return;
  361. cell = row[posX].elm;
  362. if (cell != lastCell) {
  363. colSpan = getSpanVal(cell, 'colspan');
  364. rowSpan = getSpanVal(cell, 'rowspan');
  365. if (colSpan == 1) {
  366. if (!before) {
  367. dom.insertAfter(cloneCell(cell), cell);
  368. fillLeftDown(posX, y, rowSpan - 1, colSpan);
  369. } else {
  370. cell.parentNode.insertBefore(cloneCell(cell), cell);
  371. fillLeftDown(posX, y, rowSpan - 1, colSpan);
  372. }
  373. } else
  374. setSpanVal(cell, 'colSpan', cell.colSpan + 1);
  375. lastCell = cell;
  376. }
  377. });
  378. };
  379. function deleteCols() {
  380. var cols = [];
  381. // Get selected column indexes
  382. each(grid, function(row, y) {
  383. each(row, function(cell, x) {
  384. if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
  385. each(grid, function(row) {
  386. var cell = row[x].elm, colSpan;
  387. colSpan = getSpanVal(cell, 'colSpan');
  388. if (colSpan > 1)
  389. setSpanVal(cell, 'colSpan', colSpan - 1);
  390. else
  391. dom.remove(cell);
  392. });
  393. cols.push(x);
  394. }
  395. });
  396. });
  397. cleanup();
  398. };
  399. function deleteRows() {
  400. var rows;
  401. function deleteRow(tr) {
  402. var nextTr, pos, lastCell;
  403. nextTr = dom.getNext(tr, 'tr');
  404. // Move down row spanned cells
  405. each(tr.cells, function(cell) {
  406. var rowSpan = getSpanVal(cell, 'rowSpan');
  407. if (rowSpan > 1) {
  408. setSpanVal(cell, 'rowSpan', rowSpan - 1);
  409. pos = getPos(cell);
  410. fillLeftDown(pos.x, pos.y, 1, 1);
  411. }
  412. });
  413. // Delete cells
  414. pos = getPos(tr.cells[0]);
  415. each(grid[pos.y], function(cell) {
  416. var rowSpan;
  417. cell = cell.elm;
  418. if (cell != lastCell) {
  419. rowSpan = getSpanVal(cell, 'rowSpan');
  420. if (rowSpan <= 1)
  421. dom.remove(cell);
  422. else
  423. setSpanVal(cell, 'rowSpan', rowSpan - 1);
  424. lastCell = cell;
  425. }
  426. });
  427. };
  428. // Get selected rows and move selection out of scope
  429. rows = getSelectedRows();
  430. // Delete all selected rows
  431. each(rows.reverse(), function(tr) {
  432. deleteRow(tr);
  433. });
  434. cleanup();
  435. };
  436. function cutRows() {
  437. var rows = getSelectedRows();
  438. dom.remove(rows);
  439. cleanup();
  440. return rows;
  441. };
  442. function copyRows() {
  443. var rows = getSelectedRows();
  444. each(rows, function(row, i) {
  445. rows[i] = cloneNode(row, true);
  446. });
  447. return rows;
  448. };
  449. function pasteRows(rows, before) {
  450. var selectedRows = getSelectedRows(),
  451. targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
  452. targetCellCount = targetRow.cells.length;
  453. // Calc target cell count
  454. each(grid, function(row) {
  455. var match;
  456. targetCellCount = 0;
  457. each(row, function(cell, x) {
  458. if (cell.real)
  459. targetCellCount += cell.colspan;
  460. if (cell.elm.parentNode == targetRow)
  461. match = 1;
  462. });
  463. if (match)
  464. return false;
  465. });
  466. if (!before)
  467. rows.reverse();
  468. each(rows, function(row) {
  469. var cellCount = row.cells.length, cell;
  470. // Remove col/rowspans
  471. for (i = 0; i < cellCount; i++) {
  472. cell = row.cells[i];
  473. setSpanVal(cell, 'colSpan', 1);
  474. setSpanVal(cell, 'rowSpan', 1);
  475. }
  476. // Needs more cells
  477. for (i = cellCount; i < targetCellCount; i++)
  478. row.appendChild(cloneCell(row.cells[cellCount - 1]));
  479. // Needs less cells
  480. for (i = targetCellCount; i < cellCount; i++)
  481. dom.remove(row.cells[i]);
  482. // Add before/after
  483. if (before)
  484. targetRow.parentNode.insertBefore(row, targetRow);
  485. else
  486. dom.insertAfter(row, targetRow);
  487. });
  488. // Remove current selection
  489. dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
  490. };
  491. function getPos(target) {
  492. var pos;
  493. each(grid, function(row, y) {
  494. each(row, function(cell, x) {
  495. if (cell.elm == target) {
  496. pos = {x : x, y : y};
  497. return false;
  498. }
  499. });
  500. return !pos;
  501. });
  502. return pos;
  503. };
  504. function setStartCell(cell) {
  505. startPos = getPos(cell);
  506. };
  507. function findEndPos() {
  508. var pos, maxX, maxY;
  509. maxX = maxY = 0;
  510. each(grid, function(row, y) {
  511. each(row, function(cell, x) {
  512. var colSpan, rowSpan;
  513. if (isCellSelected(cell)) {
  514. cell = grid[y][x];
  515. if (x > maxX)
  516. maxX = x;
  517. if (y > maxY)
  518. maxY = y;
  519. if (cell.real) {
  520. colSpan = cell.colspan - 1;
  521. rowSpan = cell.rowspan - 1;
  522. if (colSpan) {
  523. if (x + colSpan > maxX)
  524. maxX = x + colSpan;
  525. }
  526. if (rowSpan) {
  527. if (y + rowSpan > maxY)
  528. maxY = y + rowSpan;
  529. }
  530. }
  531. }
  532. });
  533. });
  534. return {x : maxX, y : maxY};
  535. };
  536. function setEndCell(cell) {
  537. var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
  538. endPos = getPos(cell);
  539. if (startPos && endPos) {
  540. // Get start/end positions
  541. startX = Math.min(startPos.x, endPos.x);
  542. startY = Math.min(startPos.y, endPos.y);
  543. endX = Math.max(startPos.x, endPos.x);
  544. endY = Math.max(startPos.y, endPos.y);
  545. // Expand end positon to include spans
  546. maxX = endX;
  547. maxY = endY;
  548. // Expand startX
  549. for (y = startY; y <= maxY; y++) {
  550. cell = grid[y][startX];
  551. if (!cell.real) {
  552. if (startX - (cell.colspan - 1) < startX)
  553. startX -= cell.colspan - 1;
  554. }
  555. }
  556. // Expand startY
  557. for (x = startX; x <= maxX; x++) {
  558. cell = grid[startY][x];
  559. if (!cell.real) {
  560. if (startY - (cell.rowspan - 1) < startY)
  561. startY -= cell.rowspan - 1;
  562. }
  563. }
  564. // Find max X, Y
  565. for (y = startY; y <= endY; y++) {
  566. for (x = startX; x <= endX; x++) {
  567. cell = grid[y][x];
  568. if (cell.real) {
  569. colSpan = cell.colspan - 1;
  570. rowSpan = cell.rowspan - 1;
  571. if (colSpan) {
  572. if (x + colSpan > maxX)
  573. maxX = x + colSpan;
  574. }
  575. if (rowSpan) {
  576. if (y + rowSpan > maxY)
  577. maxY = y + rowSpan;
  578. }
  579. }
  580. }
  581. }
  582. // Remove current selection
  583. dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
  584. // Add new selection
  585. for (y = startY; y <= maxY; y++) {
  586. for (x = startX; x <= maxX; x++) {
  587. if (grid[y][x])
  588. dom.addClass(grid[y][x].elm, 'mceSelected');
  589. }
  590. }
  591. }
  592. };
  593. // Expose to public
  594. tinymce.extend(this, {
  595. deleteTable : deleteTable,
  596. split : split,
  597. merge : merge,
  598. insertRow : insertRow,
  599. insertCol : insertCol,
  600. deleteCols : deleteCols,
  601. deleteRows : deleteRows,
  602. cutRows : cutRows,
  603. copyRows : copyRows,
  604. pasteRows : pasteRows,
  605. getPos : getPos,
  606. setStartCell : setStartCell,
  607. setEndCell : setEndCell
  608. });
  609. };
  610. tinymce.create('tinymce.plugins.TablePlugin', {
  611. init : function(ed, url) {
  612. var winMan, clipboardRows, hasCellSelection = true; // Might be selected cells on reload
  613. function createTableGrid(node) {
  614. var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
  615. if (tblElm)
  616. return new TableGrid(tblElm, ed.dom, selection);
  617. };
  618. function cleanup() {
  619. // Restore selection possibilities
  620. ed.getBody().style.webkitUserSelect = '';
  621. if (hasCellSelection) {
  622. ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
  623. hasCellSelection = false;
  624. }
  625. };
  626. // Register buttons
  627. each([
  628. ['table', 'table.desc', 'mceInsertTable', true],
  629. ['delete_table', 'table.del', 'mceTableDelete'],
  630. ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
  631. ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
  632. ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
  633. ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
  634. ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
  635. ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
  636. ['row_props', 'table.row_desc', 'mceTableRowProps', true],
  637. ['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
  638. ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
  639. ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
  640. ], function(c) {
  641. ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
  642. });
  643. // Select whole table is a table border is clicked
  644. if (!tinymce.isIE) {
  645. ed.onClick.add(function(ed, e) {
  646. e = e.target;
  647. if (e.nodeName === 'TABLE') {
  648. ed.selection.select(e);
  649. ed.nodeChanged();
  650. }
  651. });
  652. }
  653. ed.onPreProcess.add(function(ed, args) {
  654. var nodes, i, node, dom = ed.dom, value;
  655. nodes = dom.select('table', args.node);
  656. i = nodes.length;
  657. while (i--) {
  658. node = nodes[i];
  659. dom.setAttrib(node, 'data-mce-style', '');
  660. if ((value = dom.getAttrib(node, 'width'))) {
  661. dom.setStyle(node, 'width', value);
  662. dom.setAttrib(node, 'width', '');
  663. }
  664. if ((value = dom.getAttrib(node, 'height'))) {
  665. dom.setStyle(node, 'height', value);
  666. dom.setAttrib(node, 'height', '');
  667. }
  668. }
  669. });
  670. // Handle node change updates
  671. ed.onNodeChange.add(function(ed, cm, n) {
  672. var p;
  673. n = ed.selection.getStart();
  674. p = ed.dom.getParent(n, 'td,th,caption');
  675. cm.setActive('table', n.nodeName === 'TABLE' || !!p);
  676. // Disable table tools if we are in caption
  677. if (p && p.nodeName === 'CAPTION')
  678. p = 0;
  679. cm.setDisabled('delete_table', !p);
  680. cm.setDisabled('delete_col', !p);
  681. cm.setDisabled('delete_table', !p);
  682. cm.setDisabled('delete_row', !p);
  683. cm.setDisabled('col_after', !p);
  684. cm.setDisabled('col_before', !p);
  685. cm.setDisabled('row_after', !p);
  686. cm.setDisabled('row_before', !p);
  687. cm.setDisabled('row_props', !p);
  688. cm.setDisabled('cell_props', !p);
  689. cm.setDisabled('split_cells', !p);
  690. cm.setDisabled('merge_cells', !p);
  691. });
  692. ed.onInit.add(function(ed) {
  693. var startTable, startCell, dom = ed.dom, tableGrid;
  694. winMan = ed.windowManager;
  695. // Add cell selection logic
  696. ed.onMouseDown.add(function(ed, e) {
  697. if (e.button != 2) {
  698. cleanup();
  699. startCell = dom.getParent(e.target, 'td,th');
  700. startTable = dom.getParent(startCell, 'table');
  701. }
  702. });
  703. dom.bind(ed.getDoc(), 'mouseover', function(e) {
  704. var sel, table, target = e.target;
  705. if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
  706. table = dom.getParent(target, 'table');
  707. if (table == startTable) {
  708. if (!tableGrid) {
  709. tableGrid = createTableGrid(table);
  710. tableGrid.setStartCell(startCell);
  711. ed.getBody().style.webkitUserSelect = 'none';
  712. }
  713. tableGrid.setEndCell(target);
  714. hasCellSelection = true;
  715. }
  716. // Remove current selection
  717. sel = ed.selection.getSel();
  718. try {
  719. if (sel.removeAllRanges)
  720. sel.removeAllRanges();
  721. else
  722. sel.empty();
  723. } catch (ex) {
  724. // IE9 might throw errors here
  725. }
  726. e.preventDefault();
  727. }
  728. });
  729. ed.onMouseUp.add(function(ed, e) {
  730. var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
  731. // Move selection to startCell
  732. if (startCell) {
  733. if (tableGrid)
  734. ed.getBody().style.webkitUserSelect = '';
  735. function setPoint(node, start) {
  736. var walker = new tinymce.dom.TreeWalker(node, node);
  737. do {
  738. // Text node
  739. if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
  740. if (start)
  741. rng.setStart(node, 0);
  742. else
  743. rng.setEnd(node, node.nodeValue.length);
  744. return;
  745. }
  746. // BR element
  747. if (node.nodeName == 'BR') {
  748. if (start)
  749. rng.setStartBefore(node);
  750. else
  751. rng.setEndBefore(node);
  752. return;
  753. }
  754. } while (node = (start ? walker.next() : walker.prev()));
  755. }
  756. // Try to expand text selection as much as we can only Gecko supports cell selection
  757. selectedCells = dom.select('td.mceSelected,th.mceSelected');
  758. if (selectedCells.length > 0) {
  759. rng = dom.createRng();
  760. node = selectedCells[0];
  761. endNode = selectedCells[selectedCells.length - 1];
  762. rng.setStartBefore(node);
  763. rng.setEndAfter(node);
  764. setPoint(node, 1);
  765. walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
  766. do {
  767. if (node.nodeName == 'TD' || node.nodeName == 'TH') {
  768. if (!dom.hasClass(node, 'mceSelected'))
  769. break;
  770. lastNode = node;
  771. }
  772. } while (node = walker.next());
  773. setPoint(lastNode);
  774. sel.setRng(rng);
  775. }
  776. ed.nodeChanged();
  777. startCell = tableGrid = startTable = null;
  778. }
  779. });
  780. ed.onKeyUp.add(function(ed, e) {
  781. cleanup();
  782. });
  783. ed.onKeyDown.add(function (ed, e) {
  784. fixTableCellSelection(ed);
  785. });
  786. ed.onMouseDown.add(function (ed, e) {
  787. if (e.button != 2) {
  788. fixTableCellSelection(ed);
  789. }
  790. });
  791. function tableCellSelected(ed, rng, n, currentCell) {
  792. // The decision of when a table cell is selected is somewhat involved. The fact that this code is
  793. // required is actually a pointer to the root cause of this bug. A cell is selected when the start
  794. // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases)
  795. // or the parent of the table (in the case of the selection containing the last cell of a table).
  796. var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'),
  797. tableParent, allOfCellSelected, tableCellSelection;
  798. if (table)
  799. tableParent = table.parentNode;
  800. allOfCellSelected =rng.startContainer.nodeType == TEXT_NODE &&
  801. rng.startOffset == 0 &&
  802. rng.endOffset == 0 &&
  803. currentCell &&
  804. (n.nodeName=="TR" || n==tableParent);
  805. tableCellSelection = (n.nodeName=="TD"||n.nodeName=="TH")&& !currentCell;
  806. return allOfCellSelected || tableCellSelection;
  807. // return false;
  808. }
  809. // this nasty hack is here to work around some WebKit selection bugs.
  810. function fixTableCellSelection(ed) {
  811. if (!tinymce.isWebKit)
  812. return;
  813. var rng = ed.selection.getRng();
  814. var n = ed.selection.getNode();
  815. var currentCell = ed.dom.getParent(rng.startContainer, 'TD,TH');
  816. if (!tableCellSelected(ed, rng, n, currentCell))
  817. return;
  818. if (!currentCell) {
  819. currentCell=n;
  820. }
  821. // Get the very last node inside the table cell
  822. var end = currentCell.lastChild;
  823. while (end.lastChild)
  824. end = end.lastChild;
  825. // Select the entire table cell. Nothing outside of the table cell should be selected.
  826. rng.setEnd(end, end.nodeValue.length);
  827. ed.selection.setRng(rng);
  828. }
  829. ed.plugins.table.fixTableCellSelection=fixTableCellSelection;
  830. // Add context menu
  831. if (ed && ed.plugins.contextmenu) {
  832. ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
  833. var sm, se = ed.selection, el = se.getNode() || ed.getBody();
  834. if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
  835. m.removeAll();
  836. if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
  837. m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
  838. m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
  839. m.addSeparator();
  840. }
  841. if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
  842. m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
  843. m.addSeparator();
  844. }
  845. m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
  846. m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
  847. m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
  848. m.addSeparator();
  849. // Cell menu
  850. sm = m.addMenu({title : 'table.cell'});
  851. sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
  852. sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
  853. sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
  854. // Row menu
  855. sm = m.addMenu({title : 'table.row'});
  856. sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
  857. sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
  858. sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
  859. sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
  860. sm.addSeparator();
  861. sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
  862. sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
  863. sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
  864. sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
  865. // Column menu
  866. sm = m.addMenu({title : 'table.col'});
  867. sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
  868. sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
  869. sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
  870. } else
  871. m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
  872. });
  873. }
  874. // Fix to allow navigating up and down in a table in WebKit browsers.
  875. if (tinymce.isWebKit) {
  876. function moveSelection(ed, e) {
  877. var VK = tinymce.VK;
  878. var key = e.keyCode;
  879. function handle(upBool, sourceNode, event) {
  880. var siblingDirection = upBool ? 'previousSibling' : 'nextSibling';
  881. var currentRow = ed.dom.getParent(sourceNode, 'tr');
  882. var siblingRow = currentRow[siblingDirection];
  883. if (siblingRow) {
  884. moveCursorToRow(ed, sourceNode, siblingRow, upBool);
  885. tinymce.dom.Event.cancel(event);
  886. return true;
  887. } else {
  888. var tableNode = ed.dom.getParent(currentRow, 'table');
  889. var middleNode = currentRow.parentNode;
  890. var parentNodeName = middleNode.nodeName.toLowerCase();
  891. if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) {
  892. var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody');
  893. if (targetParent !== null) {
  894. return moveToRowInTarget(upBool, targetParent, sourceNode, event);
  895. }
  896. }
  897. return escapeTable(upBool, currentRow, siblingDirection, tableNode, event);
  898. }
  899. }
  900. function getTargetParent(upBool, topNode, secondNode, nodeName) {
  901. var tbodies = ed.dom.select('>' + nodeName, topNode);
  902. var position = tbodies.indexOf(secondNode);
  903. if (upBool && position === 0 || !upBool && position === tbodies.length - 1) {
  904. return getFirstHeadOrFoot(upBool, topNode);
  905. } else if (position === -1) {
  906. var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1;
  907. return tbodies[topOrBottom];
  908. } else {
  909. return tbodies[position + (upBool ? -1 : 1)];
  910. }
  911. }
  912. function getFirstHeadOrFoot(upBool, parent) {
  913. var tagName = upBool ? 'thead' : 'tfoot';
  914. var headOrFoot = ed.dom.select('>' + tagName, parent);
  915. return headOrFoot.length !== 0 ? headOrFoot[0] : null;
  916. }
  917. function moveToRowInTarget(upBool, targetParent, sourceNode, event) {
  918. var targetRow = getChildForDirection(targetParent, upBool);
  919. targetRow && moveCursorToRow(ed, sourceNode, targetRow, upBool);
  920. tinymce.dom.Event.cancel(event);
  921. return true;
  922. }
  923. function escapeTable(upBool, currentRow, siblingDirection, table, event) {
  924. var tableSibling = table[siblingDirection];
  925. if (tableSibling) {
  926. moveCursorToStartOfElement(tableSibling);
  927. return true;
  928. } else {
  929. var parentCell = ed.dom.getParent(table, 'td,th');
  930. if (parentCell) {
  931. return handle(upBool, parentCell, event);
  932. } else {
  933. var backUpSibling = getChildForDirection(currentRow, !upBool);
  934. moveCursorToStartOfElement(backUpSibling);
  935. return tinymce.dom.Event.cancel(event);
  936. }
  937. }
  938. }
  939. function getChildForDirection(parent, up) {
  940. var child = parent && parent[up ? 'lastChild' : 'firstChild'];
  941. // BR is not a valid table child to return in this case we return the table cell
  942. return child && child.nodeName === 'BR' ? ed.dom.getParent(child, 'td,th') : child;
  943. }
  944. function moveCursorToStartOfElement(n) {
  945. ed.selection.setCursorLocation(n, 0);
  946. }
  947. function isVerticalMovement() {
  948. return key == VK.UP || key == VK.DOWN;
  949. }
  950. function isInTable(ed) {
  951. var node = ed.selection.getNode();
  952. var currentRow = ed.dom.getParent(node, 'tr');
  953. return currentRow !== null;
  954. }
  955. function columnIndex(column) {
  956. var colIndex = 0;
  957. var c = column;
  958. while (c.previousSibling) {
  959. c = c.previousSibling;
  960. colIndex = colIndex + getSpanVal(c, "colspan");
  961. }
  962. return colIndex;
  963. }
  964. function findColumn(rowElement, columnIndex) {
  965. var c = 0;
  966. var r = 0;
  967. each(rowElement.children, function(cell, i) {
  968. c = c + getSpanVal(cell, "colspan");
  969. r = i;
  970. if (c > columnIndex)
  971. return false;
  972. });
  973. return r;
  974. }
  975. function moveCursorToRow(ed, node, row, upBool) {
  976. var srcColumnIndex = columnIndex(ed.dom.getParent(node, 'td,th'));
  977. var tgtColumnIndex = findColumn(row, srcColumnIndex);
  978. var tgtNode = row.childNodes[tgtColumnIndex];
  979. var rowCellTarget = getChildForDirection(tgtNode, upBool);
  980. moveCursorToStartOfElement(rowCellTarget || tgtNode);
  981. }
  982. function shouldFixCaret(preBrowserNode) {
  983. var newNode = ed.selection.getNode();
  984. var newParent = ed.dom.getParent(newNode, 'td,th');
  985. var oldParent = ed.dom.getParent(preBrowserNode, 'td,th');
  986. return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent)
  987. }
  988. function checkSameParentTable(nodeOne, NodeTwo) {
  989. return ed.dom.getParent(nodeOne, 'TABLE') === ed.dom.getParent(NodeTwo, 'TABLE');
  990. }
  991. if (isVerticalMovement() && isInTable(ed)) {
  992. var preBrowserNode = ed.selection.getNode();
  993. setTimeout(function() {
  994. if (shouldFixCaret(preBrowserNode)) {
  995. handle(!e.shiftKey && key === VK.UP, preBrowserNode, e);
  996. }
  997. }, 0);
  998. }
  999. }
  1000. ed.onKeyDown.add(moveSelection);
  1001. }
  1002. // Fixes an issue on Gecko where it's impossible to place the caret behind a table
  1003. // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
  1004. function fixTableCaretPos() {
  1005. var last;
  1006. // Skip empty text nodes form the end
  1007. for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
  1008. if (last && last.nodeName == 'TABLE') {
  1009. if (ed.settings.forced_root_block)
  1010. ed.dom.add(ed.getBody(), ed.settings.forced_root_block, null, tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />');
  1011. else
  1012. ed.dom.add(ed.getBody(), 'br', {'data-mce-bogus': '1'});
  1013. }
  1014. };
  1015. // Fixes an bug where it's impossible to place the caret before a table in Gecko
  1016. // this fix solves it by detecting when the caret is at the beginning of such a table
  1017. // and then manually moves the caret infront of the table
  1018. if (tinymce.isGecko) {
  1019. ed.onKeyDown.add(function(ed, e) {
  1020. var rng, table, dom = ed.dom;
  1021. // On gecko it's not possible to place the caret before a table
  1022. if (e.keyCode == 37 || e.keyCode == 38) {
  1023. rng = ed.selection.getRng();
  1024. table = dom.getParent(rng.startContainer, 'table');
  1025. if (table && ed.getBody().firstChild == table) {
  1026. if (isAtStart(rng, table)) {
  1027. rng = dom.createRng();
  1028. rng.setStartBefore(table);
  1029. rng.setEndBefore(table);
  1030. ed.selection.setRng(rng);
  1031. e.preventDefault();
  1032. }
  1033. }
  1034. }
  1035. });
  1036. }
  1037. ed.onKeyUp.add(fixTableCaretPos);
  1038. ed.onSetContent.add(fixTableCaretPos);
  1039. ed.onVisualAid.add(fixTableCaretPos);
  1040. ed.onPreProcess.add(function(ed, o) {
  1041. var last = o.node.lastChild;
  1042. if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 && (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) && last.previousSibling && last.previousSibling.nodeName == "TABLE") {
  1043. ed.dom.remove(last);
  1044. }
  1045. });
  1046. /**
  1047. * Fixes bug in Gecko where shift-enter in table cell does not place caret on new line
  1048. *
  1049. * Removed: Since the new enter logic seems to fix this one.
  1050. */
  1051. /*
  1052. if (tinymce.isGecko) {
  1053. ed.onKeyDown.add(function(ed, e) {
  1054. if (e.keyCode === tinymce.VK.ENTER && e.shiftKey) {
  1055. var node = ed.selection.getRng().startContainer;
  1056. var tableCell = dom.getParent(node, 'td,th');
  1057. if (tableCell) {
  1058. var zeroSizedNbsp = ed.getDoc().createTextNode("\uFEFF");
  1059. dom.insertAfter(zeroSizedNbsp, node);
  1060. }
  1061. }
  1062. });
  1063. }
  1064. */
  1065. fixTableCaretPos();
  1066. ed.startContent = ed.getContent({format : 'raw'});
  1067. });
  1068. // Register action commands
  1069. each({
  1070. mceTableSplitCells : function(grid) {
  1071. grid.split();
  1072. },
  1073. mceTableMergeCells : function(grid) {
  1074. var rowSpan, colSpan, cell;
  1075. cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
  1076. if (cell) {
  1077. rowSpan = cell.rowSpan;
  1078. colSpan = cell.colSpan;
  1079. }
  1080. if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
  1081. winMan.open({
  1082. url : url + '/merge_cells.htm',
  1083. width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
  1084. height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
  1085. inline : 1
  1086. }, {
  1087. rows : rowSpan,
  1088. cols : colSpan,
  1089. onaction : function(data) {
  1090. grid.merge(cell, data.cols, data.rows);
  1091. },
  1092. plugin_url : url
  1093. });
  1094. } else
  1095. grid.merge();
  1096. },
  1097. mceTableInsertRowBefore : function(grid) {
  1098. grid.insertRow(true);
  1099. },
  1100. mceTableInsertRowAfter : function(grid) {
  1101. grid.insertRow();
  1102. },
  1103. mceTableInsertColBefore : function(grid) {
  1104. grid.insertCol(true);
  1105. },
  1106. mceTableInsertColAfter : function(grid) {
  1107. grid.insertCol();
  1108. },
  1109. mceTableDeleteCol : function(grid) {
  1110. grid.deleteCols();
  1111. },
  1112. mceTableDeleteRow : function(grid) {
  1113. grid.deleteRows();
  1114. },
  1115. mceTableCutRow : function(grid) {
  1116. clipboardRows = grid.cutRows();
  1117. },
  1118. mceTableCopyRow : function(grid) {
  1119. clipboardRows = grid.copyRows();
  1120. },
  1121. mceTablePasteRowBefore : function(grid) {
  1122. grid.pasteRows(clipboardRows, true);
  1123. },
  1124. mceTablePasteRowAfter : function(grid) {
  1125. grid.pasteRows(clipboardRows);
  1126. },
  1127. mceTableDelete : function(grid) {
  1128. grid.deleteTable();
  1129. }
  1130. }, function(func, name) {
  1131. ed.addCommand(name, function() {
  1132. var grid = createTableGrid();
  1133. if (grid) {
  1134. func(grid);
  1135. ed.execCommand('mceRepaint');
  1136. cleanup();
  1137. }
  1138. });
  1139. });
  1140. // Register dialog commands
  1141. each({
  1142. mceInsertTable : function(val) {
  1143. winMan.open({
  1144. url : url + '/table.htm',
  1145. width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
  1146. height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
  1147. inline : 1
  1148. }, {
  1149. plugin_url : url,
  1150. action : val ? val.action : 0
  1151. });
  1152. },
  1153. mceTableRowProps : function() {
  1154. winMan.open({
  1155. url : url + '/row.htm',
  1156. width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
  1157. height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
  1158. inline : 1
  1159. }, {
  1160. plugin_url : url
  1161. });
  1162. },
  1163. mceTableCellProps : function() {
  1164. winMan.open({
  1165. url : url + '/cell.htm',
  1166. width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
  1167. height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
  1168. inline : 1
  1169. }, {
  1170. plugin_url : url
  1171. });
  1172. }
  1173. }, function(func, name) {
  1174. ed.addCommand(name, function(ui, val) {
  1175. func(val);
  1176. });
  1177. });
  1178. }
  1179. });
  1180. // Register plugin
  1181. tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
  1182. })(tinymce);