├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── index.html ├── main.css ├── main.js └── value.js ├── lib ├── TablePosition.js ├── changes │ ├── insertColumn.js │ ├── insertRow.js │ ├── insertTable.js │ ├── insertTableByKey.js │ ├── insertTableByPath.js │ ├── moveSelection.js │ ├── moveSelectionBy.js │ ├── removeColumn.js │ ├── removeRow.js │ ├── removeTable.js │ └── toggleHeaders.js ├── createCell.js ├── createRow.js ├── createTable.js ├── defaultRenderers.js ├── defaultSerializers.js ├── index.js ├── makeSchema.js ├── onTab.js └── onUpDown.js ├── package-lock.json ├── package.json ├── tests ├── all.js ├── always-blocks-within-cells │ ├── expected.js │ ├── input.js │ └── transform.js ├── basicTableInput.js ├── headerTableInput.js ├── hyperscript.js ├── insert-column-at │ ├── expected.js │ ├── input.js │ └── transform.js ├── insert-column │ ├── expected.js │ ├── input.js │ └── transform.js ├── insert-row │ ├── expected.js │ ├── input.js │ └── transform.js ├── insert-table-by-key │ ├── expected.js │ ├── input.js │ └── transform.js ├── insert-table-by-path │ ├── expected.js │ ├── input.js │ └── transform.js ├── insert-table │ ├── expected.js │ ├── input.js │ └── transform.js ├── move-table-selection-by │ ├── expected.js │ ├── input.js │ └── transform.js ├── move-table-selection │ ├── expected.js │ ├── input.js │ └── transform.js ├── query-isSelectionInTable │ ├── expected.js │ ├── input.js │ └── transform.js ├── remove-column-first │ ├── expected.js │ ├── input.js │ └── transform.js ├── remove-column-last-one │ ├── expected.js │ ├── input.js │ └── transform.js ├── remove-column │ ├── expected.js │ ├── input.js │ └── transform.js ├── remove-row-first │ ├── expected.js │ ├── input.js │ └── transform.js ├── remove-row-last-one │ ├── expected.js │ ├── input.js │ └── transform.js ├── remove-row │ ├── expected.js │ ├── input.js │ └── transform.js ├── remove-table │ ├── expected.js │ ├── input.js │ └── transform.js ├── schema-no-blocks-within-rows │ ├── expected.js │ ├── input.js │ └── transform.js ├── schema-no-blocks-within-table │ ├── expected.js │ ├── input.js │ └── transform.js ├── schema-rows-cells-within-tables │ ├── expected.js │ ├── input.js │ └── transform.js ├── schema-rows-require-same-columns-counts │ ├── expected.js │ ├── input.js │ └── transform.js ├── schema-standard-table │ ├── expected.js │ ├── input.js │ └── transform.js ├── schema-tables-contain-rows │ ├── expected.js │ ├── input.js │ └── transform.js ├── undo-insert-column │ ├── expected.js │ ├── input.js │ └── transform.js ├── undo-insert-row │ ├── expected.js │ ├── input.js │ └── transform.js ├── undo-insert-table │ ├── expected.js │ ├── input.js │ └── transform.js ├── undo-remove-column │ ├── expected.js │ ├── input.js │ └── transform.js ├── undo-remove-row │ ├── expected.js │ ├── input.js │ └── transform.js └── undo-remove-table │ ├── expected.js │ ├── input.js │ └── transform.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react" 5 | ], 6 | "plugins": [ 7 | "transform-class-properties" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gitbook", 3 | "env": { 4 | "mocha": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | dist 40 | example/bundle.js 41 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib 2 | !dist 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [0.9.6] - 2019-7-9 7 | 8 | - added a useful query (`editor.getTablePosition()`) and documented it in the README. 9 | 10 | ## [0.9.5] - 2019-7-8 11 | 12 | - now exports a function to obtain default serializer rules; see comment https://github.com/jasonphillips/slate-deep-table/issues/18#issuecomment-509439219 13 | 14 | ## [0.9.1] - 2019-7-8 15 | 16 | - compatibility with Slate 0.46.x 17 | 18 | ## [0.8.0] - 2018-11-2 19 | 20 | - compatibility with slate 0.43.x (thanks @eugene-preply ) 21 | 22 | ### Breaking 23 | - Slate now hoists exported commands and queries to functions callable directly on the Editor instance. So instead of invoking e.g. `plugin.utils.insertTable()` you will now call it directly on your editor, eg. `editor.insertTable()`. See the tests for more examples. 24 | - added `isSelectionInTable()` as an exported query 25 | - renamed some commands for more specificity to ensure they don't collide with other plugins: 26 | - `moveSelection()` -> `moveTableSelection()` 27 | - `moveSelectionBy()` -> `moveTableSelectionBy()` 28 | - `toggleHeaders()` -> `toggleTableHeaders()` 29 | 30 | ## [0.7.0] - 2018-10-7 31 | 32 | - compatibility with slate 0.41.x (thanks @marcjps) 33 | 34 | ## [0.6.0] - 2017-01-24 35 | 36 | - compatibility with slate 0.32.x 37 | - many updates to usage as a result; see the slate changelog, the updated README, and the example 38 | 39 | ## [0.5.1] - 2017-08-16 40 | 41 | - initial working fork of prior project 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slate-deep-table 2 | 3 | [![npm version](https://badge.fury.io/js/slate-deep-table.svg)](https://badge.fury.io/js/slate-deep-table) 4 | [![Linux Build Status](https://travis-ci.org/jasonphillips/slate-deep-table.png?branch=master)](https://travis-ci.org/jasonphillips/slate-deep-table) 5 | 6 | A Slate plugin to handle tables with nested block content. Forked from the excellent [slate-edit-table](https://github.com/GitbookIO/slate-edit-table) implementation, but retooled to work with deep content. 7 | 8 | Demo: https://jasonphillips.github.io/slate-deep-table/ 9 | 10 | ### Install 11 | 12 | ``` 13 | npm install slate-deep-table 14 | ``` 15 | 16 | ### Features 17 | 18 | - Pressing Up and Down, move the cursor to next/previous row 19 | - Pressing Tab, move the select to next cell 20 | - Pressing Shift+Tab, move the select to previous cell 21 | - Permits nested block content within table cells 22 | - Optionally create headerless tables 23 | 24 | ### Compatibility 25 | 26 | Slate is a fast-moving library, so check the CHANGELOG for information on the currently supported version. 27 | 28 | ### Simple Usage 29 | 30 | ```js 31 | import DeepTable from 'slate-deep-table' 32 | 33 | const plugins = [ 34 | DeepTable({ /* options object here; see below */ }) 35 | ] 36 | 37 | // now instantiate your Slate Editor with these plugins, according to slate documentation 38 | ``` 39 | 40 | #### Options 41 | 42 | - `[typeTable: String]` — type for table `default: 'table'` 43 | - `[typeRow: String]` — type for the rows `default: 'table_row'` 44 | - `[typeCell: String]` — type for the cells `default: 'table_cell'` 45 | - `[typeContent: String]` — type for the default blocks within cells `default: 'paragraph'` 46 | 47 | ### Queries and Commands 48 | 49 | `slate-deep-table` exports queries and commands that you can invoke on your `editor` instance: 50 | 51 | ```js 52 | // anywhere where 'editor' is passed as an argument, or using the react Component's ref, 53 | // you may directly invoke any of the exported functions below, e.g: 54 | const inATable = editor.isSelectionInTable(); 55 | 56 | if (!inATable) { 57 | editor.insertTable(); 58 | } 59 | ``` 60 | 61 | Check `example/main.js` for usage in a typical context. 62 | 63 | #### `query isSelectionInTable()` 64 | 65 | `editor.isSelectionInTable() => Boolean` 66 | 67 | Return true if current cursor position is inside a table. 68 | 69 | #### `query getTablePosition()` 70 | 71 | `editor.getTablePosition() => null || TablePosition` 72 | 73 | Returns null if cursor is not in a table, else returns an object you can use to query the current cell and row location: 74 | 75 | ```js 76 | const position = editor.getTablePosition() 77 | 78 | position.getRowIndex() // returns row id (0-indexed) 79 | position.getColumnIndex() // return column index (0-indexed) 80 | position.getWidth() // returns count of columns 81 | position.getHeight() // returns count of rows 82 | ``` 83 | 84 | #### `command insertTable()` 85 | 86 | `editor.insertTable(columns: Number?, rows: Number?) => Editor` 87 | 88 | Insert a new empty table. 89 | 90 | #### `command insertTableByKey/Path()` 91 | 92 | `editor.insertTableByKey(key: String, index: Number?, columns: Number?, rows: Number?) => Editor` 93 | 94 | `editor.insertTableByPath(path: List, index: Number?, columns: Number?, rows: Number?) => Editor` 95 | 96 | Insert a new empty table by Key/Path, follows `insertNodeByKey/Path()`'s insertion procedure. Index defaults to 0 if empty. 97 | 98 | #### `command insertRow()` 99 | 100 | `editor.insertRow(at: Number?) => Editor` 101 | 102 | Insert a new row after the current one or at the specific index (`at`). 103 | 104 | #### `command insertColumn()` 105 | 106 | `editor.insertColumn(at: Number?) => Editor` 107 | 108 | Insert a new column after the current one or at the specific index (`at`). 109 | 110 | #### `command removeTable()` 111 | 112 | `editor.removeTable() => Editor` 113 | 114 | Remove current table. 115 | 116 | #### `command removeRow()` 117 | 118 | `editor.removeRow(at: Number?) => Editor` 119 | 120 | Remove current row or the one at a specific index (`at`). 121 | 122 | #### `command removeColumn()` 123 | 124 | `editor.removeColumn(at: Number?) => Editor` 125 | 126 | Remove current column or the one at a specific index (`at`). 127 | 128 | #### `command moveTableSelection()` 129 | 130 | `editor.moveTableSelection(column: Number, row: Number) => Editor` 131 | 132 | Move the selection to a specific position in the table. 133 | 134 | #### `command moveTableSelectionBy()` 135 | 136 | `editor.moveTableSelectionBy(column: Number, row: Number) => Editor` 137 | 138 | Move the selection by the given amount of columns and rows. 139 | 140 | #### `command toggleTableHeaders()` 141 | 142 | `editor.toggleTableHeaders() => Editor` 143 | 144 | Toggles whether the table will render the first row as a header row (within a thead) or as a regular row. 145 | 146 | 147 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Slate • Table Edition 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/main.css: -------------------------------------------------------------------------------- 1 | #example { 2 | width: 100%; 3 | max-width: 600px; 4 | margin: 20px auto; 5 | padding-top: 2em; 6 | } 7 | 8 | #example .buttons { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | right: 100%; 13 | width: 100%; 14 | min-height: 1.5em; 15 | text-align: center; 16 | background-color: #555; 17 | padding: 5px; 18 | z-index: 100; 19 | } 20 | 21 | #example table { 22 | width: 100%; 23 | border-collapse: collapse; 24 | border-top: 1px solid black; 25 | } 26 | 27 | #example table tr { 28 | border: none; 29 | border-bottom: 1px solid black; 30 | border-right: 1px solid black; 31 | } 32 | 33 | #example table thead tr { 34 | background: #f5f5f5; 35 | font-weight: bold; 36 | } 37 | 38 | #example table td { 39 | border: 1px solid black; 40 | border-top: none; 41 | border-bottom: none; 42 | border-right: none; 43 | padding: .5em; 44 | position: relative; 45 | } 46 | 47 | /* for nested tables */ 48 | #example table td > table { 49 | width: 100%; 50 | } 51 | 52 | td > p:first-child { 53 | margin-top: 0; 54 | } 55 | 56 | td > p:last-child { 57 | margin-bottom: 0; 58 | } -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ReactDOM = require('react-dom'); 3 | const Slate = require('slate'); 4 | const SlateReact = require('slate-react'); 5 | const { Editor } = SlateReact; 6 | const PluginDeepTable = require('../lib/'); 7 | const initialValue = require('./value.js'); 8 | 9 | const plugins = [ 10 | PluginDeepTable() 11 | ]; 12 | 13 | const renderBlock = (props, editor, next) => { 14 | switch (props.node.type) { 15 | case 'paragraph': return

{props.children}

; 16 | case 'heading': return

{props.children}

; 17 | case 'subheading': return

{props.children}

; 18 | } 19 | return next(); 20 | }; 21 | 22 | 23 | class Example extends React.Component { 24 | constructor (props) { 25 | super(props); 26 | this.state = { value: initialValue }; 27 | this.editor = null; 28 | } 29 | 30 | onChange = ({ value }) => { 31 | this.setState({ value }); 32 | } 33 | 34 | onInsertTable = () => { 35 | this.onChange( 36 | this.editor.insertTable() 37 | ); 38 | } 39 | 40 | onInsertColumn = () => { 41 | this.onChange( 42 | this.editor.insertColumn() 43 | ); 44 | } 45 | 46 | onInsertRow = () => { 47 | this.onChange( 48 | this.editor.insertRow() 49 | ); 50 | } 51 | 52 | onRemoveColumn = () => { 53 | this.onChange( 54 | this.editor.removeColumn() 55 | ); 56 | } 57 | 58 | onRemoveRow= () => { 59 | this.onChange( 60 | this.editor.removeRow() 61 | ); 62 | } 63 | 64 | onRemoveTable = () => { 65 | this.onChange( 66 | this.editor.removeTable() 67 | ); 68 | } 69 | 70 | onToggleHeaders = () => { 71 | this.onChange( 72 | this.editor.toggleTableHeaders() 73 | ); 74 | } 75 | 76 | renderNormalToolbar = () => { 77 | return ( 78 |
79 | 80 |
81 | ); 82 | } 83 | 84 | renderTableToolbar = () => { 85 | return ( 86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | ); 96 | } 97 | 98 | render() { 99 | const { value } = this.state; 100 | const isTable = this.editor && this.editor.isSelectionInTable(value); 101 | 102 | return ( 103 |
104 | {isTable? this.renderTableToolbar() : this.renderNormalToolbar()} 105 | this.editor = editor} 110 | onChange={this.onChange} 111 | renderBlock={renderBlock} 112 | /> 113 |
114 | ); 115 | } 116 | } 117 | 118 | ReactDOM.render( 119 | , 120 | document.getElementById('example') 121 | ); 122 | -------------------------------------------------------------------------------- /example/value.js: -------------------------------------------------------------------------------- 1 | 2 | /** @jsx h */ 3 | 4 | import h from '../tests/hyperscript'; 5 | 6 | const value = ( 7 | 8 | 9 |

Slate with Deep Tables

10 |

The page demonstrates the slate-deep-table plugin.

11 | 12 |

Basic Tables

13 |

Simple tables can still contain block-level content. This page is intentionally not using other plugins, but you could easily embed lists, images, or any other block-level content in cells.

14 | 15 | 16 | 17 | 20 | 23 | 24 | 25 | 28 | 32 | 33 | 34 | 37 | 42 | 43 |
18 |

Week

19 |
21 |

Assignments

22 |
26 |

August 21st

27 |
29 |

Introductory reading

30 |

Pre-test One

31 |
35 |

August 28th

36 |
38 |

Discussion Question One

39 |

Find partners for group project

40 |

Begin Chapters 1-3

41 |
44 | 45 |

Use Tab and Shift+Tab to move from cell to cells. Press Up/Down to navigate the rows.

46 | 47 |

Headerless

48 |

Headerless tables can be used for aligning columns. One-row tables are always headerless, but you can use the Toggle Headers button to hide or show headers on any table.

49 | 50 | 51 | 52 | 56 | 60 | 61 | 62 | 66 | 70 | 71 |
53 |

Gamma Quadrant

54 |

- dominion, bad things

55 |
57 |

Delta Quadrant

58 |

- flying cubes, space monocles with laser pointers

59 |
63 |

Alpha Quadrant

64 |

- somewhat self-aggrandizing name

65 |
67 |

Beta Quadrant

68 |

- Various empires

69 |
72 | 73 |

Nested Tables

74 |

If you so desire, you may even embed tables within tables.

75 | 76 | 77 | 78 | 81 | 84 | 87 | 88 | 89 | 92 | 95 | 123 | 124 | 125 | 128 | 131 | 151 | 152 |
79 |

vero

80 |
82 |

odio

83 |
85 |

blanditiis

86 |
90 |

cupiditate

91 |
93 |

maxime placeat facere

94 |
96 | 97 | 98 | 101 | 104 | 105 | 106 | 109 | 112 | 113 | 114 | 117 | 120 | 121 |
99 |

cupiditate

100 |
102 |

expedita

103 |
107 |

23

108 |
110 |

2.98

111 |
115 |

888.2

116 |
118 |

0

119 |
122 |
126 |

Itaque

127 |
129 |

rerum hic tenetur

130 |
132 | 133 | 134 | 137 | 140 | 141 | 142 | 145 | 148 | 149 |
135 |

cupiditate

136 |
138 |

expedita

139 |
143 |

2003

144 |
146 |

33

147 |
150 |
153 |
154 |
155 | ) 156 | 157 | module.exports = value; -------------------------------------------------------------------------------- /lib/TablePosition.js: -------------------------------------------------------------------------------- 1 | const Immutable = require('immutable'); 2 | 3 | const DEFAULTS = { 4 | // Block containr for the table 5 | table: null, 6 | 7 | // Block for current row 8 | row: null, 9 | 10 | // Block for current cell 11 | cell: null 12 | }; 13 | 14 | class TablePosition extends Immutable.Record(DEFAULTS) { 15 | 16 | /** 17 | * Get count of columns 18 | * @return {Number} 19 | */ 20 | getWidth() { 21 | const { table } = this; 22 | const rows = table.nodes; 23 | const cells = rows.get(0).nodes; 24 | 25 | return cells.size; 26 | } 27 | 28 | /** 29 | * Get count of rows 30 | * @return {Number} 31 | */ 32 | getHeight() { 33 | const { table } = this; 34 | const rows = table.nodes; 35 | 36 | return rows.size; 37 | } 38 | 39 | /** 40 | * Get index of current row in the table. 41 | * @return {Number} 42 | */ 43 | getRowIndex() { 44 | const { table, row } = this; 45 | const rows = table.nodes; 46 | 47 | return rows.findIndex(x => x === row); 48 | } 49 | 50 | /** 51 | * Get index of current column in the row. 52 | * @return {Number} 53 | */ 54 | getColumnIndex() { 55 | const { row, cell } = this; 56 | const cells = row.nodes; 57 | 58 | return cells.findIndex(x => x === cell); 59 | } 60 | 61 | /** 62 | * @return {Boolean} True if on first cell of the table 63 | */ 64 | isFirstCell() { 65 | return this.isFirstRow() && this.isFirstColumn(); 66 | } 67 | 68 | /** 69 | * @return {Boolean} True if on last cell of the table 70 | */ 71 | isLastCell() { 72 | return this.isLastRow() && this.isLastColumn(); 73 | } 74 | 75 | /** 76 | * @return {Boolean} True if on first row 77 | */ 78 | isFirstRow() { 79 | return this.getRowIndex() === 0; 80 | } 81 | 82 | /** 83 | * @return {Boolean} True if on last row 84 | */ 85 | isLastRow() { 86 | return this.getRowIndex() === this.getHeight() - 1; 87 | } 88 | 89 | /** 90 | * @return {Boolean} True if on first column 91 | */ 92 | isFirstColumn() { 93 | return this.getColumnIndex() === 0; 94 | } 95 | 96 | /** 97 | * @return {Boolean} True if on last column 98 | */ 99 | isLastColumn() { 100 | return this.getColumnIndex() === this.getWidth() - 1; 101 | } 102 | 103 | /** 104 | * Create a new instance of a TablePosition from a Slate value 105 | * and a current cell or node within a cell. 106 | * 107 | * @param {Slate.Value} value 108 | * @param {Slate.Block} startBlock 109 | * @param {Object} opts 110 | * @return {TablePosition} 111 | */ 112 | static create(value, startBlock, opts) { 113 | const cell = value.document.getClosest(startBlock.key, (p) => (p.type === opts.typeCell)); 114 | const row = value.document.getClosest(startBlock.key, (p) => (p.type === opts.typeRow)); 115 | const table = value.document.getClosest(startBlock.key, (p) => (p.type === opts.typeTable)); 116 | 117 | return new TablePosition({ 118 | table, 119 | row, 120 | cell 121 | }); 122 | } 123 | 124 | /** 125 | * Check to see if current node is within a cell 126 | * 127 | * @param {Slate.Value} value 128 | * @param {Slate.Block} startBlock 129 | * @param {Object} opts 130 | * @return {Boolean} 131 | */ 132 | static isInCell(value, startBlock, opts) { 133 | return ( 134 | startBlock.type === opts.typeCell || 135 | value.document.getClosest(startBlock.key, (p) => (p.type === opts.typeCell)) 136 | ) ? true : false; 137 | } 138 | } 139 | 140 | module.exports = TablePosition; 141 | -------------------------------------------------------------------------------- /lib/changes/insertColumn.js: -------------------------------------------------------------------------------- 1 | const { List } = require('immutable'); 2 | const TablePosition = require('../TablePosition'); 3 | const moveSelection = require('./moveSelection'); 4 | const createCell = require('../createCell'); 5 | 6 | /** 7 | * Insert a new column in current table 8 | * 9 | * @param {Object} opts 10 | * @param {Slate.Editor} editor 11 | * @param {Number} at 12 | * @return {Slate.Editor} 13 | */ 14 | function insertColumn(opts, editor, at) { 15 | const { value } = editor; 16 | const { startBlock } = value; 17 | 18 | const pos = TablePosition.create(value, startBlock, opts); 19 | const { table } = pos; 20 | 21 | if (typeof at === 'undefined') { 22 | at = pos.getColumnIndex() + 1; 23 | } 24 | 25 | // Insert the new cell 26 | editor.withoutNormalizing( () => { 27 | table.nodes.forEach((row) => { 28 | const newCell = createCell(opts); 29 | editor.insertNodeByKey(row.key, at, newCell); 30 | }); 31 | }); 32 | 33 | // Update the selection (not doing can break the undo) 34 | return moveSelection(opts, editor, pos.getColumnIndex() + 1, pos.getRowIndex()); 35 | } 36 | 37 | module.exports = insertColumn; 38 | -------------------------------------------------------------------------------- /lib/changes/insertRow.js: -------------------------------------------------------------------------------- 1 | const createRow = require('../createRow'); 2 | const TablePosition = require('../TablePosition'); 3 | 4 | /** 5 | * Insert a new row in current table 6 | * 7 | * @param {Object} opts 8 | * @param {Slate.Editor} editor 9 | * @param {Number} at 10 | * @param {Function} textGetter 11 | * @return {Slate.Editor} 12 | */ 13 | function insertRow(opts, editor, at, textGetter) { 14 | const { value } = editor; 15 | const { startBlock } = value; 16 | 17 | const pos = TablePosition.create(value, startBlock, opts); 18 | const { table } = pos; 19 | 20 | // Create a new row with the right count of cells 21 | const firstRow = table.nodes.get(0); 22 | const newRow = createRow(opts, firstRow.nodes.size, textGetter); 23 | 24 | if (typeof at === 'undefined') { 25 | at = pos.getRowIndex() + 1; 26 | } 27 | 28 | return editor 29 | 30 | .insertNodeByKey(table.key, at, newRow) 31 | 32 | .moveToEndOfNode( 33 | newRow.nodes.get(pos.getColumnIndex()) 34 | ); 35 | } 36 | 37 | module.exports = insertRow; 38 | -------------------------------------------------------------------------------- /lib/changes/insertTable.js: -------------------------------------------------------------------------------- 1 | const createTable = require('../createTable'); 2 | 3 | /** 4 | * Insert a new table 5 | * 6 | * @param {Object} opts 7 | * @param {Slate.Editor} editor 8 | * @param {Number} columns 9 | * @param {Number} rows 10 | * @return {Slate.Editor} 11 | */ 12 | function insertTable(opts, editor, columns = 2, rows = 2) { 13 | const { value } = editor; 14 | 15 | if (!value.selection.start.key) return false; 16 | 17 | // Create the table node 18 | const fillWithEmptyText = (x, y) => ''; 19 | const table = createTable(opts, columns, rows, fillWithEmptyText); 20 | 21 | const done = editor 22 | .insertBlock(table); 23 | return done; 24 | } 25 | 26 | module.exports = insertTable; 27 | -------------------------------------------------------------------------------- /lib/changes/insertTableByKey.js: -------------------------------------------------------------------------------- 1 | const createTable = require('../createTable'); 2 | 3 | /** 4 | * Insert a new table by key, if index is left empty it defaults to 0 5 | * 6 | * @param {Object} opts 7 | * @param {Slate.Editor} editor 8 | * @param {String} key 9 | * @param {Number} index 10 | * @param {Number} columns 11 | * @param {Number} rows 12 | * @return {Slate.Editor} 13 | */ 14 | function insertTableByKey(opts, editor, key, index = 0, columns = 2, rows = 2) { 15 | // Create the table node 16 | const fillWithEmptyText = (x, y) => ''; 17 | const table = createTable(opts, columns, rows, fillWithEmptyText); 18 | 19 | const done = editor.insertNodeByKey(key, index, table); 20 | return done; 21 | } 22 | 23 | module.exports = insertTableByKey; 24 | -------------------------------------------------------------------------------- /lib/changes/insertTableByPath.js: -------------------------------------------------------------------------------- 1 | const createTable = require('../createTable'); 2 | 3 | /** 4 | * Insert a new table by path, if index is left empty it defaults to 0 5 | * 6 | * @param {Object} opts 7 | * @param {Slate.Editor} editor 8 | * @param {List} path 9 | * @param {Number} index 10 | * @param {Number} columns 11 | * @param {Number} rows 12 | * @return {Slate.Editor} 13 | */ 14 | function insertTableByPath(opts, editor, path, index = 0, columns = 2, rows = 2) { 15 | // Create the table node 16 | const fillWithEmptyText = (x, y) => ''; 17 | const table = createTable(opts, columns, rows, fillWithEmptyText); 18 | 19 | const done = editor.insertNodeByPath(path, index, table); 20 | return done; 21 | } 22 | 23 | module.exports = insertTableByPath; 24 | -------------------------------------------------------------------------------- /lib/changes/moveSelection.js: -------------------------------------------------------------------------------- 1 | const TablePosition = require('../TablePosition'); 2 | 3 | /** 4 | * Move selection to {x,y} 5 | * 6 | * @param {Object} opts 7 | * @param {Slate.Editor} editor 8 | * @param {Number} x 9 | * @param {Number} y 10 | * @return {Slate.Editor} 11 | */ 12 | function moveSelection(opts, editor, x, y) { 13 | const { value } = editor; 14 | let { startBlock } = value; 15 | let startOffset = value.selection.start.offset; 16 | 17 | if (!TablePosition.isInCell(value, startBlock, opts)) { 18 | throw new Error('moveSelection can only be applied in a cell'); 19 | } 20 | 21 | const pos = TablePosition.create(value, startBlock, opts); 22 | const { table } = pos; 23 | 24 | const row = table.nodes.get(y); 25 | const cell = row.nodes.get(x); 26 | 27 | // Calculate new offset 28 | const cellTextLength = cell.text.length; 29 | if (startOffset > cellTextLength) { 30 | startOffset = cellTextLength; 31 | } 32 | 33 | return editor.moveTo(cell.getFirstText().key, startOffset); 34 | } 35 | 36 | module.exports = moveSelection; 37 | -------------------------------------------------------------------------------- /lib/changes/moveSelectionBy.js: -------------------------------------------------------------------------------- 1 | const TablePosition = require('../TablePosition'); 2 | const moveSelection = require('./moveSelection'); 3 | 4 | /** 5 | * Move selection by a {x,y} relative movement 6 | * 7 | * @param {Object} opts 8 | * @param {Slate.Editor} editor 9 | * @param {Number} x Move horizontally by x 10 | * @param {Number} y Move vertically by y 11 | * @return {Slate.Editor} 12 | */ 13 | function moveSelectionBy(opts, editor, x, y) { 14 | const { value } = editor; 15 | const { startBlock } = value; 16 | 17 | if (!TablePosition.isInCell(value, startBlock, opts)) { 18 | throw new Error('moveSelectionBy can only be applied in a cell'); 19 | } 20 | 21 | const pos = TablePosition.create(value, startBlock, opts); 22 | const rowIndex = pos.getRowIndex(); 23 | const colIndex = pos.getColumnIndex(); 24 | const width = pos.getWidth(); 25 | const height = pos.getHeight(); 26 | 27 | const [absX, absY] = normPos(x + colIndex, y + rowIndex, width, height); 28 | 29 | if (absX === -1) { 30 | // Out of table 31 | return editor; 32 | } 33 | 34 | return moveSelection(opts, editor, absX, absY); 35 | } 36 | 37 | /** 38 | * Normalize position in a table. If x is out of the row, update y accordingly 39 | * @param {Number} x 40 | * @param {Number} y 41 | * @param {Number} width 42 | * @param {Number} height 43 | * @return {Array} [-1, -1] if the new selection is out of table 44 | */ 45 | function normPos(x, y, width, height) { 46 | if (x < 0) { 47 | x = (width - 1); 48 | y--; 49 | } 50 | 51 | if (y < 0) { 52 | return [-1, -1]; 53 | } 54 | 55 | if (x >= width) { 56 | x = 0; 57 | y++; 58 | } 59 | 60 | if (y >= height) { 61 | return [-1, -1]; 62 | } 63 | 64 | return [x, y]; 65 | } 66 | 67 | module.exports = moveSelectionBy; 68 | -------------------------------------------------------------------------------- /lib/changes/removeColumn.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | const { Text } = Slate; 3 | const { List } = require('immutable'); 4 | const TablePosition = require('../TablePosition'); 5 | 6 | /** 7 | * Delete current column in a table 8 | * 9 | * @param {Object} opts 10 | * @param {Slate.Editor} editor 11 | * @param {Number} at 12 | * @return {Slate.Editor} 13 | */ 14 | function removeColumn(opts, editor, at) { 15 | const { value } = editor; 16 | const { startBlock } = value; 17 | 18 | const pos = TablePosition.create(value, startBlock, opts); 19 | const { table } = pos; 20 | 21 | if (typeof at === 'undefined') { 22 | at = pos.getColumnIndex(); 23 | } 24 | 25 | const rows = table.nodes; 26 | 27 | // Remove the cell from every row 28 | if (pos.getWidth() > 1) { 29 | editor.withoutNormalizing( () => { 30 | rows.forEach((row) => { 31 | const cell = row.nodes.get(at); 32 | editor.removeNodeByKey(cell.key); 33 | }); 34 | }); 35 | } 36 | // If last column, clear text in cells instead 37 | else { 38 | editor.withoutNormalizing( () => { 39 | rows.forEach((row) => { 40 | row.nodes.forEach((cell) => { 41 | // remove all children of cells 42 | // the schema will create an empty child content block in each cell 43 | cell.nodes.forEach((node) => { 44 | editor.removeNodeByKey(node.key); 45 | }); 46 | }); 47 | }); 48 | }); 49 | } 50 | 51 | // Replace the table 52 | return editor; 53 | } 54 | 55 | module.exports = removeColumn; 56 | -------------------------------------------------------------------------------- /lib/changes/removeRow.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | const { Text } = Slate; 3 | const TablePosition = require('../TablePosition'); 4 | 5 | /** 6 | * Remove current row in a table. Clear it if last remaining row 7 | * 8 | * @param {Object} opts 9 | * @param {Slate.Editor} editor 10 | * @param {Number} at 11 | * @return {Slate.Editor} 12 | */ 13 | function removeRow(opts, editor, at) { 14 | const { value } = editor; 15 | const { startBlock } = value; 16 | 17 | const pos = TablePosition.create(value, startBlock, opts); 18 | const { table } = pos; 19 | 20 | if (typeof at === 'undefined') { 21 | at = pos.getRowIndex(); 22 | } 23 | 24 | const row = table.nodes.get(at); 25 | // Update table by removing the row 26 | if (pos.getHeight() > 1) { 27 | editor.removeNodeByKey(row.key); 28 | } 29 | // If last remaining row, clear it instead 30 | else { 31 | editor.withoutNormalizing( () => { 32 | row.nodes.forEach((cell) => { 33 | // remove all children of cells 34 | // the schema will create an empty child content block in each cell 35 | cell.nodes.forEach((node) => { 36 | editor.removeNodeByKey(node.key); 37 | }); 38 | }); 39 | }); 40 | } 41 | 42 | return editor; 43 | } 44 | 45 | module.exports = removeRow; 46 | -------------------------------------------------------------------------------- /lib/changes/removeTable.js: -------------------------------------------------------------------------------- 1 | const TablePosition = require('../TablePosition'); 2 | 3 | /** 4 | * Delete the whole table 5 | * 6 | * @param {Object} opts 7 | * @param {Slate.Editor} editor 8 | * @param {Number} at 9 | * @return {Slate.Editor} 10 | */ 11 | function removeTable(opts, editor, at) { 12 | const { value } = editor; 13 | const { startBlock } = value; 14 | 15 | const pos = TablePosition.create(value, startBlock, opts); 16 | const { table } = pos; 17 | 18 | return editor 19 | .deselect() 20 | .removeNodeByKey(table.key); 21 | } 22 | 23 | module.exports = removeTable; 24 | -------------------------------------------------------------------------------- /lib/changes/toggleHeaders.js: -------------------------------------------------------------------------------- 1 | const TablePosition = require('../TablePosition'); 2 | 3 | /** 4 | * Toggles table headers on / off 5 | * 6 | * @param {Object} opts 7 | * @param {Slate.Editor} editor 8 | * @return {Slate.Editor} 9 | */ 10 | function toggleHeaders(opts, editor) { 11 | const { value } = editor; 12 | const { startBlock } = value; 13 | 14 | const pos = TablePosition.create(value, startBlock, opts); 15 | const { table } = pos; 16 | 17 | const currentSetting = !!table.get('data').get('headless'); 18 | 19 | editor.setNodeByKey(table.key, { 20 | data: { 21 | headless: !currentSetting 22 | } 23 | }); 24 | 25 | return editor; 26 | } 27 | 28 | module.exports = toggleHeaders; -------------------------------------------------------------------------------- /lib/createCell.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | const { Text } = Slate; 3 | 4 | /** 5 | * Create a new cell 6 | * @param {String} type 7 | * @param {String} text? 8 | * @return {Slate.Node} 9 | */ 10 | function createCell(opts, text) { 11 | text = text || ''; 12 | const { typeCell, typeContent } = opts; 13 | 14 | return Slate.Block.create({ 15 | type: typeCell, 16 | nodes: [ 17 | Slate.Block.create({ 18 | type: typeContent, 19 | nodes: [ 20 | Text.fromJSON({ 21 | object: 'text', 22 | text, 23 | }) 24 | ] 25 | }) 26 | ] 27 | }); 28 | } 29 | 30 | module.exports = createCell; 31 | -------------------------------------------------------------------------------- /lib/createRow.js: -------------------------------------------------------------------------------- 1 | const Immutable = require('immutable'); 2 | const Slate = require('slate'); 3 | const createCell = require('./createCell'); 4 | 5 | /** 6 | * Create a new row block 7 | * 8 | * @param {Object} opts 9 | * @param {Number} columns 10 | * @param {Function} textGetter 11 | * @return {State.Block} 12 | */ 13 | function createRow(opts, columns, textGetter) { 14 | const cellNodes = Immutable.Range(0, columns) 15 | .map(i => createCell(opts, textGetter ? textGetter(i) : '')) 16 | .toList(); 17 | 18 | return Slate.Block.create({ 19 | type: opts.typeRow, 20 | nodes: cellNodes 21 | }); 22 | } 23 | 24 | module.exports = createRow; 25 | -------------------------------------------------------------------------------- /lib/createTable.js: -------------------------------------------------------------------------------- 1 | const { Range } = require('immutable'); 2 | const Slate = require('slate'); 3 | const createRow = require('./createRow'); 4 | 5 | /** 6 | * Create a table 7 | * 8 | * @param {Object} opts 9 | * @param {Number} columns 10 | * @param {Number} rows 11 | * @param {Function} textGetter 12 | * @return {Slate.Block} 13 | */ 14 | function createTable(opts, columns, rows, textGetter) { 15 | const rowNodes = Range(0, rows) 16 | .map(i => createRow(opts, columns, textGetter ? textGetter.bind(null, i) : null)) 17 | .toList(); 18 | 19 | return Slate.Block.create({ 20 | type: opts.typeTable, 21 | nodes: rowNodes, 22 | data: {} 23 | }); 24 | } 25 | 26 | module.exports = createTable; 27 | -------------------------------------------------------------------------------- /lib/defaultRenderers.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | /** 4 | * split rows into thead contens and body contents, 5 | * unless "headless" option is set 6 | */ 7 | const splitHeader = (props) => { 8 | const rows = props.children; 9 | const header = !(props.node.get('data').get('headless')); 10 | 11 | if (!header || !rows || !rows.length || rows.length===1) { 12 | return {header: null, rows: rows}; 13 | } 14 | return { 15 | header: rows[0], 16 | rows: rows.slice(1), 17 | }; 18 | } 19 | 20 | /** 21 | * default renderers for easier use in your own schema 22 | * @param {Object} opts The same opts passed into plugin instance 23 | */ 24 | const makeRenderers = (opts = {}) => (props, editor, next) => { 25 | switch (props.node.type) { 26 | case 'paragraph': return

{props.children}

; 27 | case 'heading': return

{props.children}

; 28 | case 'subheading': return

{props.children}

; 29 | case opts.typeTable: 30 | const {header, rows} = splitHeader(props); 31 | return ( 32 | 33 | {header && 34 | 35 | {header} 36 | 37 | } 38 | 39 | {rows} 40 | 41 |
42 | ); 43 | case opts.typeRow: return {props.children}; 44 | case opts.typeCell: return {props.children}; 45 | default: return next(); 46 | } 47 | } 48 | 49 | module.exports = makeRenderers; -------------------------------------------------------------------------------- /lib/defaultSerializers.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | // default rules to pass to slate's html serializer (see tests) 4 | const makeSerializerRules = (opts = {}) => { 5 | opts.typeTable = opts.typeTable || 'table'; 6 | opts.typeRow = opts.typeRow || 'table_row'; 7 | opts.typeCell = opts.typeCell || 'table_cell'; 8 | opts.typeContent = opts.typeContent || 'paragraph'; 9 | 10 | const TABLE_CHILD_TAGS = { 11 | tr: opts.typeRow, 12 | th: opts.typeCell, 13 | td: opts.typeCell, 14 | } 15 | 16 | return [ 17 | { 18 | serialize(obj, children) { 19 | if (obj.object == 'block') { 20 | switch (obj.type) { 21 | case opts.typeTable: 22 | const headers = !obj.data.get('headless'); 23 | const rows = children; 24 | const split = (!headers || !rows || !rows.size || rows.size===1) 25 | ? { header: null, rows: rows } 26 | : { 27 | header: rows.get(0), 28 | rows: rows.slice(1), 29 | } 30 | 31 | return ( 32 | 33 | {headers && 34 | {split.header} 35 | } 36 | {split.rows} 37 |
38 | ); 39 | case opts.typeRow: return {children}; 40 | case opts.typeCell: return {children}; 41 | case opts.typeContent: return

