├── .babelrc ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── browserslist ├── example ├── index.html ├── main.css ├── main.js └── value.js ├── lib ├── changes │ ├── decreaseItemDepth.js │ ├── increaseItemDepth.js │ ├── index.js │ ├── splitListItem.js │ ├── unwrapList.js │ └── wrapInList.js ├── core.js ├── handlers │ ├── index.js │ ├── onBackspace.js │ ├── onEnter.js │ └── onTab.js ├── index.js ├── options.js ├── utils │ ├── getCurrentItem.js │ ├── getCurrentList.js │ ├── getItemDepth.js │ ├── getItemsAtRange.js │ ├── getListForItem.js │ ├── getPreviousItem.js │ ├── index.js │ ├── isList.js │ └── isSelectionInList.js └── validation │ ├── index.js │ ├── schema.js │ └── validateNode.js ├── package.json ├── tests ├── all.js ├── backspace-empty-between-inline │ ├── change.js │ ├── expected.js │ └── input.js ├── backspace-end-of-inline │ ├── change.js │ ├── expected.js │ └── input.js ├── backspace-start-of-inline │ ├── change.js │ ├── expected.js │ └── input.js ├── backspace-start-of-item │ ├── change.js │ ├── expected.js │ └── input.js ├── decrease-item-depth-basic │ ├── change.js │ ├── expected.js │ └── input.js ├── decrease-item-depth-long-sublist-with-data │ ├── change.js │ ├── expected.js │ └── input.js ├── decrease-item-depth-long-sublist │ ├── change.js │ ├── expected.js │ └── input.js ├── enter-empty-block-in-item │ ├── change.js │ ├── expected.js │ └── input.js ├── enter-empty-item │ ├── change.js │ ├── expected.js │ └── input.js ├── enter-middle-item │ ├── change.js │ ├── expected.js │ └── input.js ├── enter-nested-item │ ├── change.js │ ├── expected.js │ └── input.js ├── get-current-item │ ├── change.js │ └── input.js ├── get-previous-item │ ├── change.js │ └── input.js ├── hyperscript.js ├── increase-item-depth-basic-with-data │ ├── change.js │ ├── expected.js │ └── input.js ├── increase-item-depth-basic │ ├── change.js │ ├── expected.js │ └── input.js ├── increase-item-depth-complex │ ├── change.js │ ├── expected.js │ └── input.js ├── increase-item-depth-existing-sublist │ ├── change.js │ ├── expected.js │ └── input.js ├── increase-item-depth-fifth │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-items-are-list-children │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-items-contain-blocks-2 │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-items-contain-blocks │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-join-adjacent-lists-unless-different │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-join-adjacent-lists │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-lists-contain-items │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-nested-lists │ ├── change.js │ ├── expected.js │ └── input.js ├── schema-ul-ul-li-li │ ├── change.js │ ├── expected.js │ └── input.js ├── shift-enter-middle-item │ ├── change.js │ └── input.js ├── split-item-end │ ├── change.js │ ├── expected.js │ └── input.js ├── split-item-offset-multiple-blocks │ ├── change.js │ ├── expected.js │ └── input.js ├── split-item-offset │ ├── change.js │ ├── expected.js │ └── input.js ├── split-item-start │ ├── change.js │ ├── expected.js │ └── input.js ├── split-item-sublist-deep │ ├── change.js │ ├── expected.js │ └── input.js ├── split-item-sublist │ ├── change.js │ ├── expected.js │ └── input.js ├── undo-decrease-item-depth-long-sublist │ ├── change.js │ ├── expected.js │ └── input.js ├── undo-increase-item-depth-complex │ ├── change.js │ ├── expected.js │ └── input.js ├── undo-split-item-sublist-deep │ ├── change.js │ ├── expected.js │ └── input.js ├── undo-unwrap-long-list │ ├── change.js │ ├── expected.js │ └── input.js ├── undo-wrap-in-list │ ├── change.js │ ├── expected.js │ └── input.js ├── unwrap-list-multiple-nested │ ├── change.js │ ├── expected.js │ └── input.js ├── unwrap-list-multiple │ ├── change.js │ ├── expected.js │ └── input.js ├── unwrap-list │ ├── change.js │ ├── expected.js │ └── input.js ├── unwrap-long-list │ ├── change.js │ ├── expected.js │ └── input.js ├── unwrap-nested-list │ ├── change.js │ ├── expected.js │ └── input.js ├── wrap-in-list-list-with-data │ ├── change.js │ ├── expected.js │ └── input.js ├── wrap-in-list-list │ ├── change.js │ ├── expected.js │ └── input.js ├── wrap-in-list-multiple-with-data │ ├── change.js │ ├── expected.js │ └── input.js ├── wrap-in-list-multiple │ ├── change.js │ ├── expected.js │ └── input.js ├── wrap-in-list-with-data │ ├── change.js │ ├── expected.js │ └── input.js ├── wrap-in-list │ ├── change.js │ ├── expected.js │ └── input.js └── wrap-in-ol │ ├── change.js │ ├── expected.js │ └── input.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "flow", "react", "stage-0"], 3 | "plugins": [ 4 | "transform-runtime", 5 | [ 6 | "module-resolver", 7 | { 8 | "alias": { 9 | "h": "./tests/hyperscript" 10 | } 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | example/bundle.js 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["gitbook"], 4 | "rules": { 5 | "no-param-reassign": 0, 6 | "import/no-commonjs": 2, 7 | "react/void-dom-elements-no-children": 0, 8 | "jsx-a11y/html-has-lang": 0, 9 | "jsx-a11y/accessible-emoji": 0 10 | }, 11 | "plugins": ["flowtype"], 12 | "env": { 13 | "mocha": true 14 | }, 15 | "settings": { 16 | "import/resolver": { 17 | "babel-module": {} 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [options] 2 | emoji=true 3 | esproposal.decorators=ignore 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editors 2 | .idea 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | dist 43 | example/bundle.js 44 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib 2 | !dist 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | 6 | script: 7 | - npm run test 8 | - npm run lint 9 | # - npm run flow -------------------------------------------------------------------------------- /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 | 7 | ## [Unreleased] 8 | 9 | [Unreleased]: https://github.com/GitbookIO/slate-edit-list/compare/0.12.2...HEAD 10 | 11 | ## 0.12.3 - 2019-01-07 12 | 13 | - Accept `immutable@4` 14 | 15 | ## 0.12.2 - 2018-08-20 16 | 17 | - Added optional argument `type` to `isSelectionInList` 18 | 19 | ## 0.12.1 - 2018-08-20 20 | 21 | - Avoid merging non-list blocks, even when using the `canMerge` option 22 | 23 | ## 0.12.0 - 2018-08-03 24 | 25 | - Add `canMerge` option to control which adjacent lists can 26 | be merged automatically through normalization. 27 | 28 | ## 0.11.3 - 2018-04-19 29 | 30 | - Limit package size by publishing only the `dist` folder 31 | 32 | ## 0.11.2 - 2018-02-06 33 | 34 | ## 0.11.1 - 2018-02-06 35 | 36 | - Fixed build 37 | 38 | ## 0.11.0 - 2018-02-05 39 | 40 | - Upgrade to be compatible with Slate 0.32.x 41 | 42 | ## 0.10.3 - 2018-02-05 43 | 44 | - Fix: delete selection while pressing Enter in a list 45 | 46 | ## 0.10.2 - 2018-01-09 47 | 48 | - Wider slate peer dependency range 49 | 50 | ## 0.10.1 - 2017-11-08 51 | 52 | - Fix errors due to some unwanted normalizations in changes. 53 | 54 | ## 0.10.0 - 2017-11-07 55 | 56 | - Upgrade to be compatible with Slate 0.30.x 57 | 58 | ## 0.9.0 - 2017-11-06 59 | 60 | - Upgrade to be compatible with Slate 0.27.x 61 | 62 | ## 0.8.0 - 2017-09-20 63 | 64 | - Upgrade to be compatible with Slate after the `expose-transform` branch went in. 65 | - Change all instances of `transform` to `change` 66 | - Change the namespace of `plugin.transforms` to `plugin.changes` 67 | 68 | ## 0.7.1 - 2017-06-21 69 | 70 | - Add normalization rule to join adjacent lists of the same types. 71 | 72 | ## 0.7.0 - 2016-05-12 73 | 74 | - Add support for more than two list types through the option `types` 75 | - **BREAKING** Removed old options for `typeUL` and `typeOL`. 76 | - **BREAKING** `wrapInList` now takes a `type` param 77 | 78 | ## 0.6.4 - 2016-03-20 79 | 80 | - Now supports custom `data` for list containers. Added `data` parameter to `wrapInList`. 81 | 82 | ## 0.6.3 - 2016-03-20 83 | 84 | - Fix normalization of list items containing just one inline node. 85 | - List items normalization now wraps all their children in a single default block, when needed. 86 | 87 | ## 0.6.2 - 2016-02-14 88 | 89 | - `isSelectionInList` made smarter, adapted to fit the case where we can, and cannot, use `unwrapList`. 90 | 91 | ## 0.6.1 - 2016-02-14 92 | 93 | - `wrapInList` now wraps the highest blocks in the selection, and merge selected list together. 94 | - Fixed schema rule for list items containing list items. 95 | 96 | ## 0.6.0 - 2016-02-13 97 | 98 | - Added `utils.getItemsAtRange` 99 | - `wrapInList` now wraps the selected blocks in distinct items (not a big, single one) 100 | - `unwrapList` now unwraps all the selected items. Ignores complex ranges that span outside of lists. 101 | 102 | ## 0.5.7 - 2016-02-13 103 | 104 | - Fix item being unwrapped when hitting Enter in an empty block 105 | 106 | ## 0.5.6 - 2016-12-16 107 | 108 | - Fix bug with increasing item depth, that would reorder items. 109 | 110 | ## 0.5.5 - 2016-12-08 111 | 112 | - Fixed issue with selection when unwrapping 113 | 114 | ## 0.5.4 - 2016-12-08 115 | 116 | - Adapted for Slate@0.16.1 117 | - Fix unwrapping items with nested lists 118 | - Fix items being unwrapped when backspacing after an inline 119 | 120 | ## 0.5.3 - 2016-11-29 121 | 122 | - Fix slate peer dependency to be less restrictive 123 | 124 | ## 0.5.2 - 2016-11-29 125 | 126 | - Fix schema validation for content of list items 127 | 128 | ## 0.5.1 - 2016-11-03 129 | 130 | - Move slate to `peerDependencies` 131 | 132 | ## 0.4.2 - 2016-09-19 133 | 134 | - Use of this plugin with other container plugins (such as `slate-edit-blockquote`) 135 | 136 | ## 0.4.1 - 2016-09-15 137 | 138 | - Undo/Redo of `splitListItem` and `decreaseItemDepth` actions 139 | 140 | ## 0.4.0 - 2016-09-14 141 | 142 | - **BREAKING** Updated to `slate^0.14.x` 143 | -------------------------------------------------------------------------------- /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 | > ⚠️ This repository is archived and has moved to [GitBook's fork](https://github.com/GitbookIO/slate) of [ianstormtaylor/slate](https://github.com/ianstormtaylor/slate). 2 | > Previous versions are still [available on NPM](https://www.npmjs.com/package/slate-edit-table) 3 | > All the versions using GitBook's fork of slate are now published under the `@gitbook` NPM scope. 4 | > To learn more about why we forked Slate, read [our manifest](https://github.com/GitbookIO/slate/blob/master/Forked.md) 5 | 6 | # slate-edit-list 7 | 8 | [![NPM version](https://badge.fury.io/js/slate-edit-list.svg)](http://badge.fury.io/js/slate-edit-list) 9 | [![Linux Build Status](https://travis-ci.org/GitbookIO/slate-edit-list.png?branch=master)](https://travis-ci.org/GitbookIO/slate-edit-list) 10 | 11 | A Slate plugin to handle keyboard events in lists. List items can contain blocks. 12 | 13 | Demo: [gitbookio.github.io/slate-edit-list/](https://gitbookio.github.io/slate-edit-list/) 14 | 15 | ### Install 16 | 17 | ``` 18 | npm install slate-edit-list 19 | ``` 20 | 21 | ### Features 22 | 23 | Natural keybindings: 24 | 25 | - Pressing Enter insert a new list item 26 | - Pressing Shift+Enter split the block in the list item 27 | - Pressing Tab increase the depth of the item (creates a sub-list) 28 | - Pressing Shift+Tab decrease the depth of the item 29 | - Pressing Delete (OSX) or Backspace at the start, remove the list item (or the list) 30 | 31 | Simple validation/normalization (see [assumptions about the schema](#assumption-about-the-schema)): 32 | 33 | - Lists can contain only list items, and at least one. 34 | - List items can only be the direct children of a list. 35 | - List items must always contain blocks. 36 | 37 | Useful transforms: see [Utilities and Transform](#utilities-and-transform). 38 | 39 | ### Simple Usage 40 | 41 | ```js 42 | import EditList from 'slate-edit-list' 43 | 44 | const plugins = [ 45 | EditList() 46 | ] 47 | ``` 48 | 49 | #### Arguments 50 | 51 | This plugin accepts options to redefine the following block types: 52 | 53 | - `types: string = ["ol_list", "ul_list"]` — the array of possible types for list containers. First value will be used as default. 54 | - `typeItem: string = "list_item"` — type for list items. 55 | - `typeDefault: string = "paragraph"` — type for default block in list items. 56 | - `canMerge: (Node, Node) => boolean` — controls which list can be merged automatically (for example when they are adjacent). Defaults to merging list with identical types 57 | 58 | 59 | #### Assumption about the schema 60 | 61 | You can use this plugins with custom list block types (using plugin [arguments](#arguments)). But your lists structure should still conform to a few rules. These rules are implemented as schema. 62 | 63 | Here is what a minimal list would look like: 64 | 65 | 66 | ```yaml 67 | nodes: 68 | - kind: block 69 | type: ul_list # Default type for bulleted lists container 70 | nodes: 71 | - kind: block 72 | type: list_item # List containers can only contain list items 73 | nodes: 74 | # List items contain blocks. They cannot be the direct container of text. 75 | - kind: block 76 | type: paragraph # Default type of blocks in a list item 77 | nodes: 78 | - kind: text 79 | text: Hello World 80 | ``` 81 | 82 | And here is an example of a multi-level list: 83 | 84 | ```yaml 85 | nodes: 86 | - kind: block 87 | type: ol_list 88 | nodes: 89 | - kind: block 90 | type: list_item 91 | nodes: 92 | - kind: block 93 | type: paragraph 94 | nodes: 95 | - kind: text 96 | text: Item 1 97 | - kind: block 98 | type: ol_list 99 | nodes: 100 | - kind: block 101 | type: list_item 102 | nodes: 103 | - kind: block 104 | type: paragraph 105 | nodes: 106 | - kind: text 107 | text: Item 1.1 108 | - kind: block 109 | type: list_item 110 | nodes: 111 | - kind: block 112 | type: paragraph 113 | nodes: 114 | - kind: text 115 | text: Item 1.2 116 | ``` 117 | 118 | ### Utilities and Transform 119 | 120 | `slate-edit-list` exports utilities and transforms: 121 | 122 | #### `plugin.utils.isSelectionInList(value: Value, type?: string) => Boolean` 123 | 124 | Return true if selection is inside a list (and it can be unwrap). Optional param `type` can be supplied to deduce whether list is of specified type. 125 | 126 | #### `plugin.utils.isList(node: Node) => Boolean` 127 | 128 | Return true if the node is one of the list type. 129 | 130 | #### `plugin.utils.getItemDepth(value: Value, block: Block?) => Number` 131 | 132 | Returns the depth of the current item (or the depth of the given block) in a list. 0 means not in a list. 133 | 134 | #### `plugin.utils.getCurrentItem(value: Value, block: Block?) => Block || Void` 135 | 136 | Returns the current item at selection (or at the given block). 137 | 138 | #### `plugin.utils.getCurrentList(value: Value, block: Block?) => Block || Void` 139 | 140 | Returns the current list at selection (or at the given block). 141 | 142 | #### `plugin.utils.getItemsAtRange(value: Value, range: Selection?) => List` 143 | 144 | Return the list of items at the given range. The returned items are the highest list of of successive items that cover the given range. 145 | 146 | The returned list is empty if no such list can be found. 147 | 148 | #### `plugin.changes.increaseItemDepth(change: Change) => Transform` 149 | 150 | Increase the depth of the current item. 151 | 152 | #### `plugin.changes.decreaseItemDepth(change: Change) => Transform` 153 | 154 | Decrease the depth of the current item. 155 | 156 | #### `plugin.changes.wrapInList(change: Change, type: String?, data: Object|Data?) => Transform` 157 | 158 | Wrap the current blocks in list items of a list container of the given type. You can pass optional data for the created list container. 159 | 160 | #### `plugin.changes.unwrapList(change: Change) => Transform` 161 | 162 | Unwrap all items at range from their list. 163 | 164 | #### `plugin.changes.splitListItem(change: Change) => Transform` 165 | 166 | Split current block into a new list item. 167 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | last 2 Chrome versions 2 | last 2 Firefox versions 3 | last 2 Safari versions 4 | last 2 Edge versions 5 | IE 11 6 | >1% 7 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Slate • List 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 | } 6 | 7 | #example pre { 8 | background: #f5f5f5; 9 | padding: 10px; 10 | } 11 | 12 | button { 13 | cursor: pointer; 14 | border: 1px solid #ccc; 15 | padding: 6px; 16 | border-radius: 2px; 17 | margin: 0px; 18 | background: white; 19 | transition: background 0.3s; 20 | outline: none; 21 | } 22 | button:hover { 23 | background: #ccc; 24 | } 25 | 26 | button.active { 27 | background: #d9edff; 28 | } 29 | 30 | button.disabled { 31 | opacity: 0.4; 32 | cursor: auto; 33 | } 34 | 35 | .sep { 36 | margin: 15px; 37 | } 38 | 39 | .current-item { 40 | position: relative; 41 | } 42 | 43 | .current-item:after { 44 | position: absolute; 45 | content: ''; 46 | border: 2px solid #d9edff; 47 | left: -25px; 48 | height: 100%; 49 | top: 0; 50 | } 51 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global document */ 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import { Editor } from '@gitbook/slate-react'; 7 | 8 | import PluginEditList from '../lib/'; 9 | 10 | import INITIAL_VALUE from './value'; 11 | 12 | const plugin = PluginEditList(); 13 | const plugins = [plugin]; 14 | 15 | function renderNode(props: *) { 16 | const { node, attributes, children, editor } = props; 17 | const isCurrentItem = plugin.utils 18 | .getItemsAtRange(editor.value) 19 | .contains(node); 20 | 21 | switch (node.type) { 22 | case 'ul_list': 23 | return ; 24 | case 'ol_list': 25 | return
    {children}
; 26 | 27 | case 'list_item': 28 | return ( 29 |
  • 34 | {props.children} 35 |
  • 36 | ); 37 | 38 | case 'paragraph': 39 | return

    {children}

    ; 40 | case 'heading': 41 | return

    {children}

    ; 42 | default: 43 | return

    {children}

    ; 44 | } 45 | } 46 | 47 | class Example extends React.Component<*, *> { 48 | state = { 49 | value: INITIAL_VALUE 50 | }; 51 | 52 | renderToolbar() { 53 | const { 54 | wrapInList, 55 | unwrapList, 56 | increaseItemDepth, 57 | decreaseItemDepth 58 | } = plugin.changes; 59 | const inList = plugin.utils.isSelectionInList(this.state.value); 60 | 61 | return ( 62 |
    63 | 69 | 70 | 76 | 77 | 83 | 84 | · 85 | 86 | 89 | 92 |
    93 | ); 94 | } 95 | 96 | call(change) { 97 | this.setState({ 98 | value: this.state.value.change().call(change).value 99 | }); 100 | } 101 | 102 | onChange = ({ value }) => { 103 | this.setState({ 104 | value 105 | }); 106 | }; 107 | 108 | render() { 109 | return ( 110 |
    111 | {this.renderToolbar()} 112 | 119 | // To update the highlighting of nodes inside the selection 120 | props.node.type === 'list_item' 121 | } 122 | /> 123 |
    124 | ); 125 | } 126 | } 127 | 128 | // $FlowFixMe 129 | ReactDOM.render(, document.getElementById('example')); 130 | -------------------------------------------------------------------------------- /example/value.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | // eslint-disable-next-line import/no-extraneous-dependencies 3 | import { createHyperscript } from '@gitbook/slate-hyperscript'; 4 | 5 | const h = createHyperscript({ 6 | blocks: { 7 | heading: 'heading', 8 | paragraph: 'paragraph', 9 | ul_list: 'ul_list', 10 | ol_list: 'ol_list', 11 | list_item: 'list_item' 12 | } 13 | }); 14 | 15 | export default ( 16 | 17 | 18 | Slate + List Edition 19 | 20 | This page is a basic example of Slate + slate-edit-list plugin. 21 | Press Enter in a list to create a new list item. Press Enter 22 | again to exit and Shift+Enter to create a paragraph in a list. 23 | The items at range are detected and highlighted, for 24 | demonstration purpose. 25 | 26 | 27 | 28 | First item in the list 29 | 30 | 31 | List item can contain blocks 32 | Here is a heading 33 | And another paragraph 34 | 35 | 36 | 37 | Third item in the list, with a nested list 38 | 39 | 40 | 41 | First item in the nested list 42 | 43 | 44 | 45 | Second item in the nested list 46 | 47 | 48 | 49 | 50 | 51 | End paragraph 52 | 53 | 54 | ); 55 | -------------------------------------------------------------------------------- /lib/changes/decreaseItemDepth.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Block, type Change } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import { getItemDepth, getCurrentItem } from '../utils'; 6 | 7 | /** 8 | * Decreases the depth of the current item. The following items will 9 | * be moved as sublist of the decreased item. 10 | * 11 | * No-op for root items. 12 | */ 13 | function decreaseItemDepth(opts: Options, change: Change): Change { 14 | const { value } = change; 15 | const { document } = value; 16 | 17 | // Cannot decrease item depth of root items 18 | const depth = getItemDepth(opts, value); 19 | if (depth == 1) { 20 | return change; 21 | } 22 | 23 | const currentItem = getCurrentItem(opts, value); 24 | if (!currentItem) { 25 | return change; 26 | } 27 | 28 | const currentList = document.getParent(currentItem.key); 29 | const parentItem = document.getParent(currentList.key); 30 | const parentList = document.getParent(parentItem.key); 31 | // The items following the item will be moved to a sublist of currentItem 32 | const followingItems = currentList.nodes 33 | .skipUntil(i => i === currentItem) 34 | .rest(); 35 | 36 | // True if the currentItem and the followingItems make the whole 37 | // currentList, and hence the currentList will be emptied 38 | const willEmptyCurrentList = 39 | currentList.nodes.size === followingItems.size + 1; 40 | 41 | if (!followingItems.isEmpty()) { 42 | // Add them as sublist of currentItem 43 | const sublist = Block.create({ 44 | object: 'block', 45 | type: currentList.type, 46 | data: currentList.data 47 | }); 48 | // Add the sublist 49 | change.insertNodeByKey( 50 | currentItem.key, 51 | currentItem.nodes.size, 52 | sublist, 53 | { normalize: false } 54 | ); 55 | 56 | change.moveNodeByKey( 57 | currentItem.key, 58 | parentList.key, 59 | parentList.nodes.indexOf(parentItem) + 1, 60 | { normalize: false } 61 | ); 62 | 63 | // Move the followingItems to the sublist 64 | followingItems.forEach((item, index) => 65 | change.moveNodeByKey( 66 | item.key, 67 | sublist.key, 68 | sublist.nodes.size + index, 69 | { normalize: false } 70 | ) 71 | ); 72 | } else { 73 | change.moveNodeByKey( 74 | currentItem.key, 75 | parentList.key, 76 | parentList.nodes.indexOf(parentItem) + 1 77 | ); 78 | } 79 | 80 | // Remove the currentList completely if needed 81 | if (willEmptyCurrentList) { 82 | change.removeNodeByKey(currentList.key); 83 | } 84 | 85 | return change; 86 | } 87 | 88 | export default decreaseItemDepth; 89 | -------------------------------------------------------------------------------- /lib/changes/increaseItemDepth.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Block, type Change } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import { 6 | getPreviousItem, 7 | getCurrentItem, 8 | getListForItem, 9 | isList 10 | } from '../utils'; 11 | 12 | /** 13 | * Increase the depth of the current item by putting it in a sub-list 14 | * of previous item. 15 | * For first items in a list, does nothing. 16 | */ 17 | function increaseItemDepth(opts: Options, change: Change): Change { 18 | const previousItem = getPreviousItem(opts, change.value); 19 | const currentItem = getCurrentItem(opts, change.value); 20 | 21 | if (!previousItem) { 22 | return change; 23 | } 24 | 25 | if (!currentItem) { 26 | return change; 27 | } 28 | 29 | // Move the item in the sublist of previous item 30 | return moveAsSubItem(opts, change, currentItem, previousItem.key); 31 | } 32 | 33 | /** 34 | * Move the given item to the sublist at the end of destination item, 35 | * creating a sublist if needed. 36 | */ 37 | function moveAsSubItem( 38 | opts: Options, 39 | change: Change, 40 | // The list item to add 41 | item: Block, 42 | // The key of the destination node 43 | destKey: string 44 | ): Change { 45 | const destination = change.value.document.getDescendant(destKey); 46 | const lastIndex = destination.nodes.size; 47 | const lastChild = destination.nodes.last(); 48 | 49 | // The potential existing last child list 50 | const existingList = isList(opts, lastChild) ? lastChild : null; 51 | 52 | if (existingList) { 53 | return change.moveNodeByKey( 54 | item.key, 55 | existingList.key, 56 | existingList.nodes.size // as last item 57 | ); 58 | } 59 | const currentList = getListForItem(opts, change.value, destination); 60 | if (!currentList) { 61 | throw new Error('Destination is not in a list'); 62 | } 63 | 64 | const newSublist = Block.create({ 65 | object: 'block', 66 | type: currentList.type, 67 | data: currentList.data 68 | }); 69 | 70 | change.insertNodeByKey(destKey, lastIndex, newSublist, { 71 | normalize: false 72 | }); 73 | 74 | return change.moveNodeByKey(item.key, newSublist.key, 0); 75 | } 76 | 77 | export default increaseItemDepth; 78 | -------------------------------------------------------------------------------- /lib/changes/index.js: -------------------------------------------------------------------------------- 1 | import wrapInList from './wrapInList'; 2 | import unwrapList from './unwrapList'; 3 | import splitListItem from './splitListItem'; 4 | import increaseItemDepth from './increaseItemDepth'; 5 | import decreaseItemDepth from './decreaseItemDepth'; 6 | 7 | export { 8 | wrapInList, 9 | unwrapList, 10 | splitListItem, 11 | increaseItemDepth, 12 | decreaseItemDepth 13 | }; 14 | -------------------------------------------------------------------------------- /lib/changes/splitListItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Change } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import { getCurrentItem } from '../utils'; 6 | 7 | /** 8 | * Split a list item at the start of the current range. 9 | */ 10 | function splitListItem(opts: Options, change: Change): Change { 11 | const { value } = change; 12 | const currentItem = getCurrentItem(opts, value); 13 | if (!currentItem) { 14 | return change; 15 | } 16 | 17 | const splitOffset = value.startOffset; 18 | 19 | return change.splitDescendantsByKey( 20 | currentItem.key, 21 | value.startKey, 22 | splitOffset 23 | ); 24 | } 25 | 26 | export default splitListItem; 27 | -------------------------------------------------------------------------------- /lib/changes/unwrapList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Change } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import { getItemsAtRange } from '../utils'; 6 | 7 | /** 8 | * Unwrap items at range from their list. 9 | */ 10 | function unwrapList(opts: Options, change: Change): Change { 11 | const items = getItemsAtRange(opts, change.value); 12 | 13 | if (items.isEmpty()) { 14 | return change; 15 | } 16 | 17 | // Unwrap the items from their list 18 | items.forEach(item => 19 | change.unwrapNodeByKey(item.key, { normalize: false }) 20 | ); 21 | 22 | // Parent of the list of the items 23 | const firstItem = items.first(); 24 | const parent = change.value.document.getParent(firstItem.key); 25 | 26 | let index = parent.nodes.findIndex(node => node.key === firstItem.key); 27 | 28 | // Unwrap the items' children 29 | items.forEach(item => { 30 | item.nodes.forEach(node => { 31 | change.moveNodeByKey(node.key, parent.key, index, { 32 | normalize: false 33 | }); 34 | index += 1; 35 | }); 36 | }); 37 | 38 | // Finally, remove the now empty items 39 | items.forEach(item => 40 | change.removeNodeByKey(item.key, { normalize: false }) 41 | ); 42 | 43 | return change; 44 | } 45 | 46 | export default unwrapList; 47 | -------------------------------------------------------------------------------- /lib/changes/wrapInList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Data, type Value, type Change, type Block } from '@gitbook/slate'; 3 | import { List } from 'immutable'; 4 | 5 | import type Options from '../options'; 6 | import { isList } from '../utils'; 7 | 8 | /** 9 | * Wrap the blocks in the current selection in a new list. Selected 10 | * lists are merged together. 11 | */ 12 | function wrapInList( 13 | opts: Options, 14 | change: Change, 15 | type?: string, 16 | data?: Object | Data 17 | ): Change { 18 | const selectedBlocks = getHighestSelectedBlocks(change.value); 19 | type = type || opts.types[0]; 20 | 21 | // Wrap in container 22 | change.wrapBlock( 23 | { 24 | type, 25 | data: Data.create(data) 26 | }, 27 | { normalize: false } 28 | ); 29 | 30 | // Wrap in list items 31 | selectedBlocks.forEach(node => { 32 | if (isList(opts, node)) { 33 | // Merge its items with the created list 34 | node.nodes.forEach(({ key }) => 35 | change.unwrapNodeByKey(key, { normalize: false }) 36 | ); 37 | } else { 38 | change.wrapBlockByKey(node.key, opts.typeItem, { 39 | normalize: false 40 | }); 41 | } 42 | }); 43 | 44 | return change.normalize(); 45 | } 46 | 47 | /** 48 | * Returns the highest list of blocks that cover the current selection 49 | */ 50 | function getHighestSelectedBlocks(value: Value): List { 51 | const range = value.selection; 52 | const { document } = value; 53 | 54 | const startBlock = document.getClosestBlock(range.startKey); 55 | const endBlock = document.getClosestBlock(range.endKey); 56 | 57 | if (startBlock === endBlock) { 58 | return List([startBlock]); 59 | } 60 | const ancestor = document.getCommonAncestor(startBlock.key, endBlock.key); 61 | const startPath = ancestor.getPath(startBlock.key); 62 | const endPath = ancestor.getPath(endBlock.key); 63 | 64 | return ancestor.nodes.slice(startPath[0], endPath[0] + 1); 65 | } 66 | 67 | export default wrapInList; 68 | -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import Options, { type OptionsFormat } from './options'; 3 | import { schema, validateNode } from './validation'; 4 | import { 5 | wrapInList, 6 | unwrapList, 7 | splitListItem, 8 | increaseItemDepth, 9 | decreaseItemDepth 10 | } from './changes'; 11 | import { 12 | getItemDepth, 13 | isList, 14 | isSelectionInList, 15 | getCurrentItem, 16 | getCurrentList, 17 | getItemsAtRange, 18 | getPreviousItem 19 | } from './utils'; 20 | 21 | /** 22 | * Returns the core of the plugin, limited to the validation and normalization 23 | * part of `slate-edit-list`, and utils. 24 | * 25 | * Import this directly: `import EditListCore from '@gitbook/slate-edit-table/lib/core'` 26 | * if you don't care about behavior/rendering. 27 | */ 28 | function core( 29 | // Options for the plugin 30 | opts: OptionsFormat = {} 31 | ): Object { 32 | opts = new Options(opts); 33 | 34 | return { 35 | schema: schema(opts), 36 | validateNode: validateNode(opts), 37 | 38 | utils: { 39 | getCurrentItem: getCurrentItem.bind(null, opts), 40 | getCurrentList: getCurrentList.bind(null, opts), 41 | getItemDepth: getItemDepth.bind(null, opts), 42 | getItemsAtRange: getItemsAtRange.bind(null, opts), 43 | getPreviousItem: getPreviousItem.bind(null, opts), 44 | isList: isList.bind(null, opts), 45 | isSelectionInList: isSelectionInList.bind(null, opts) 46 | }, 47 | 48 | changes: { 49 | decreaseItemDepth: bindAndScopeChange(opts, decreaseItemDepth), 50 | increaseItemDepth: bindAndScopeChange(opts, increaseItemDepth), 51 | splitListItem: bindAndScopeChange(opts, splitListItem), 52 | unwrapList: bindAndScopeChange(opts, unwrapList), 53 | wrapInList: wrapInList.bind(null, opts) 54 | } 55 | }; 56 | } 57 | 58 | /** 59 | * Bind a change to given options, and scope it to act only inside a list 60 | */ 61 | function bindAndScopeChange(opts: Options, fn: *): * { 62 | return (change, ...args) => { 63 | const { value } = change; 64 | 65 | if (!isSelectionInList(opts, value)) { 66 | return change; 67 | } 68 | 69 | // $FlowFixMe 70 | return fn(...[opts, change].concat(args)); 71 | }; 72 | } 73 | 74 | export default core; 75 | -------------------------------------------------------------------------------- /lib/handlers/index.js: -------------------------------------------------------------------------------- 1 | import onEnter from './onEnter'; 2 | import onTab from './onTab'; 3 | import onBackspace from './onBackspace'; 4 | 5 | export { onEnter, onTab, onBackspace }; 6 | -------------------------------------------------------------------------------- /lib/handlers/onBackspace.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Change } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import { unwrapList } from '../changes'; 6 | import { getCurrentItem } from '../utils'; 7 | 8 | /** 9 | * User pressed Delete in an editor 10 | */ 11 | function onBackspace( 12 | event: *, 13 | change: Change, 14 | editor: *, 15 | opts: Options 16 | ): void | any { 17 | const { value } = change; 18 | const { startOffset, selection } = value; 19 | 20 | // Only unwrap... 21 | // ... with a collapsed selection 22 | if (selection.isExpanded) { 23 | return undefined; 24 | } 25 | 26 | // ... when at the beginning of nodes 27 | if (startOffset > 0) { 28 | return undefined; 29 | } 30 | // ... in a list 31 | const currentItem = getCurrentItem(opts, value); 32 | if (!currentItem) { 33 | return undefined; 34 | } 35 | // ... more precisely at the beginning of the current item 36 | if (!selection.isAtStartOf(currentItem)) { 37 | return undefined; 38 | } 39 | 40 | event.preventDefault(); 41 | return unwrapList(opts, change); 42 | } 43 | 44 | export default onBackspace; 45 | -------------------------------------------------------------------------------- /lib/handlers/onEnter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Change } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import { unwrapList, splitListItem, decreaseItemDepth } from '../changes'; 6 | import { getCurrentItem, getItemDepth } from '../utils'; 7 | 8 | /** 9 | * User pressed Enter in an editor 10 | * 11 | * Enter in a list item should split the list item 12 | * Enter in an empty list item should remove it 13 | * Shift+Enter in a list item should make a new line 14 | */ 15 | function onEnter( 16 | event: *, 17 | change: Change, 18 | editor: *, 19 | opts: Options 20 | ): void | any { 21 | // Pressing Shift+Enter 22 | // should split block normally 23 | if (event.shiftKey) { 24 | return undefined; 25 | } 26 | 27 | const { value } = change; 28 | const currentItem = getCurrentItem(opts, value); 29 | 30 | // Not in a list 31 | if (!currentItem) { 32 | return undefined; 33 | } 34 | 35 | event.preventDefault(); 36 | 37 | // If expanded, delete first. 38 | if (value.isExpanded) { 39 | change.delete(); 40 | } 41 | 42 | if (currentItem.isEmpty) { 43 | // Block is empty, we exit the list 44 | if (getItemDepth(opts, value) > 1) { 45 | return decreaseItemDepth(opts, change); 46 | } 47 | // Exit list 48 | return unwrapList(opts, change); 49 | } 50 | // Split list item 51 | return splitListItem(opts, change); 52 | } 53 | 54 | export default onEnter; 55 | -------------------------------------------------------------------------------- /lib/handlers/onTab.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Change } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import { decreaseItemDepth, increaseItemDepth } from '../changes'; 6 | import { getCurrentItem } from '../utils'; 7 | 8 | /** 9 | * User pressed Tab in an editor. 10 | * Tab -> Increase item depth if inside a list item 11 | * Shift+Tab -> Decrease item depth if inside a list item 12 | */ 13 | function onTab(event: *, change: Change, editor: *, opts: Options): void | any { 14 | const { value } = change; 15 | const { isCollapsed } = value; 16 | 17 | if (!isCollapsed || !getCurrentItem(opts, value)) { 18 | return undefined; 19 | } 20 | 21 | // Shift+tab reduce depth 22 | if (event.shiftKey) { 23 | event.preventDefault(); 24 | 25 | return decreaseItemDepth(opts, change); 26 | } 27 | 28 | // Tab increases depth 29 | event.preventDefault(); 30 | 31 | return increaseItemDepth(opts, change); 32 | } 33 | 34 | export default onTab; 35 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import Options, { type OptionsFormat } from './options'; 3 | import { onEnter, onTab, onBackspace } from './handlers'; 4 | import core from './core'; 5 | 6 | const KEY_ENTER = 'Enter'; 7 | const KEY_TAB = 'Tab'; 8 | const KEY_BACKSPACE = 'Backspace'; 9 | 10 | /** 11 | * A Slate plugin to handle keyboard events in lists. 12 | */ 13 | function EditList( 14 | // Options for the plugin 15 | opts: OptionsFormat = {} 16 | ): Object { 17 | opts = new Options(opts); 18 | const corePlugin = core(opts); 19 | 20 | return { 21 | ...corePlugin, 22 | 23 | onKeyDown: onKeyDown.bind(null, opts) 24 | }; 25 | } 26 | 27 | /** 28 | * User is pressing a key in the editor 29 | */ 30 | function onKeyDown(opts: Options, event, change, editor: *): void | any { 31 | const args = [event, change, editor, opts]; 32 | 33 | switch (event.key) { 34 | case KEY_ENTER: 35 | return onEnter(...args); 36 | case KEY_TAB: 37 | return onTab(...args); 38 | case KEY_BACKSPACE: 39 | return onBackspace(...args); 40 | default: 41 | return undefined; 42 | } 43 | } 44 | 45 | export default EditList; 46 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Node } from '@gitbook/slate'; 3 | import { Record } from 'immutable'; 4 | 5 | export type OptionsFormat = { 6 | types?: string[], 7 | typeItem?: string, 8 | typeDefault?: string, 9 | canMerge?: (listA: Node, listB: Node) => boolean 10 | }; 11 | 12 | /** 13 | * The plugin options 14 | */ 15 | class Options extends Record({ 16 | types: ['ul_list', 'ol_list'], 17 | typeItem: 'list_item', 18 | typeDefault: 'paragraph', 19 | canMerge: (a: Node, b: Node) => a.type === b.type 20 | }) { 21 | // The possibles types for list containers 22 | types: string[]; 23 | // The type of list items 24 | typeItem: string; 25 | // The type of default block in items 26 | typeDefault: string; 27 | // You can control here the automatic merging of adjacent lists 28 | canMerge: (listA: Node, listB: Node) => boolean; 29 | } 30 | 31 | export default Options; 32 | -------------------------------------------------------------------------------- /lib/utils/getCurrentItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Value, type Block } from '@gitbook/slate'; 3 | import type Options from '../options'; 4 | 5 | /** 6 | * Return the current list item, from current selection or from a node. 7 | */ 8 | function getCurrentItem(opts: Options, value: Value, block?: Block): ?Block { 9 | const { document } = value; 10 | 11 | if (!block) { 12 | if (!value.selection.startKey) return null; 13 | block = value.startBlock; 14 | } 15 | 16 | const parent = document.getParent(block.key); 17 | return parent && parent.type === opts.typeItem ? parent : null; 18 | } 19 | 20 | export default getCurrentItem; 21 | -------------------------------------------------------------------------------- /lib/utils/getCurrentList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Value, type Block } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import getCurrentItem from './getCurrentItem'; 6 | import getListForItem from './getListForItem'; 7 | 8 | /** 9 | * Return the parent list block, from current selection or from a node (paragraph in a list item). 10 | */ 11 | function getCurrentList(opts: Options, value: Value, block?: Block): ?Block { 12 | const item = getCurrentItem(opts, value, block); 13 | 14 | if (!item) { 15 | return null; 16 | } 17 | 18 | return getListForItem(opts, value, item); 19 | } 20 | 21 | export default getCurrentList; 22 | -------------------------------------------------------------------------------- /lib/utils/getItemDepth.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Value, type Block } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import getCurrentItem from './getCurrentItem'; 6 | 7 | /** 8 | * Get depth of current block in a document list 9 | */ 10 | function getItemDepth(opts: Options, value: Value, block?: Block): number { 11 | const { document, startBlock } = value; 12 | block = block || startBlock; 13 | 14 | const currentItem = getCurrentItem(opts, value, block); 15 | if (!currentItem) { 16 | return 0; 17 | } 18 | 19 | const list = document.getParent(currentItem.key); 20 | 21 | return 1 + getItemDepth(opts, value, list); 22 | } 23 | 24 | export default getItemDepth; 25 | -------------------------------------------------------------------------------- /lib/utils/getItemsAtRange.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Value, type Block, type Range } from '@gitbook/slate'; 3 | import { List } from 'immutable'; 4 | 5 | import type Options from '../options'; 6 | import isList from './isList'; 7 | import getCurrentItem from './getCurrentItem'; 8 | 9 | /** 10 | * Return the list of items at the given range. The returned items are 11 | * the highest list item blocks that cover the range. 12 | * 13 | * Returns an empty list if no list of items can cover the range 14 | */ 15 | function getItemsAtRange( 16 | opts: Options, 17 | value: Value, 18 | range?: Range 19 | ): List { 20 | range = range || value.selection; 21 | 22 | if (!range.startKey) { 23 | return List(); 24 | } 25 | 26 | const { document } = value; 27 | 28 | const startBlock = document.getClosestBlock(range.startKey); 29 | const endBlock = document.getClosestBlock(range.endKey); 30 | 31 | if (startBlock === endBlock) { 32 | const item = getCurrentItem(opts, value, startBlock); 33 | return item ? List([item]) : List(); 34 | } 35 | 36 | const ancestor = document.getCommonAncestor(startBlock.key, endBlock.key); 37 | 38 | if (isList(opts, ancestor)) { 39 | const startPath = ancestor.getPath(startBlock.key); 40 | const endPath = ancestor.getPath(endBlock.key); 41 | 42 | return ancestor.nodes.slice(startPath[0], endPath[0] + 1); 43 | } else if (ancestor.type === opts.typeItem) { 44 | // The ancestor is the highest list item that covers the range 45 | return List([ancestor]); 46 | } 47 | // No list of items can cover the range 48 | return List(); 49 | } 50 | 51 | export default getItemsAtRange; 52 | -------------------------------------------------------------------------------- /lib/utils/getListForItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Value, type Block } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import isList from './isList'; 6 | 7 | /** 8 | * Return the parent list block for an item block. 9 | */ 10 | function getListForItem(opts: Options, value: Value, item: Block): ?Block { 11 | const { document } = value; 12 | const parent = document.getParent(item.key); 13 | return parent && isList(opts, parent) ? parent : null; 14 | } 15 | 16 | export default getListForItem; 17 | -------------------------------------------------------------------------------- /lib/utils/getPreviousItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Value, type Block } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import getCurrentItem from './getCurrentItem'; 6 | 7 | /** 8 | * Return the previous item, from current selection or from a node. 9 | */ 10 | function getPreviousItem(opts: Options, value: Value, block?: Block): ?Block { 11 | const { document, startBlock } = value; 12 | block = block || startBlock; 13 | 14 | const currentItem = getCurrentItem(opts, value, block); 15 | if (!currentItem) { 16 | return null; 17 | } 18 | 19 | const previousSibling = document.getPreviousSibling(currentItem.key); 20 | 21 | if (!previousSibling) { 22 | return null; 23 | } else if (previousSibling.type === opts.typeItem) { 24 | return previousSibling; 25 | } 26 | return null; 27 | } 28 | 29 | export default getPreviousItem; 30 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | import getCurrentItem from './getCurrentItem'; 2 | import getCurrentList from './getCurrentList'; 3 | import getItemDepth from './getItemDepth'; 4 | import getItemsAtRange from './getItemsAtRange'; 5 | import getListForItem from './getListForItem'; 6 | import getPreviousItem from './getPreviousItem'; 7 | import isList from './isList'; 8 | import isSelectionInList from './isSelectionInList'; 9 | 10 | export { 11 | getCurrentItem, 12 | getCurrentList, 13 | getItemDepth, 14 | getItemsAtRange, 15 | getListForItem, 16 | getPreviousItem, 17 | isList, 18 | isSelectionInList 19 | }; 20 | -------------------------------------------------------------------------------- /lib/utils/isList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Node } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | 6 | /** 7 | * True if the node is a list container 8 | */ 9 | function isList(opts: Options, node: Node): boolean { 10 | return opts.types.includes(node.type); 11 | } 12 | 13 | export default isList; 14 | -------------------------------------------------------------------------------- /lib/utils/isSelectionInList.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Value } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | import getItemsAtRange from './getItemsAtRange'; 6 | import getListForItem from './getListForItem'; 7 | 8 | /** 9 | * True if selection is inside a list (and can be unwrapped) 10 | */ 11 | function isSelectionInList( 12 | opts: Options, 13 | value: Value, 14 | type?: string 15 | ): boolean { 16 | const items = getItemsAtRange(opts, value); 17 | return ( 18 | !items.isEmpty() && 19 | // Check the type of the list if needed 20 | (!type || 21 | getListForItem(opts, value, items.first()).get('type') === type) 22 | ); 23 | } 24 | 25 | export default isSelectionInList; 26 | -------------------------------------------------------------------------------- /lib/validation/index.js: -------------------------------------------------------------------------------- 1 | import schema from './schema'; 2 | import validateNode from './validateNode'; 3 | 4 | export { schema, validateNode }; 5 | -------------------------------------------------------------------------------- /lib/validation/schema.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Change, type Node } from '@gitbook/slate'; 3 | 4 | import type Options from '../options'; 5 | 6 | /** 7 | * Create a schema definition with rules to normalize lists 8 | */ 9 | function schema(opts: Options): Object { 10 | const constructedSchema = { 11 | blocks: { 12 | [opts.typeItem]: { 13 | parent: { types: opts.types }, 14 | nodes: [{ objects: ['block'] }], 15 | 16 | normalize: normalize({ 17 | parent_type_invalid: (change, context) => 18 | change.unwrapBlockByKey(context.node.key, { 19 | normalize: false 20 | }), 21 | child_object_invalid: (change, context) => 22 | wrapChildrenInDefaultBlock(opts, change, context.node) 23 | }) 24 | } 25 | } 26 | }; 27 | 28 | // validate all list types, ensure they only have list item children 29 | opts.types.forEach(type => { 30 | constructedSchema.blocks[type] = { 31 | nodes: [{ types: [opts.typeItem] }], 32 | normalize: normalize({ 33 | child_type_invalid: (change, context) => 34 | change.wrapBlockByKey(context.child.key, opts.typeItem, { 35 | normalize: false 36 | }) 37 | }) 38 | }; 39 | }); 40 | 41 | return constructedSchema; 42 | } 43 | 44 | /* 45 | * Allows to define a normalize function through a keyed collection of functions 46 | */ 47 | function normalize(reasons: { [string]: (Change, context: any) => any }): * { 48 | return (change, reason, context) => { 49 | const reasonFn = reasons[reason]; 50 | if (reasonFn) { 51 | reasonFn(change, context); 52 | } 53 | }; 54 | } 55 | 56 | /** 57 | * Wraps all child of a node in the default block type. 58 | * Returns a change, for chaining purposes 59 | */ 60 | function wrapChildrenInDefaultBlock( 61 | opts: Options, 62 | change: Change, 63 | node: Node 64 | ): Change { 65 | change.wrapBlockByKey(node.nodes.first().key, opts.typeDefault, { 66 | normalize: false 67 | }); 68 | 69 | const wrapper = change.value.document.getDescendant(node.key).nodes.first(); 70 | 71 | // Add in the remaining items 72 | node.nodes.rest().forEach((child, index) => 73 | change.moveNodeByKey(child.key, wrapper.key, index + 1, { 74 | normalize: false 75 | }) 76 | ); 77 | 78 | return change; 79 | } 80 | 81 | export default schema; 82 | -------------------------------------------------------------------------------- /lib/validation/validateNode.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { type Change, type Node } from '@gitbook/slate'; 3 | 4 | import { isList } from '../utils'; 5 | import type Options from '../options'; 6 | 7 | type Normalizer = Change => any; 8 | 9 | /** 10 | * Create a schema definition with rules to normalize lists 11 | */ 12 | function validateNode(opts: Options): Node => void | Normalizer { 13 | return node => joinAdjacentLists(opts, node); 14 | } 15 | 16 | /** 17 | * A rule that joins adjacent lists of the same type 18 | */ 19 | function joinAdjacentLists(opts: Options, node: Node): void | Normalizer { 20 | if (node.object !== 'document' && node.object !== 'block') { 21 | return undefined; 22 | } 23 | 24 | const invalids = node.nodes 25 | .map((child, i) => { 26 | if (!isList(opts, child)) return null; 27 | const next = node.nodes.get(i + 1); 28 | if (!next || !isList(opts, next) || !opts.canMerge(child, next)) { 29 | return null; 30 | } 31 | 32 | return [child, next]; 33 | }) 34 | .filter(Boolean); 35 | 36 | if (invalids.isEmpty()) { 37 | return undefined; 38 | } 39 | 40 | /** 41 | * Join the list pairs 42 | */ 43 | // We join in reverse order, so that multiple lists folds onto the first one 44 | return change => { 45 | invalids.reverse().forEach(pair => { 46 | const [first, second] = pair; 47 | const updatedSecond = change.value.document.getDescendant( 48 | second.key 49 | ); 50 | updatedSecond.nodes.forEach((secondNode, index) => { 51 | change.moveNodeByKey( 52 | secondNode.key, 53 | first.key, 54 | first.nodes.size + index, 55 | { normalize: false } 56 | ); 57 | }); 58 | 59 | change.removeNodeByKey(second.key, { normalize: false }); 60 | }); 61 | }; 62 | } 63 | 64 | export default validateNode; 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitbook/slate-edit-list", 3 | "description": "A Slate plugin to handle keyboard events in lists.", 4 | "version": "0.12.3", 5 | "license": "Apache-2.0", 6 | "repository": "git://github.com/GitbookIO/slate-edit-list.git", 7 | "main": "./dist/index.js", 8 | "peerDependencies": { 9 | "immutable": "^3.8.2 || ^4.0.0-rc.12", 10 | "@gitbook/slate": "^0.34.7" 11 | }, 12 | "files": [ 13 | "dist", 14 | "*.md" 15 | ], 16 | "devDependencies": { 17 | "babel-cli": "^6.26.0", 18 | "babel-core": "^6.26.0", 19 | "babel-eslint": "^8.0.2", 20 | "babel-plugin-module-resolver": "^3.1.1", 21 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 22 | "babel-plugin-transform-runtime": "^6.23.0", 23 | "babel-preset-env": "^1.6.1", 24 | "babel-preset-flow": "^6.23.0", 25 | "babel-preset-react": "^6.24.1", 26 | "babel-preset-stage-0": "^6.24.1", 27 | "babelify": "^8.0.0", 28 | "browserify": "^13.3.0", 29 | "eslint": "^4.15.0", 30 | "eslint-config-gitbook": "4.0.0", 31 | "eslint-import-resolver-babel-module": "^4.0.0", 32 | "eslint-plugin-flowtype": "^2.48.0", 33 | "eslint-plugin-import": "^2.12.0", 34 | "eslint-plugin-jsx-a11y": "^6.0.3", 35 | "eslint-plugin-prettier": "^2.6.0", 36 | "eslint-plugin-react": "^7.8.2", 37 | "expect": "^1.20.2", 38 | "flow-bin": "^0.57.3", 39 | "flow-copy-source": "^1.2.1", 40 | "gh-pages": "^0.11.0", 41 | "http-server": "^0.9.0", 42 | "immutable": "^4.0.0-rc.12", 43 | "mocha": "^3.0.1", 44 | "prettier": "^1.13.3", 45 | "react": "^16.0.0", 46 | "react-dom": "^16.0.0", 47 | "@gitbook/slate": "^0.34.11", 48 | "slate-hyperprint": "^2.2.4", 49 | "@gitbook/slate-hyperscript": "^0.5.26", 50 | "@gitbook/slate-react": "^0.13.9", 51 | "stringify": "^5.1.0" 52 | }, 53 | "scripts": { 54 | "build:dist": "rm -r ./dist; babel ./lib --out-dir ./dist", 55 | "build:flow": "flow-copy-source -v ./lib/ ./dist/", 56 | "prepublish": "npm run build:dist && npm run build:flow", 57 | "postpublish": "npm run deploy-example", 58 | "lint": "eslint ./", 59 | "build-example": "browserify ./example/main.js -o ./example/bundle.js -t [ babelify --presets [ env react stage-0 ] ] -t [ stringify --extensions [.yaml] ]", 60 | "serve-example": "http-server ./example/ -p 8081", 61 | "start": "npm run build-example && npm run serve-example", 62 | "deploy-example": "npm run build-example && gh-pages -d ./example", 63 | "test": "./node_modules/.bin/mocha ./tests/all.js --compilers js:babel-register --reporter=list" 64 | }, 65 | "keywords": [ 66 | "slate" 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /tests/all.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import expect from 'expect'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | import Slate from '@gitbook/slate'; 7 | import hyperprint from 'slate-hyperprint'; 8 | 9 | import EditList from '../lib'; 10 | 11 | // Provide the value with 12 | function deserializeValue(plugin, value) { 13 | const SCHEMA = Slate.Schema.create({ 14 | plugins: [plugin] 15 | }); 16 | 17 | return Slate.Value.fromJS( 18 | { 19 | selection: value.selection, 20 | document: value.document, 21 | schema: SCHEMA 22 | }, 23 | { normalize: false } 24 | ); 25 | } 26 | 27 | describe('slate-edit-list', () => { 28 | const tests = fs.readdirSync(__dirname); 29 | 30 | tests.forEach((test, index) => { 31 | if (test[0] === '.' || path.extname(test).length > 0) return; 32 | it(test, () => { 33 | const dir = path.resolve(__dirname, test); 34 | const plugin = EditList(); 35 | 36 | const input = deserializeValue( 37 | plugin, 38 | require(path.resolve(dir, 'input.js')).default 39 | ); 40 | 41 | const expectedPath = path.resolve(dir, 'expected.js'); 42 | const expected = 43 | fs.existsSync(expectedPath) && 44 | deserializeValue(plugin, require(expectedPath).default); 45 | 46 | const runChange = require(path.resolve(dir, 'change.js')).default; 47 | 48 | const newChange = runChange(plugin, input.change()); 49 | 50 | if (expected) { 51 | const actual = newChange.value; 52 | 53 | expect(hyperprint(actual, { strict: true })).toEqual( 54 | hyperprint(expected, { strict: true }) 55 | ); 56 | } 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/backspace-empty-between-inline/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | plugin.onKeyDown( 5 | { 6 | preventDefault: () => {}, 7 | stopPropagation: () => {}, 8 | key: 'Backspace' 9 | }, 10 | change, 11 | {} 12 | ); 13 | 14 | // Selection check 15 | expect(change.value.startBlock.text).toEqual(''); 16 | expect(change.value.selection.anchorOffset).toEqual(0); 17 | expect(change.value.selection.isCollapsed).toBe(true); 18 | 19 | return change; 20 | } 21 | -------------------------------------------------------------------------------- /tests/backspace-empty-between-inline/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | First item 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Third item 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /tests/backspace-empty-between-inline/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | First item 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Third item 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /tests/backspace-end-of-inline/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | plugin.onKeyDown( 3 | { 4 | preventDefault: () => {}, 5 | stopPropagation: () => {}, 6 | key: 'Backspace' 7 | }, 8 | change, 9 | {} 10 | ); 11 | 12 | return change; 13 | } 14 | -------------------------------------------------------------------------------- /tests/backspace-end-of-inline/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | Second item 14 | 15 | 16 | 17 | 18 | Third item 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /tests/backspace-end-of-inline/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | Second item 14 | 15 | 16 | 17 | 18 | 19 | Third item 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/backspace-start-of-inline/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | plugin.onKeyDown( 5 | { 6 | preventDefault: () => {}, 7 | stopPropagation: () => {}, 8 | key: 'Backspace' 9 | }, 10 | change, 11 | {} 12 | ); 13 | 14 | // Selection check 15 | expect(change.value.startBlock.text).toEqual('Second item'); 16 | expect(change.value.selection.anchorOffset).toEqual(0); 17 | expect(change.value.selection.isCollapsed).toBe(true); 18 | return change; 19 | } 20 | -------------------------------------------------------------------------------- /tests/backspace-start-of-inline/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | Second item 14 | 15 | 16 | 17 | 18 | Third item 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /tests/backspace-start-of-inline/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | Second item 15 | 16 | 17 | 18 | 19 | Third item 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/backspace-start-of-item/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.onKeyDown( 3 | { 4 | preventDefault: () => {}, 5 | stopPropagation: () => {}, 6 | key: 'Backspace' 7 | }, 8 | change, 9 | {} 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /tests/backspace-start-of-item/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /tests/backspace-start-of-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | Second item 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-basic/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.decreaseItemDepth(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-basic/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-basic/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | Se 14 | 15 | cond item 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-long-sublist-with-data/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.decreaseItemDepth(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-long-sublist-with-data/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 1.1 13 | 14 | 15 | Item 1.2 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-long-sublist-with-data/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | 13 | It 14 | 15 | em 1.1 16 | 17 | 18 | 19 | Item 1.2 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-long-sublist/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.decreaseItemDepth); 3 | } 4 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-long-sublist/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 1.1 13 | 14 | 15 | Item 1.2 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /tests/decrease-item-depth-long-sublist/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | 13 | It 14 | 15 | em 1.1 16 | 17 | 18 | 19 | Item 1.2 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /tests/enter-empty-block-in-item/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.onKeyDown( 3 | { 4 | preventDefault: () => {}, 5 | stopPropagation: () => {}, 6 | key: 'Enter' 7 | }, 8 | change, 9 | {} 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /tests/enter-empty-block-in-item/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | A first paragraph 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /tests/enter-empty-block-in-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | A first paragraph 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/enter-empty-item/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.onKeyDown( 3 | { 4 | preventDefault: () => {}, 5 | stopPropagation: () => {}, 6 | key: 'Enter' 7 | }, 8 | change, 9 | {} 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /tests/enter-empty-item/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /tests/enter-empty-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /tests/enter-middle-item/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.onKeyDown( 3 | { 4 | preventDefault: () => {}, 5 | stopPropagation: () => {}, 6 | key: 'Enter' 7 | }, 8 | change, 9 | {} 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /tests/enter-middle-item/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second 13 | 14 | 15 | item 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/enter-middle-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | Second 14 | item 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/enter-nested-item/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.onKeyDown( 3 | { 4 | preventDefault: () => {}, 5 | stopPropagation: () => {}, 6 | key: 'Enter' 7 | }, 8 | change, 9 | {} 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /tests/enter-nested-item/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/enter-nested-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /tests/get-current-item/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | const currentItem = plugin.utils.getCurrentItem(change.value); 5 | expect(currentItem.key).toBe('current_item'); 6 | } 7 | -------------------------------------------------------------------------------- /tests/get-current-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | Second item 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /tests/get-previous-item/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | const previousItem = plugin.utils.getPreviousItem(change.value); 5 | expect(previousItem.key).toBe('previous_item'); 6 | } 7 | -------------------------------------------------------------------------------- /tests/get-previous-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | Second item 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /tests/hyperscript.js: -------------------------------------------------------------------------------- 1 | import { createHyperscript } from '@gitbook/slate-hyperscript'; 2 | 3 | // Hyperscript function used to convert the JSX syntax 4 | // in tests to Slate models `create` calls. 5 | const h = createHyperscript({ 6 | blocks: { 7 | heading: 'heading', 8 | list_item: 'list_item', 9 | ul_list: 'ul_list', 10 | ol_list: 'ol_list', 11 | paragraph: 'paragraph', 12 | unknown: 'unknown' 13 | }, 14 | inlines: { 15 | link: 'link' 16 | }, 17 | marks: {} 18 | }); 19 | 20 | export default h; 21 | -------------------------------------------------------------------------------- /tests/increase-item-depth-basic-with-data/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.increaseItemDepth(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/increase-item-depth-basic-with-data/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /tests/increase-item-depth-basic-with-data/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | Second item 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /tests/increase-item-depth-basic/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.increaseItemDepth(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/increase-item-depth-basic/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /tests/increase-item-depth-basic/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | 14 | Second item 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /tests/increase-item-depth-complex/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.increaseItemDepth(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/increase-item-depth-complex/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | First item in the nested list 16 | 17 | 18 | 19 | Second item in the nested list 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /tests/increase-item-depth-complex/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | First item in the nested list 16 | 17 | 18 | 19 | 20 | Second item in the nested list 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /tests/increase-item-depth-existing-sublist/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.increaseItemDepth(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/increase-item-depth-existing-sublist/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 1.1 13 | 14 | 15 | Item 2 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /tests/increase-item-depth-existing-sublist/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 1.1 13 | 14 | 15 | 16 | 17 | 18 | 19 | Item 2 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/increase-item-depth-fifth/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.increaseItemDepth); 3 | } 4 | -------------------------------------------------------------------------------- /tests/increase-item-depth-fifth/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1 10 | 11 | 12 | 1.1 13 | 14 | 15 | 1.2 16 | 17 | 18 | 1.3 19 | 20 | 21 | 1.4 22 | 23 | 24 | 2 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /tests/increase-item-depth-fifth/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1 10 | 11 | 12 | 1.1 13 | 14 | 15 | 1.2 16 | 17 | 18 | 1.3 19 | 20 | 21 | 1.4 22 | 23 | 24 | 25 | 26 | 27 | 2 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | -------------------------------------------------------------------------------- /tests/schema-items-are-list-children/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-items-are-list-children/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Orphan 8 | 9 | 10 | Valid item 11 | 12 | 13 | Direct child of another item 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /tests/schema-items-are-list-children/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | Orphan 9 | 10 | 11 | 12 | Valid item 13 | 14 | 15 | 16 | Direct child of another item 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /tests/schema-items-contain-blocks-2/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-items-contain-blocks-2/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | A link 11 | 12 | 13 | 14 | 2nd item 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /tests/schema-items-contain-blocks-2/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | A link 10 | 11 | 2nd item 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /tests/schema-items-contain-blocks/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-items-contain-blocks/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | 1st 11 | 2nd 12 | 13 | 14 | 15 | 2nd item 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/schema-items-contain-blocks/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1st 10 | 2nd 11 | 12 | 2nd item 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /tests/schema-join-adjacent-lists-unless-different/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-join-adjacent-lists-unless-different/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1.1 10 | 11 | 12 | 1.2 13 | 14 | 15 | 16 | 17 | 2.1 18 | 19 | 20 | 2.2 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/schema-join-adjacent-lists-unless-different/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1.1 10 | 11 | 12 | 1.2 13 | 14 | 15 | 16 | 17 | 2.1 18 | 19 | 20 | 2.2 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/schema-join-adjacent-lists/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-join-adjacent-lists/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1.1 10 | 11 | 12 | 1.2 13 | 14 | 15 | 2.1 16 | 17 | 18 | 2.2 19 | 20 | 21 | 3.1 22 | 23 | 24 | 3.2 25 | 26 | 27 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /tests/schema-join-adjacent-lists/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1.1 10 | 11 | 12 | 1.2 13 | 14 | 15 | 16 | 17 | 2.1 18 | 19 | 20 | 2.2 21 | 22 | 23 | 24 | 25 | 3.1 26 | 27 | 28 | 3.2 29 | 30 | 31 | 32 | 33 | ); 34 | -------------------------------------------------------------------------------- /tests/schema-lists-contain-items/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-lists-contain-items/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 1st item 10 | 11 | 12 | 2nd item 13 | 14 | 15 | 3rd item 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/schema-lists-contain-items/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 1st item 9 | 2nd item 10 | 3rd item 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /tests/schema-nested-lists/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-nested-lists/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Nested list 10 | 11 | 12 | 1.1 13 | 14 | 15 | 1.2 16 | 17 | 18 | 19 | 20 | 2 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/schema-nested-lists/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Nested list 10 | 11 | 12 | 1.1 13 | 14 | 15 | 1.2 16 | 17 | 18 | 19 | 20 | 2 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/schema-ul-ul-li-li/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.normalize(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/schema-ul-ul-li-li/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 1st item 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /tests/schema-ul-ul-li-li/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 1st item 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /tests/shift-enter-middle-item/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | const ret = plugin.onKeyDown( 5 | { 6 | preventDefault: () => {}, 7 | stopPropagation: () => {}, 8 | key: 'Enter', 9 | shiftKey: true 10 | }, 11 | change, 12 | {} 13 | ); 14 | 15 | expect(ret == null).toBe(true); 16 | } 17 | -------------------------------------------------------------------------------- /tests/shift-enter-middle-item/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | 13 | Second 14 | item 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/split-item-end/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.splitListItem(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/split-item-end/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/split-item-end/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | Hello World 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/split-item-offset-multiple-blocks/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.splitListItem(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/split-item-offset-multiple-blocks/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First block 10 | Split 11 | 12 | 13 | Here 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /tests/split-item-offset-multiple-blocks/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First block 10 | 11 | Split 12 | 13 | Here 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /tests/split-item-offset/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.splitListItem(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/split-item-offset/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello 10 | 11 | 12 | World 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/split-item-offset/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | Hello 11 | World 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/split-item-start/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.splitListItem); 3 | } 4 | -------------------------------------------------------------------------------- /tests/split-item-start/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Hello World 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/split-item-start/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | Hello World 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/split-item-sublist-deep/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | plugin.changes.splitListItem(change); 5 | 6 | // check new selection 7 | const selectedNode = change.value.document.getTexts().get(2); 8 | 9 | expect(change.value.selection.toJS()).toMatch({ 10 | anchorKey: selectedNode.key, 11 | anchorOffset: 0, 12 | focusKey: selectedNode.key, 13 | focusOffset: 0 14 | }); 15 | 16 | return change; 17 | } 18 | -------------------------------------------------------------------------------- /tests/split-item-sublist-deep/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | It 13 | 14 | 15 | em 1.1 16 | 17 | 18 | Item 1.1.1 19 | 20 | 21 | Item 1.1.2 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | -------------------------------------------------------------------------------- /tests/split-item-sublist-deep/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | 13 | It 14 | 15 | em 1.1 16 | 17 | 18 | 19 | Item 1.1.1 20 | 21 | 22 | Item 1.1.2 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /tests/split-item-sublist/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return plugin.changes.splitListItem(change); 3 | } 4 | -------------------------------------------------------------------------------- /tests/split-item-sublist/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | It 10 | 11 | 12 | em 1 13 | 14 | 15 | Item 1.1 16 | 17 | 18 | Item 1.2 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/split-item-sublist/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | It 11 | 12 | em 1 13 | 14 | 15 | 16 | Item 1.1 17 | 18 | 19 | Item 1.2 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /tests/undo-decrease-item-depth-long-sublist/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | change.call(plugin.changes.decreaseItemDepth).undo(); 5 | 6 | // Back to previous cursor position 7 | expect(change.value.startBlock.text).toEqual('Item 1.1'); 8 | 9 | return change; 10 | } 11 | -------------------------------------------------------------------------------- /tests/undo-decrease-item-depth-long-sublist/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 1.1 13 | 14 | 15 | Item 1.2 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /tests/undo-decrease-item-depth-long-sublist/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | 13 | 14 | Item 1.1 15 | 16 | 17 | 18 | Item 1.2 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /tests/undo-increase-item-depth-complex/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | const initialText = change.value.startBlock.text; 5 | const initialSelection = change.value.selection; 6 | 7 | change.call(plugin.changes.increaseItemDepth).undo(); 8 | 9 | // Back to previous cursor position 10 | expect(change.value.startBlock.text).toEqual(initialText); 11 | expect(change.value.selection.toJS()).toEqual(initialSelection.toJS()); 12 | 13 | return change; 14 | } 15 | -------------------------------------------------------------------------------- /tests/undo-increase-item-depth-complex/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | First item in the nested list 16 | 17 | 18 | 19 | Second item in the nested list 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /tests/undo-increase-item-depth-complex/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | First item 10 | 11 | 12 | Second item 13 | 14 | 15 | First item in the nested list 16 | 17 | 18 | 19 | 20 | Second item in the nested list 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /tests/undo-split-item-sublist-deep/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | const initialText = change.value.startBlock.text; 5 | const initialSelection = change.value.selection; 6 | 7 | change.call(plugin.changes.splitListItem).undo(); 8 | 9 | // Back to previous cursor position 10 | expect(change.value.startBlock.text).toEqual(initialText); 11 | expect(change.value.selection.toJS()).toEqual(initialSelection.toJS()); 12 | 13 | return change; 14 | } 15 | -------------------------------------------------------------------------------- /tests/undo-split-item-sublist-deep/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 1.1 13 | 14 | 15 | Item 1.1.1 16 | 17 | 18 | Item 1.1.2 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /tests/undo-split-item-sublist-deep/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | 13 | It 14 | 15 | em 1.1 16 | 17 | 18 | 19 | Item 1.1.1 20 | 21 | 22 | Item 1.1.2 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /tests/undo-unwrap-long-list/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | const initialText = change.value.startBlock.text; 5 | const initialSelection = change.value.selection; 6 | 7 | change.call(plugin.changes.unwrapList).undo(); 8 | 9 | // Back to previous cursor position 10 | expect(change.value.startBlock.text).toEqual(initialText); 11 | expect(change.value.selection.toJS()).toEqual(initialSelection.toJS()); 12 | 13 | return change; 14 | } 15 | -------------------------------------------------------------------------------- /tests/undo-unwrap-long-list/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 2 13 | 14 | 15 | Item 3 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/undo-unwrap-long-list/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | 13 | 14 | Item 2 15 | 16 | 17 | 18 | Item 3 19 | 20 | 21 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /tests/undo-wrap-in-list/change.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export default function(plugin, change) { 4 | const { value } = change; 5 | const initialText = value.startBlock.text; 6 | const initialSelection = value.selection; 7 | 8 | change.call(plugin.changes.wrapInList).undo(); 9 | 10 | // Back to previous cursor position 11 | expect(change.value.startBlock.text).toEqual(initialText); 12 | expect(change.value.selection.toJS()).toEqual(initialSelection.toJS()); 13 | 14 | return change; 15 | } 16 | -------------------------------------------------------------------------------- /tests/undo-wrap-in-list/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Hello World 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /tests/undo-wrap-in-list/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /tests/unwrap-list-multiple-nested/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.unwrapList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/unwrap-list-multiple-nested/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | First item 8 | Second item, with nested list 9 | 10 | 11 | First item in the nested list 12 | 13 | 14 | Second item in the nested list 15 | 16 | 17 | 18 | 19 | Third item 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /tests/unwrap-list-multiple-nested/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | Fi 11 | 12 | rst item 13 | 14 | 15 | 16 | Second item, with nested list 17 | 18 | 19 | 20 | Firs 21 | t item in the nested list 22 | 23 | 24 | 25 | 26 | Second item in the nested list 27 | 28 | 29 | 30 | 31 | 32 | Third item 33 | 34 | 35 | 36 | 37 | ); 38 | -------------------------------------------------------------------------------- /tests/unwrap-list-multiple/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.unwrapList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/unwrap-list-multiple/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | First item 9 | Second item 10 | Blah blah 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /tests/unwrap-list-multiple/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | 10 | 11 | Fi 12 | 13 | rst item 14 | 15 | 16 | 17 | 18 | Seco 19 | 20 | nd item 21 | 22 | 23 | 24 | Blah blah 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /tests/unwrap-list/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.unwrapList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/unwrap-list/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Hello World 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /tests/unwrap-list/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | Hello World 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tests/unwrap-long-list/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.unwrapList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/unwrap-long-list/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | Item 2 13 | 14 | 15 | Item 3 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /tests/unwrap-long-list/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Item 1 10 | 11 | 12 | 13 | 14 | Item 2 15 | 16 | 17 | 18 | Item 3 19 | 20 | 21 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /tests/unwrap-nested-list/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.unwrapList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/unwrap-nested-list/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Item 1 8 | 9 | 10 | Subtitem 1 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /tests/unwrap-nested-list/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | Item 1 12 | 13 | 14 | 15 | Subtitem 1 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /tests/wrap-in-list-list-with-data/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | const data = { style: { listStyleType: 'disc' } }; 3 | return change.call(plugin.changes.wrapInList, false, data); 4 | } 5 | -------------------------------------------------------------------------------- /tests/wrap-in-list-list-with-data/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | 10 | First item 11 | 12 | 13 | Subitem 14 | 15 | 16 | 17 | 18 | Second item 19 | 20 | 21 | Blah blah 22 | 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /tests/wrap-in-list-list-with-data/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | 10 | 11 | Fi 12 | 13 | rst item 14 | 15 | 16 | 17 | Subitem 18 | 19 | 20 | 21 | 22 | Second item 23 | 24 | 25 | 26 | Blah 27 | blah 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /tests/wrap-in-list-list/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.wrapInList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/wrap-in-list-list/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | 10 | First item 11 | 12 | 13 | Subitem 14 | 15 | 16 | 17 | 18 | Second item 19 | 20 | 21 | Blah blah 22 | 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /tests/wrap-in-list-list/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | 10 | 11 | Fi 12 | 13 | rst item 14 | 15 | 16 | 17 | Subitem 18 | 19 | 20 | 21 | 22 | Second item 23 | 24 | 25 | 26 | Blah 27 | blah 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /tests/wrap-in-list-multiple-with-data/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | const data = { style: { listStyleType: 'disc' } }; 3 | return change.call(plugin.changes.wrapInList, false, data); 4 | } 5 | -------------------------------------------------------------------------------- /tests/wrap-in-list-multiple-with-data/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | 10 | First item 11 | 12 | 13 | Second item 14 | 15 | 16 | Blah blah 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /tests/wrap-in-list-multiple-with-data/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | Fi 10 | 11 | rst item 12 | 13 | 14 | Seco 15 | 16 | nd item 17 | 18 | Blah blah 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /tests/wrap-in-list-multiple/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.wrapInList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/wrap-in-list-multiple/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | 10 | First item 11 | 12 | 13 | Second item 14 | 15 | 16 | Blah blah 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /tests/wrap-in-list-multiple/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | Blah blah 8 | 9 | Fi 10 | 11 | rst item 12 | 13 | 14 | Seco 15 | 16 | nd item 17 | 18 | Blah blah 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /tests/wrap-in-list-with-data/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | const data = { style: { listStyleType: 'decimal' } }; 3 | return change.call(plugin.changes.wrapInList, 'ol_list', data); 4 | } 5 | -------------------------------------------------------------------------------- /tests/wrap-in-list-with-data/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /tests/wrap-in-list-with-data/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /tests/wrap-in-list/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.wrapInList); 3 | } 4 | -------------------------------------------------------------------------------- /tests/wrap-in-list/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /tests/wrap-in-list/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /tests/wrap-in-ol/change.js: -------------------------------------------------------------------------------- 1 | export default function(plugin, change) { 2 | return change.call(plugin.changes.wrapInList, 'ol_list'); 3 | } 4 | -------------------------------------------------------------------------------- /tests/wrap-in-ol/expected.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /tests/wrap-in-ol/input.js: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import h from 'h'; 3 | 4 | export default ( 5 | 6 | 7 | 8 | 9 | Hello World 10 | 11 | 12 | 13 | ); 14 | --------------------------------------------------------------------------------