table.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import Block from '../blots/block';
  2. import Container from '../blots/container';
  3. class TableCell extends Block {
  4. static create(value) {
  5. const node = super.create();
  6. if (value) {
  7. node.setAttribute('data-row', value);
  8. } else {
  9. node.setAttribute('data-row', tableId());
  10. }
  11. return node;
  12. }
  13. static formats(domNode) {
  14. if (domNode.hasAttribute('data-row')) {
  15. return domNode.getAttribute('data-row');
  16. }
  17. return undefined;
  18. }
  19. cellOffset() {
  20. if (this.parent) {
  21. return this.parent.children.indexOf(this);
  22. }
  23. return -1;
  24. }
  25. format(name, value) {
  26. if (name === TableCell.blotName && value) {
  27. this.domNode.setAttribute('data-row', value);
  28. } else {
  29. super.format(name, value);
  30. }
  31. }
  32. row() {
  33. return this.parent;
  34. }
  35. rowOffset() {
  36. if (this.row()) {
  37. return this.row().rowOffset();
  38. }
  39. return -1;
  40. }
  41. table() {
  42. return this.row() && this.row().table();
  43. }
  44. }
  45. TableCell.blotName = 'table';
  46. TableCell.tagName = 'TD';
  47. class TableRow extends Container {
  48. checkMerge() {
  49. if (super.checkMerge() && this.next.children.head != null) {
  50. const thisHead = this.children.head.formats();
  51. const thisTail = this.children.tail.formats();
  52. const nextHead = this.next.children.head.formats();
  53. const nextTail = this.next.children.tail.formats();
  54. return (
  55. thisHead.table === thisTail.table &&
  56. thisHead.table === nextHead.table &&
  57. thisHead.table === nextTail.table
  58. );
  59. }
  60. return false;
  61. }
  62. optimize(...args) {
  63. super.optimize(...args);
  64. this.children.forEach(child => {
  65. if (child.next == null) return;
  66. const childFormats = child.formats();
  67. const nextFormats = child.next.formats();
  68. if (childFormats.table !== nextFormats.table) {
  69. const next = this.splitAfter(child);
  70. if (next) {
  71. next.optimize();
  72. }
  73. // We might be able to merge with prev now
  74. if (this.prev) {
  75. this.prev.optimize();
  76. }
  77. }
  78. });
  79. }
  80. rowOffset() {
  81. if (this.parent) {
  82. return this.parent.children.indexOf(this);
  83. }
  84. return -1;
  85. }
  86. table() {
  87. return this.parent && this.parent.parent;
  88. }
  89. }
  90. TableRow.blotName = 'table-row';
  91. TableRow.tagName = 'TR';
  92. class TableBody extends Container {}
  93. TableBody.blotName = 'table-body';
  94. TableBody.tagName = 'TBODY';
  95. class TableContainer extends Container {
  96. balanceCells() {
  97. const rows = this.descendants(TableRow);
  98. const maxColumns = rows.reduce((max, row) => {
  99. return Math.max(row.children.length, max);
  100. }, 0);
  101. rows.forEach(row => {
  102. new Array(maxColumns - row.children.length).fill(0).forEach(() => {
  103. let value;
  104. if (row.children.head != null) {
  105. value = TableCell.formats(row.children.head.domNode);
  106. }
  107. const blot = this.scroll.create(TableCell.blotName, value);
  108. row.appendChild(blot);
  109. blot.optimize(); // Add break blot
  110. });
  111. });
  112. }
  113. cells(column) {
  114. return this.rows().map(row => row.children.at(column));
  115. }
  116. deleteColumn(index) {
  117. const [body] = this.descendant(TableBody);
  118. if (body == null || body.children.head == null) return;
  119. body.children.forEach(row => {
  120. const cell = row.children.at(index);
  121. if (cell != null) {
  122. cell.remove();
  123. }
  124. });
  125. }
  126. insertColumn(index) {
  127. const [body] = this.descendant(TableBody);
  128. if (body == null || body.children.head == null) return;
  129. body.children.forEach(row => {
  130. const ref = row.children.at(index);
  131. const value = TableCell.formats(row.children.head.domNode);
  132. const cell = this.scroll.create(TableCell.blotName, value);
  133. row.insertBefore(cell, ref);
  134. });
  135. }
  136. insertRow(index) {
  137. const [body] = this.descendant(TableBody);
  138. if (body == null || body.children.head == null) return;
  139. const id = tableId();
  140. const row = this.scroll.create(TableRow.blotName);
  141. body.children.head.children.forEach(() => {
  142. const cell = this.scroll.create(TableCell.blotName, id);
  143. row.appendChild(cell);
  144. });
  145. const ref = body.children.at(index);
  146. body.insertBefore(row, ref);
  147. }
  148. rows() {
  149. const body = this.children.head;
  150. if (body == null) return [];
  151. return body.children.map(row => row);
  152. }
  153. }
  154. TableContainer.blotName = 'table-container';
  155. TableContainer.tagName = 'TABLE';
  156. TableContainer.allowedChildren = [TableBody];
  157. TableBody.requiredContainer = TableContainer;
  158. TableBody.allowedChildren = [TableRow];
  159. TableRow.requiredContainer = TableBody;
  160. TableRow.allowedChildren = [TableCell];
  161. TableCell.requiredContainer = TableRow;
  162. function tableId() {
  163. const id = Math.random()
  164. .toString(36)
  165. .slice(2, 6);
  166. return `row-${id}`;
  167. }
  168. export { TableCell, TableRow, TableBody, TableContainer, tableId };