{children}

; 42 | default: return; 43 | } 44 | } 45 | if (obj.object == 'inline' && obj.type == 'link') { 46 | return {children}; 47 | } 48 | }, 49 | deserialize(el, next) { 50 | const tag = el.tagName.toLowerCase(); 51 | 52 | if (tag==='table') { 53 | const data = { headless: true } 54 | 55 | if ( 56 | el.firstElementChild && 57 | el.firstElementChild.tagName.toLowerCase()==='thead' 58 | ) { 59 | data.headless = false 60 | } 61 | 62 | return { 63 | object: "block", 64 | type: opts.typeTable, 65 | data: data, 66 | nodes: next(el.childNodes), 67 | } 68 | } 69 | 70 | const type = TABLE_CHILD_TAGS[tag]; 71 | 72 | if (type) { 73 | return { 74 | object: "block", 75 | type: type, 76 | data: {}, 77 | nodes: next(el.childNodes), 78 | } 79 | } 80 | } 81 | } 82 | ]; 83 | } 84 | 85 | module.exports = makeSerializerRules; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const insertTable = require('./changes/insertTable'); 2 | const insertTableByKey = require('./changes/insertTableByKey'); 3 | const insertTableByPath = require('./changes/insertTableByPath'); 4 | const insertRow = require('./changes/insertRow'); 5 | const removeRow = require('./changes/removeRow'); 6 | const insertColumn = require('./changes/insertColumn'); 7 | const removeColumn = require('./changes/removeColumn'); 8 | const removeTable = require('./changes/removeTable'); 9 | const moveTableSelection = require('./changes/moveSelection'); 10 | const moveTableSelectionBy = require('./changes/moveSelectionBy'); 11 | const toggleTableHeaders = require('./changes/toggleHeaders'); 12 | 13 | const TablePosition = require('./TablePosition'); 14 | const onTab = require('./onTab'); 15 | const onUpDown = require('./onUpDown'); 16 | const makeSchema = require('./makeSchema'); 17 | const makeRenderers = require('./defaultRenderers'); 18 | const makeSerializerRules = require('./defaultSerializers'); 19 | 20 | const KEY_TAB = 'Tab'; 21 | const KEY_DOWN = 'ArrowUp'; 22 | const KEY_UP = 'ArrowDown'; 23 | 24 | /** 25 | * @param {String} opts.typeTable The type of table blocks 26 | * @param {String} opts.typeRow The type of row blocks 27 | * @param {String} opts.typeCell The type of cell blocks 28 | * @param {String} opts.typeContent The type of default content blocks 29 | */ 30 | function EditTable(opts) { 31 | opts = opts || {}; 32 | opts.typeTable = opts.typeTable || 'table'; 33 | opts.typeRow = opts.typeRow || 'table_row'; 34 | opts.typeCell = opts.typeCell || 'table_cell'; 35 | opts.typeContent = opts.typeContent || 'paragraph'; 36 | 37 | /** 38 | * Is the selection in a table 39 | */ 40 | function isSelectionInTable(editor) { 41 | const { startBlock } = editor.value; 42 | if (!startBlock) return false; 43 | 44 | return TablePosition.isInCell(editor.value, startBlock, opts); 45 | } 46 | 47 | /** 48 | * Bind an editor command to our instance options as first arg 49 | */ 50 | function bindEditor(fn) { 51 | return function(editor, ...args) { 52 | if (!isSelectionInTable(editor)) { 53 | return editor; 54 | } 55 | 56 | return fn(...[opts, editor].concat(args)); 57 | }; 58 | } 59 | 60 | /** 61 | * User is pressing a key in the editor 62 | */ 63 | function onKeyDown(event, editor, next) { 64 | // Only handle events in cells 65 | if (!isSelectionInTable(editor)) { 66 | return next(); 67 | } 68 | 69 | // Build arguments list 70 | const args = [ event, editor, opts ]; 71 | 72 | switch (event.key) { 73 | case KEY_TAB: 74 | return onTab(...args); 75 | case KEY_DOWN: 76 | case KEY_UP: 77 | return onUpDown(...args); 78 | } 79 | return next(); 80 | } 81 | 82 | const { schema, normalizeNode } = makeSchema(opts); 83 | const renderBlock = makeRenderers(opts); 84 | 85 | function getPosition(editor) { 86 | if (!TablePosition.isInCell(editor.value, editor.value.startBlock, opts)) { 87 | return null; 88 | } 89 | return TablePosition.create(editor.value, editor.value.startBlock, opts); 90 | } 91 | 92 | return { 93 | onKeyDown, 94 | 95 | schema, 96 | normalizeNode, 97 | renderBlock, 98 | 99 | queries: { 100 | isSelectionInTable, 101 | getTablePosition: getPosition, 102 | }, 103 | 104 | commands: { 105 | insertTable: insertTable.bind(null, opts), 106 | insertTableByKey: insertTableByKey.bind(null, opts), 107 | insertTableByPath: insertTableByPath.bind(null, opts), 108 | insertRow: bindEditor(insertRow), 109 | removeRow: bindEditor(removeRow), 110 | insertColumn: bindEditor(insertColumn), 111 | removeColumn: bindEditor(removeColumn), 112 | removeTable: bindEditor(removeTable), 113 | moveTableSelection: bindEditor(moveTableSelection), 114 | moveTableSelectionBy: bindEditor(moveTableSelectionBy), 115 | toggleTableHeaders: bindEditor(toggleTableHeaders), 116 | } 117 | }; 118 | } 119 | 120 | // attach top-level function to create serializer rules 121 | EditTable.makeSerializerRules = makeSerializerRules 122 | 123 | module.exports = EditTable; -------------------------------------------------------------------------------- /lib/makeSchema.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | // const { SchemaViolations } = Slate; 3 | const { Range, List } = require('immutable'); 4 | const createCell = require('./createCell'); 5 | const createRow = require('./createRow'); 6 | 7 | const SchemaViolations = { 8 | ChildRequired: 'child_min_invalid', 9 | ChildObjectInvalid: 'child_object_invalid', 10 | ChildTypeInvalid: 'child_type_invalid', 11 | ParentTypeInvalid: 'parent_type_invalid', 12 | } 13 | 14 | /** 15 | * convenience methods used below 16 | */ 17 | 18 | const insertChild = (editor, error, type) => { 19 | return editor.insertNodeByKey( 20 | error.node.key, 21 | error.index, 22 | { object: 'block', type } 23 | ); 24 | }; 25 | 26 | const removeChild = (editor, error) => { 27 | return editor.removeNodeByKey(error.child.key); 28 | }; 29 | 30 | const wrapWithParent = (editor, error, type) => { 31 | return editor.wrapBlockByKey( 32 | error.node.key, 33 | type 34 | ); 35 | }; 36 | 37 | 38 | /** 39 | * Create a schema for tables 40 | * @param {String} opts.typeTable The type of table blocks 41 | * @param {String} opts.typeRow The type of row blocks 42 | * @param {String} opts.typeCell The type of cell blocks 43 | * @param {String} opts.typeContent The default type of content blocks in cells 44 | * @return {Object} A schema definition with rules to normalize tables 45 | */ 46 | function makeSchema(opts) { 47 | const schema = { 48 | blocks: { 49 | [opts.typeCell]: { 50 | parent: { type: opts.typeRow /* object: block in here stops it invalidating them, why? maybe the rules are OR */}, 51 | nodes: [{ 52 | match: { object: 'block' }, 53 | min: 1 54 | }], 55 | normalize: (editor, error) => { // reason, ctx 56 | 57 | // enforce cells must contain blocks, insert or wrap if not 58 | switch (error.code) { 59 | case SchemaViolations.ChildRequired: 60 | return editor.command(insertChild, error, opts.typeContent); 61 | 62 | case SchemaViolations.ChildObjectInvalid: 63 | // wrap non-block children with a block 64 | return editor.replaceNodeByKey( 65 | error.child.key, 66 | { 67 | object: 'block', 68 | type: opts.typeContent, 69 | nodes: error.node.mapDescendants(n => n.regenerateKey()).nodes 70 | } 71 | ); 72 | 73 | case SchemaViolations.ParentTypeInvalid: 74 | return editor.command(wrapWithParent, error, opts.typeRow); 75 | } 76 | } 77 | }, 78 | [opts.typeRow]: { 79 | parent: { type: opts.typeTable }, 80 | nodes: [{ 81 | match: {object: 'block', type: opts.typeCell}, 82 | min: 1 83 | }], 84 | normalize: (editor, error) => { 85 | 86 | // enforce rows must contain cells, drop all else 87 | switch (error.code) { 88 | case SchemaViolations.ChildRequired: 89 | return editor.command(insertChild, error, opts.typeCell); 90 | 91 | case SchemaViolations.ChildObjectInvalid: 92 | return editor.replaceNodeByKey( 93 | error.child.key, 94 | {object: 'block', type: opts.typeCell} 95 | ); 96 | 97 | case SchemaViolations.ChildTypeInvalid: 98 | // i wonder why we remove it instead of converting it to a cell. 99 | return editor.command(removeChild, error); 100 | 101 | case SchemaViolations.ParentTypeInvalid: 102 | return editor.command(wrapWithParent, error, opts.typeTable); 103 | } 104 | } 105 | }, 106 | [opts.typeTable]: { 107 | nodes: [{ 108 | match: {object: 'block', type: opts.typeRow}, 109 | min: 1 110 | }], 111 | normalize: (editor, error) => { 112 | 113 | // enforce rows must contain cells, drop all else 114 | switch (error.code) { 115 | case SchemaViolations.ChildRequired: 116 | return editor.command(insertChild, error, opts.typeRow); 117 | 118 | case SchemaViolations.ChildObjectInvalid: 119 | return editor.command(removeChild, error) 120 | .command(insertChild, error, opts.typeRow); 121 | 122 | case SchemaViolations.ChildTypeInvalid: 123 | return editor.command(removeChild, error); 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | const isRow = (node) => node.type === opts.typeRow; 131 | const isCell = (node) => node.type === opts.typeCell; 132 | const countCells = (row) => row.nodes.count(isCell); 133 | 134 | const normalizeNode = (node, editor, next) => { 135 | 136 | if (node.object != 'block') return next(); 137 | if (node.type !== opts.typeTable) return next(); 138 | 139 | const table = node; 140 | const rows = table.nodes.filter(isRow); 141 | 142 | // The number of column this table has 143 | const columns = rows.reduce((count, row) => { 144 | return Math.max(count, countCells(row)); 145 | }, 1); // Min 1 column 146 | 147 | const invalidRows = rows 148 | .map(row => { 149 | const cells = countCells(row); 150 | const invalids = row.nodes.filterNot(isCell); 151 | 152 | // Row is valid: right count of cells and no extra node 153 | if (invalids.isEmpty() && cells === columns) { 154 | return null; 155 | } 156 | 157 | // Otherwise, remove the invalids and append the missing cells 158 | return { 159 | row, 160 | invalids, 161 | add: (columns - cells) 162 | }; 163 | }) 164 | .filter(Boolean); 165 | 166 | if (invalidRows.size === 0) return next(); 167 | 168 | return (editor) => invalidRows.reduce((tr, { row, invalids, add }) => { 169 | 170 | tr = invalids.reduce((t, child) => { 171 | return t.removeNodeByKey(child.key); 172 | }, tr); 173 | 174 | tr = Range(0, add).reduce(t => { 175 | const cell = createCell(opts); 176 | return t.insertNodeByKey(row.key, 0, cell); 177 | }, tr); 178 | 179 | return tr; 180 | }, editor); 181 | } 182 | 183 | return { schema, normalizeNode }; 184 | } 185 | 186 | module.exports = makeSchema; 187 | -------------------------------------------------------------------------------- /lib/onTab.js: -------------------------------------------------------------------------------- 1 | const TablePosition = require('./TablePosition'); 2 | const moveSelectionBy = require('./changes/moveSelectionBy'); 3 | const insertRow = require('./changes/insertRow'); 4 | 5 | /** 6 | * Select all text of current block. 7 | * @param {Slate.Editor} editor 8 | * @return {Slate.Editor} 9 | */ 10 | function selectAllText(editor) { 11 | const { value } = editor; 12 | const { startBlock } = value; 13 | 14 | return editor.moveToRangeOfNode(startBlock); 15 | } 16 | 17 | /** 18 | * Pressing "Tab" moves the cursor to the next cell 19 | * and select the whole text 20 | */ 21 | function onTab(event, editor, opts) { 22 | const { value } = editor; 23 | event.preventDefault(); 24 | const direction = (event.shiftKey ? -1 : +1); 25 | 26 | // Create new row if needed 27 | const { startBlock } = value; 28 | const pos = TablePosition.create(value, startBlock, opts); 29 | 30 | if (pos.isFirstCell() && direction === -1) { 31 | editor = insertRow(opts, editor, 0); 32 | } else if (pos.isLastCell() && direction === 1) { 33 | editor = insertRow(opts, editor); 34 | } 35 | 36 | // Move 37 | editor = moveSelectionBy(opts, editor, direction, 0); 38 | 39 | // Select all cell. 40 | return selectAllText(editor); 41 | } 42 | 43 | module.exports = onTab; 44 | -------------------------------------------------------------------------------- /lib/onUpDown.js: -------------------------------------------------------------------------------- 1 | const TablePosition = require('./TablePosition'); 2 | const moveSelectionBy = require('./changes/moveSelectionBy'); 3 | 4 | function onUpDown(event, editor, opts) { 5 | const { value } = editor; 6 | const direction = event.key === 'ArrowUp' ? -1 : +1; 7 | const { startBlock } = value; 8 | const pos = TablePosition.create(value, startBlock, opts); 9 | 10 | if ((pos.isFirstRow() && direction === -1) 11 | || (pos.isLastRow() && direction === +1)) { 12 | // Let the default behavior move out of the table 13 | return editor; 14 | 15 | } else { 16 | event.preventDefault(); 17 | 18 | editor = moveSelectionBy( 19 | opts, editor, 20 | 0, event.key === 'ArrowUp' ? -1 : +1 21 | ); 22 | 23 | return editor; 24 | } 25 | } 26 | 27 | module.exports = onUpDown; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slate-deep-table", 3 | "description": "A Slate plugin to handle tables containing complex content.", 4 | "version": "0.9.7", 5 | "license": "Apache-2.0", 6 | "repository": "git://github.com/jasonphillips/slate-deep-table.git", 7 | "main": "./dist/index.js", 8 | "dependencies": { 9 | "slate-html-serializer": "^0.8.0" 10 | }, 11 | "peerDependencies": { 12 | "react": ">=16.6.0", 13 | "react-dom": ">=16.6.0", 14 | "slate": "0.47.x", 15 | "immutable": ">=3.8.1" 16 | }, 17 | "devDependencies": { 18 | "babel-cli": "^6.11.4", 19 | "babel-core": "^6.11.4", 20 | "babel-plugin-transform-class-properties": "^6.24.1", 21 | "babel-preset-es2015": "^6.9.0", 22 | "babel-preset-react": "^6.11.1", 23 | "babelify": "^7.3.0", 24 | "browserify": "^16.3.0", 25 | "eslint": "^4.18.2", 26 | "eslint-config-gitbook": "1.4.0", 27 | "expect": "^1.20.2", 28 | "gh-pages": "^0.11.0", 29 | "http-server": "^0.11.1", 30 | "immutable": "^3.8.1", 31 | "jsdom": "^15.1.1", 32 | "mocha": "^6.1.4", 33 | "react": ">=16.6.0", 34 | "react-dom": ">=16.6.0", 35 | "slate": "^0.47.x", 36 | "slate-hyperscript": "^0.13.0", 37 | "slate-react": "^0.22.0", 38 | "watchify": "^3.11.1" 39 | }, 40 | "scripts": { 41 | "prepublish": "babel ./lib --out-dir ./dist", 42 | "postpublish": "npm run deploy-example", 43 | "lint": "eslint ./", 44 | "build-example": "browserify ./example/main.js -o ./example/bundle.js -t [ babelify --presets [ es2015 react ] --plugins transform-class-properties ] ", 45 | "watch-example": "watchify ./example/main.js -o ./example/bundle.js -t [ babelify --presets [ es2015 react ] --plugins transform-class-properties ] ", 46 | "serve-example": "http-server ./example/ -p 8080", 47 | "start": "npm run build-example; npm run serve-example", 48 | "deploy-example": "npm run build-example; gh-pages -d ./example", 49 | "test": "./node_modules/.bin/mocha ./tests/*.js --require babel-register --reporter=list" 50 | }, 51 | "keywords": [ 52 | "slate" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /tests/all.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const Slate = require('slate'); 5 | const { Editor } = require('slate'); 6 | const DeepTable = require('../lib'); 7 | const makeSchema = require('../lib/makeSchema'); 8 | const Html = require('slate-html-serializer').default; 9 | const jsdom = require('jsdom'); 10 | 11 | // a basic table for serialize test, with no headers 12 | const basicTable = require('./basicTableInput').default; 13 | // with headers 14 | const headerTable = require('./headerTableInput').default; 15 | 16 | // a vanilla slate instance to normalize with 17 | const normalizeDefault = value => (new Slate.Editor({ value })).value 18 | 19 | // use the default serializers 20 | const html = new Html({ 21 | parseHtml: jsdom.JSDOM.fragment, 22 | rules: DeepTable.makeSerializerRules({}), 23 | }) 24 | 25 | describe('slate-deep-table', function() { 26 | const tests = fs.readdirSync(__dirname); 27 | const plugin = DeepTable(); 28 | 29 | tests.forEach(function(test) { 30 | if (test[0] === '.' || path.extname(test).length > 0) return; 31 | 32 | it(test, function() { 33 | const dir = path.resolve(__dirname, test); 34 | const input = require(path.resolve(dir, 'input.js')).default; 35 | const expected = require(path.resolve(dir, 'expected.js')).default; 36 | const runTransform = require(path.resolve(dir, 'transform.js')); 37 | 38 | // create editor instance to normalize input 39 | const editor = new Slate.Editor({ value: input, plugins: [plugin] }) 40 | 41 | // normalize expected as well, but with bare slate Editor 42 | const normalizedExpected = normalizeDefault(expected) 43 | 44 | const actual = runTransform(editor); 45 | //console.error(serialize(actual)) 46 | 47 | expect(actual.toJSON()).toEqual(normalizedExpected.toJSON()); 48 | }); 49 | }); 50 | 51 | it('can serialize with tbody, thead', function () { 52 | const serialized = html.serialize(headerTable); 53 | expect(serialized).toEqual( 54 | ` 55 | 56 | 57 | 60 | 63 | 64 | 65 | 66 | 67 | 70 | 73 | 74 | 75 | 78 | 81 | 82 | 83 |
58 |

Col 0, Row 0

59 |
61 |

Col 1, Row 0

62 |
68 |

Col 0, Row 1

69 |
71 |

Col 1, Row 1

72 |
76 |

Col 0, Row 2

77 |
79 |

Col 1, Row 2

80 |
` 84 | .split('\n').map(line => line.trim()).join('')) 85 | }) 86 | 87 | it('can serialize headerless with only tbody', function () { 88 | const serialized = html.serialize(basicTable); 89 | expect(serialized).toEqual( 90 | ` 91 | 92 | 93 | 96 | 99 | 100 | 101 | 104 | 107 | 108 | 109 | 112 | 115 | 116 | 117 |
94 |

Col 0, Row 0

95 |
97 |

Col 1, Row 0

98 |
102 |

Col 0, Row 1

103 |
105 |

Col 1, Row 1

106 |
110 |

Col 0, Row 2

111 |
113 |

Col 1, Row 2

114 |
118 |

An extraneous paragraph

119 | ` 120 | .split('\n').map(line => line.trim()).join('')) 121 | }) 122 | 123 | it('can deserialize an html table', function () { 124 | const htmlTable = ` 125 | 126 | 127 | 130 | 133 | 134 | 135 | 138 | 141 | 142 | 143 | 146 | 149 | 150 |
128 |

Col 0, Row 0

129 |
131 |

Col 1, Row 0

132 |
136 |

Col 0, Row 1

137 |
139 |

Col 1, Row 1

140 |
144 |

Col 0, Row 2

145 |
147 |

Col 1, Row 2

148 |
151 |

An extraneous paragraph

152 | `.split('\n').map(line => line.trim()).join(''); 153 | 154 | const deserialized = html.deserialize(htmlTable); 155 | const value = new Slate.Editor({ value: deserialized, plugins: [plugin] }).value 156 | 157 | expect(value.toJSON()).toEqual(basicTable.toJSON()); 158 | }) 159 | 160 | it('can deserialize an html table with headers', function () { 161 | const htmlTable = ` 162 | 163 | 164 | 165 | 168 | 171 | 172 | 173 | 174 | 175 | 178 | 181 | 182 | 183 | 186 | 189 | 190 | 191 |
166 |

Col 0, Row 0

167 |
169 |

Col 1, Row 0

170 |
176 |

Col 0, Row 1

177 |
179 |

Col 1, Row 1

180 |
184 |

Col 0, Row 2

185 |
187 |

Col 1, Row 2

188 |
192 | `.split('\n').map(line => line.trim()).join(''); 193 | 194 | const deserialized = html.deserialize(htmlTable); 195 | const value = new Slate.Editor({ value: deserialized, plugins: [plugin] }).value 196 | 197 | expect(value.toJSON()).toEqual(headerTable.toJSON()); 198 | }) 199 | 200 | it('handlers.getPosition() allows table position to be found', function () { 201 | const editor = new Slate.Editor({ value: basicTable, plugins: [plugin] }) 202 | 203 | // move to row 2, cell 1 204 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 205 | editor.moveToRangeOfNode(cursorBlock); 206 | 207 | // check current position 208 | const tablePosition = editor.getTablePosition(); 209 | expect(tablePosition).toBeTruthy(); 210 | expect(tablePosition.getRowIndex()).toEqual(1); 211 | expect(tablePosition.getColumnIndex()).toEqual(0); 212 | expect(tablePosition.getWidth()).toEqual(2); 213 | expect(tablePosition.getHeight()).toEqual(3); 214 | 215 | // move outside table, expect null result 216 | const cursorBlockTwo = editor.value.document.getDescendant('_cursor_2_'); 217 | editor.moveToRangeOfNode(cursorBlockTwo); 218 | 219 | const tablePositionTwo = editor.getTablePosition(); 220 | expect(tablePositionTwo).toEqual(null); 221 | }) 222 | }); 223 | -------------------------------------------------------------------------------- /tests/always-blocks-within-cells/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 |
11 |

Row 1, Col 1

12 |
14 |

Row 1, Col 2

15 |
17 |

Row 1, Col 3

18 |
21 |
22 |
23 | ); 24 | 25 | export default value; -------------------------------------------------------------------------------- /tests/always-blocks-within-cells/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Row 1, Col 1Row 1, Col 2Row 1, Col 3
15 |
16 |
17 | ); 18 | 19 | export default value; -------------------------------------------------------------------------------- /tests/always-blocks-within-cells/transform.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | 3 | module.exports = function(editor) { 4 | return editor.value; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/basicTableInput.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from './hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
19 |

Col 0, Row 1

20 |
22 |

Col 1, Row 1

23 |
27 |

Col 0, Row 2

28 |
30 |

Col 1, Row 2

31 |
34 |

An extraneous paragraph

35 |
36 |
37 | ); 38 | 39 | export default value; -------------------------------------------------------------------------------- /tests/headerTableInput.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from './hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
19 |

Col 0, Row 1

20 |
22 |

Col 1, Row 1

23 |
27 |

Col 0, Row 2

28 |
30 |

Col 1, Row 2

31 |
34 |
35 |
36 | ); 37 | 38 | export default value; -------------------------------------------------------------------------------- /tests/hyperscript.js: -------------------------------------------------------------------------------- 1 | import { createHyperscript } from 'slate-hyperscript' 2 | 3 | const h = createHyperscript({ 4 | blocks: { 5 | p: 'paragraph', 6 | table: 'table', 7 | tr: 'table_row', 8 | td: 'table_cell', 9 | other: 'other', // random invalid block 10 | h2: 'heading', 11 | h3: 'subheading', 12 | }, 13 | inlines: { 14 | a: 'link', 15 | }, 16 | marks: { 17 | b: 'bold', 18 | }, 19 | key: true, 20 | }); 21 | 22 | export default h; -------------------------------------------------------------------------------- /tests/insert-column-at/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

15 |
17 |

Col 1, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

26 |
28 |

Col 1, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

37 |
39 |

Col 1, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/insert-column-at/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
19 |

Col 0, Row 1

20 |
22 |

Col 1, Row 1

23 |
27 |

Col 0, Row 2

28 |
30 |

Col 1, Row 2

31 |
34 |
35 |
36 | ); 37 | 38 | export default value; -------------------------------------------------------------------------------- /tests/insert-column-at/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .insertColumn() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/insert-column/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/insert-column/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
19 |

Col 0, Row 1

20 |
22 |

Col 1, Row 1

23 |
27 |

Col 0, Row 2

28 |
30 |

Col 1, Row 2

31 |
34 |
35 |
36 | ); 37 | 38 | export default value; -------------------------------------------------------------------------------- /tests/insert-column/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .insertColumn() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/insert-row/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

34 |
36 |

37 |
39 |

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/insert-row/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
32 |
33 |
34 | ); 35 | 36 | export default value; -------------------------------------------------------------------------------- /tests/insert-row/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .insertRow() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/insert-table-by-key/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

9 | 10 | 11 | 14 | 17 | 18 | 19 | 22 | 25 | 26 |
12 |

13 |
15 |

16 |
20 |

21 |
23 |

24 |
27 |

28 |
29 |
30 | ); 31 | 32 | export default value; -------------------------------------------------------------------------------- /tests/insert-table-by-key/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

9 |
10 |
11 | ); 12 | 13 | export default value; -------------------------------------------------------------------------------- /tests/insert-table-by-key/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | return editor.insertTableByKey('_cursor_').value 3 | }; 4 | -------------------------------------------------------------------------------- /tests/insert-table-by-path/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

9 | 10 | 11 | 14 | 17 | 18 | 19 | 22 | 25 | 26 |
12 |

13 |
15 |

16 |
20 |

21 |
23 |

24 |
27 |

28 |
29 |
30 | ); 31 | 32 | export default value; -------------------------------------------------------------------------------- /tests/insert-table-by-path/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

9 |
10 |
11 | ); 12 | 13 | export default value; -------------------------------------------------------------------------------- /tests/insert-table-by-path/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | // path of cursor 3 | const path = editor.value.document.getPath('_cursor_'); 4 | 5 | // pass it to the insertTable() method 6 | return editor.insertTableByPath(path).value 7 | }; 8 | -------------------------------------------------------------------------------- /tests/insert-table/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

Before

9 | 10 | 11 | 14 | 17 | 18 | 19 | 22 | 25 | 26 |
12 |

13 |
15 |

16 |
20 |

21 |
23 |

24 |
27 |
28 |
29 | ); 30 | 31 | export default value; -------------------------------------------------------------------------------- /tests/insert-table/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

BeforeAfter

9 |
10 |
11 | ); 12 | 13 | export default value; -------------------------------------------------------------------------------- /tests/insert-table/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .moveForward(6) // Cursor here: Before|After 7 | .insertTable() 8 | .value; 9 | }; 10 | -------------------------------------------------------------------------------- /tests/move-table-selection-by/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/move-table-selection-by/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/move-table-selection-by/transform.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | module.exports = function(editor) { 4 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 5 | const offset = 2; 6 | 7 | editor 8 | .moveToRangeOfNode(cursorBlock) 9 | .moveForward(offset) 10 | .moveTableSelectionBy(-1, -1); 11 | 12 | const { selection, startBlock } = editor.value; 13 | 14 | expect(startBlock.text).toEqual('Col 0, Row 0'); 15 | expect(selection.start.key).toEqual(selection.end.key); 16 | // Keep same offset 17 | expect(selection.start.offset).toEqual(offset); 18 | 19 | return editor.value; 20 | }; 21 | -------------------------------------------------------------------------------- /tests/move-table-selection/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/move-table-selection/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/move-table-selection/transform.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | module.exports = function(editor) { 4 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 5 | const offset = 2; 6 | editor 7 | .moveToRangeOfNode(cursorBlock) 8 | .moveForward(offset) 9 | .moveTableSelection(2, 2); 10 | 11 | const { selection, startBlock } = editor.value; 12 | 13 | expect(startBlock.text).toEqual('Col 2, Row 2'); 14 | expect(selection.start.key).toEqual(selection.end.key); 15 | // Keep same offset 16 | expect(selection.start.offset).toEqual(offset); 17 | 18 | return editor.value; 19 | }; 20 | -------------------------------------------------------------------------------- /tests/query-isSelectionInTable/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

A paragraph not in a table

9 | 10 | 11 | 14 | 15 |
12 |

A paragraph within a table

13 |
16 |
17 |
18 | ); 19 | 20 | export default value; -------------------------------------------------------------------------------- /tests/query-isSelectionInTable/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

A paragraph not in a table

9 | 10 | 11 | 14 | 15 |
12 |

A paragraph within a table

13 |
16 |
17 |
18 | ); 19 | 20 | export default value; -------------------------------------------------------------------------------- /tests/query-isSelectionInTable/transform.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | module.exports = function(editor) { 4 | const nonTableBlock = editor.value.document.getDescendant('_cursor_outside_table_'); 5 | const tableBlock = editor.value.document.getDescendant('_cursor_inside_table_'); 6 | 7 | // move to paragraph outside table, run query 8 | const queryNonTableBlock = editor 9 | .moveToRangeOfNode(nonTableBlock) 10 | .isSelectionInTable(); 11 | 12 | expect(queryNonTableBlock).toBeFalsy(); 13 | 14 | // move to paragraph within table, run query 15 | const queryTableBlock = editor 16 | .moveToRangeOfNode(tableBlock) 17 | .isSelectionInTable(); 18 | 19 | expect(queryTableBlock).toBeTruthy(); 20 | 21 | return editor.value; 22 | }; 23 | -------------------------------------------------------------------------------- /tests/remove-column-first/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 1, Row 0

12 |
14 |

Col 2, Row 0

15 |
19 |

Col 1, Row 1

20 |
22 |

Col 2, Row 1

23 |
27 |

Col 1, Row 2

28 |
30 |

Col 2, Row 2

31 |
34 |
35 |
36 | ); 37 | 38 | export default value; -------------------------------------------------------------------------------- /tests/remove-column-first/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/remove-column-first/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeColumn() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/remove-column-last-one/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 |
11 |

12 |
16 |

17 |
21 |

22 |
25 |
26 |
27 | ); 28 | 29 | export default value; -------------------------------------------------------------------------------- /tests/remove-column-last-one/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 |
11 |

Col 0, Row 0

12 |
16 |

Col 0, Row 1

17 |
21 |

Col 0, Row 2

22 |
25 |
26 |
27 | ); 28 | 29 | export default value; -------------------------------------------------------------------------------- /tests/remove-column-last-one/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeColumn() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/remove-column/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 0, Row 0

12 |
14 |

Col 2, Row 0

15 |
19 |

Col 0, Row 1

20 |
22 |

Col 2, Row 1

23 |
27 |

Col 0, Row 2

28 |
30 |

Col 2, Row 2

31 |
34 |
35 |
36 | ); 37 | 38 | export default value; -------------------------------------------------------------------------------- /tests/remove-column/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/remove-column/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeColumn() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/remove-row-first/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 |
11 |

Col 0, Row 1

12 |
14 |

Col 1, Row 1

15 |
17 |

Col 2, Row 1

18 |
22 |

Col 0, Row 2

23 |
25 |

Col 1, Row 2

26 |
28 |

Col 2, Row 2

29 |
32 |
33 |
34 | ); 35 | 36 | export default value; -------------------------------------------------------------------------------- /tests/remove-row-first/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/remove-row-first/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeRow() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/remove-row-last-one/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 |
11 |

12 |
14 |

15 |
17 |

18 |
21 |
22 |
23 | ); 24 | 25 | export default value; -------------------------------------------------------------------------------- /tests/remove-row-last-one/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
21 |
22 |
23 | ); 24 | 25 | export default value; -------------------------------------------------------------------------------- /tests/remove-row-last-one/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeRow() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/remove-row/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 2

23 |
25 |

Col 1, Row 2

26 |
28 |

Col 2, Row 2

29 |
32 |
33 |
34 | ); 35 | 36 | export default value; -------------------------------------------------------------------------------- /tests/remove-row/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/remove-row/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeRow() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/remove-table/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

Before

9 |

After

10 |
11 |
12 | ); 13 | 14 | export default value; -------------------------------------------------------------------------------- /tests/remove-table/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

Before

9 | 10 | 11 | 14 | 17 | 20 | 21 | 22 | 25 | 28 | 31 | 32 | 33 | 36 | 39 | 42 | 43 |
12 |

Col 0, Row 0

13 |
15 |

Col 1, Row 0

16 |
18 |

Col 2, Row 0

19 |
23 |

Col 0, Row 1

24 |
26 |

Col 1, Row 1

27 |
29 |

Col 2, Row 1

30 |
34 |

Col 0, Row 2

35 |
37 |

Col 1, Row 2

38 |
40 |

Col 2, Row 2

41 |
44 |

After

45 |
46 |
47 | ); 48 | 49 | export default value; -------------------------------------------------------------------------------- /tests/remove-table/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | return editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeTable() 7 | .value; 8 | }; 9 | -------------------------------------------------------------------------------- /tests/schema-no-blocks-within-rows/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 |
11 |

Row 1, Col 1

12 |
14 |

Row 1, Col 2

15 |
17 |

Row 1, Col 3

18 |
21 |
22 |
23 | ); 24 | 25 | export default value; -------------------------------------------------------------------------------- /tests/schema-no-blocks-within-rows/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 |

Row 1, Col 4

20 | 21 |
11 |

Row 1, Col 1

12 |
14 |

Row 1, Col 2

15 |
17 |

Row 1, Col 3

18 |
22 |
23 |
24 | ); 25 | 26 | export default value; -------------------------------------------------------------------------------- /tests/schema-no-blocks-within-rows/transform.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | 3 | module.exports = function(editor) { 4 | return editor.value; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/schema-no-blocks-within-table/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 |
11 |

Row 1, Col 1

12 |
14 |

Row 1, Col 2

15 |
17 |

Row 1, Col 3

18 |
21 |
22 |
23 | ); 24 | 25 | export default value; -------------------------------------------------------------------------------- /tests/schema-no-blocks-within-table/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 |

Row 1, Col 4

21 |
11 |

Row 1, Col 1

12 |
14 |

Row 1, Col 2

15 |
17 |

Row 1, Col 3

18 |
22 |
23 |
24 | ); 25 | 26 | export default value; -------------------------------------------------------------------------------- /tests/schema-no-blocks-within-table/transform.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | 3 | module.exports = function(editor) { 4 | return editor.value; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/schema-rows-cells-within-tables/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 14 |
11 |

No table

12 |
15 | 16 | 17 | 20 | 21 |
18 |

No rows

19 |
22 |
23 |
24 | ); 25 | 26 | export default value; -------------------------------------------------------------------------------- /tests/schema-rows-cells-within-tables/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | No table 10 | 11 | No rows 12 | 13 | 14 | ); 15 | 16 | export default value; -------------------------------------------------------------------------------- /tests/schema-rows-cells-within-tables/transform.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | 3 | module.exports = function(editor) { 4 | return editor.value; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/schema-rows-require-same-columns-counts/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 1, Row 1

12 |
14 |

Col 2, Row 1

15 |
17 |

Col 3, Row 1

18 |
22 |

23 |
25 |

26 |
28 |

There is only one column here

29 |
33 |

34 |
36 |

Col 1, Row 3

37 |
39 |

Col 3, Row 3

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/schema-rows-require-same-columns-counts/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 |

Col 2, Row 3

29 |
30 | 33 | 34 |
11 |

Col 1, Row 1

12 |
14 |

Col 2, Row 1

15 |
17 |

Col 3, Row 1

18 |
There is only one column here
25 |

Col 1, Row 3

26 |
31 |

Col 3, Row 3

32 |
35 |
36 |
37 | ); 38 | 39 | export default value; -------------------------------------------------------------------------------- /tests/schema-rows-require-same-columns-counts/transform.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | 3 | module.exports = function(editor) { 4 | return editor.value; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/schema-standard-table/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/schema-standard-table/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/schema-standard-table/transform.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | 3 | module.exports = function(editor) { 4 | return editor.value; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/schema-tables-contain-rows/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 14 |
11 |

12 |
15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 |
18 |

Row 1, Col 1

19 |
23 |

Row 3, Col 1

24 |
27 |
28 |
29 | ); 30 | 31 | export default value; -------------------------------------------------------------------------------- /tests/schema-tables-contain-rows/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | no rows
9 | 10 | 11 | 14 | 15 | 16 | 19 | 20 | 21 | 24 | 25 |
12 |

Row 1, Col 1

13 |
17 |

Row 2, Col 1

18 |
22 |

Row 3, Col 1

23 |
26 |
27 |
28 | ); 29 | 30 | export default value; -------------------------------------------------------------------------------- /tests/schema-tables-contain-rows/transform.js: -------------------------------------------------------------------------------- 1 | const Slate = require('slate'); 2 | 3 | module.exports = function(editor) { 4 | return editor.value; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/undo-insert-column/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
19 |

Col 0, Row 1

20 |
22 |

Col 1, Row 1

23 |
27 |

Col 0, Row 2

28 |
30 |

Col 1, Row 2

31 |
34 |
35 |
36 | ); 37 | 38 | export default value; -------------------------------------------------------------------------------- /tests/undo-insert-column/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
19 |

Col 0, Row 1

20 |
22 |

Col 1, Row 1

23 |
27 |

Col 0, Row 2

28 |
30 |

Col 1, Row 2

31 |
34 |
35 |
36 | ); 37 | 38 | export default value; -------------------------------------------------------------------------------- /tests/undo-insert-column/transform.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | module.exports = function(editor) { 4 | 5 | editor.insertColumn(); 6 | 7 | editor.undo(); 8 | 9 | // Back to previous cursor position 10 | expect(editor.value.startBlock.text).toEqual('Col 1, Row 1'); 11 | 12 | return editor.value; 13 | }; 14 | -------------------------------------------------------------------------------- /tests/undo-insert-row/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
32 |
33 |
34 | ); 35 | 36 | export default value; -------------------------------------------------------------------------------- /tests/undo-insert-row/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
32 |
33 |
34 | ); 35 | 36 | export default value; -------------------------------------------------------------------------------- /tests/undo-insert-row/transform.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | module.exports = function(editor) { 4 | const value = editor 5 | .insertRow() 6 | .undo() 7 | .value 8 | 9 | // Back to previous cursor position 10 | expect(value.startBlock.text).toEqual('Col 1, Row 1'); 11 | 12 | return value; 13 | }; 14 | -------------------------------------------------------------------------------- /tests/undo-insert-table/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

BeforeAfter

9 |
10 |
11 | ); 12 | 13 | export default value; -------------------------------------------------------------------------------- /tests/undo-insert-table/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

BeforeAfter

9 |
10 |
11 | ); 12 | 13 | export default value; -------------------------------------------------------------------------------- /tests/undo-insert-table/transform.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | module.exports = function(editor) { 4 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 5 | 6 | const value = editor 7 | .moveToRangeOfNode(cursorBlock) 8 | .moveForward(6) 9 | .insertTable() 10 | .undo() 11 | .value; 12 | 13 | // Back to previous cursor position 14 | expect(value.startBlock.text).toEqual('BeforeAfter'); 15 | 16 | return value; 17 | }; 18 | -------------------------------------------------------------------------------- /tests/undo-remove-column/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/undo-remove-column/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/undo-remove-column/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | const value = editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeColumn() 7 | .undo() 8 | .value; 9 | 10 | return value; 11 | }; 12 | -------------------------------------------------------------------------------- /tests/undo-remove-row/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/undo-remove-row/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 | 9 | 10 | 13 | 16 | 19 | 20 | 21 | 24 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 |
11 |

Col 0, Row 0

12 |
14 |

Col 1, Row 0

15 |
17 |

Col 2, Row 0

18 |
22 |

Col 0, Row 1

23 |
25 |

Col 1, Row 1

26 |
28 |

Col 2, Row 1

29 |
33 |

Col 0, Row 2

34 |
36 |

Col 1, Row 2

37 |
39 |

Col 2, Row 2

40 |
43 |
44 |
45 | ); 46 | 47 | export default value; -------------------------------------------------------------------------------- /tests/undo-remove-row/transform.js: -------------------------------------------------------------------------------- 1 | module.exports = function(editor) { 2 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 3 | 4 | const value = editor 5 | .moveToRangeOfNode(cursorBlock) 6 | .removeRow() 7 | .undo() 8 | .value; 9 | 10 | return value; 11 | }; 12 | -------------------------------------------------------------------------------- /tests/undo-remove-table/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

Before

9 | 10 | 11 | 14 | 17 | 20 | 21 | 22 | 25 | 28 | 31 | 32 | 33 | 36 | 39 | 42 | 43 |
12 |

Col 0, Row 0

13 |
15 |

Col 1, Row 0

16 |
18 |

Col 2, Row 0

19 |
23 |

Col 0, Row 1

24 |
26 |

Col 1, Row 1

27 |
29 |

Col 2, Row 1

30 |
34 |

Col 0, Row 2

35 |
37 |

Col 1, Row 2

38 |
40 |

Col 2, Row 2

41 |
44 |

After

45 |
46 |
47 | ); 48 | 49 | export default value; -------------------------------------------------------------------------------- /tests/undo-remove-table/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import h from '../hyperscript'; 4 | 5 | const value = ( 6 | 7 | 8 |

Before

9 | 10 | 11 | 14 | 17 | 20 | 21 | 22 | 25 | 28 | 31 | 32 | 33 | 36 | 39 | 42 | 43 |
12 |

Col 0, Row 0

13 |
15 |

Col 1, Row 0

16 |
18 |

Col 2, Row 0

19 |
23 |

Col 0, Row 1

24 |
26 |

Col 1, Row 1

27 |
29 |

Col 2, Row 1

30 |
34 |

Col 0, Row 2

35 |
37 |

Col 1, Row 2

38 |
40 |

Col 2, Row 2

41 |
44 |

After

45 |
46 |
47 | ); 48 | 49 | export default value; -------------------------------------------------------------------------------- /tests/undo-remove-table/transform.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | module.exports = function(editor) { 4 | const cursorBlock = editor.value.document.getDescendant('_cursor_'); 5 | 6 | const value = editor 7 | .moveToRangeOfNode(cursorBlock) 8 | .removeTable() 9 | .undo() 10 | .value; 11 | 12 | return value; 13 | }; 14 | --------------------------------------------------------------------------------