├── .babelrc
├── .eslintignore
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── index.html
├── main.css
├── main.js
└── value.js
├── lib
├── changes
│ ├── dedentLines.js
│ ├── indentLines.js
│ ├── index.js
│ ├── toggleCodeBlock.js
│ ├── unwrapCodeBlock.js
│ ├── unwrapCodeBlockByKey.js
│ ├── wrapCodeBlock.js
│ └── wrapCodeBlockByKey.js
├── core.js
├── handlers
│ ├── index.js
│ ├── onBackspace.js
│ ├── onEnter.js
│ ├── onKeyDown.js
│ ├── onModEnter.js
│ ├── onPaste.js
│ ├── onSelectAll.js
│ ├── onShiftTab.js
│ └── onTab.js
├── index.js
├── options.js
├── utils
│ ├── deserializeCode.js
│ ├── getCurrentCode.js
│ ├── getCurrentIndent.js
│ ├── getIndent.js
│ ├── index.js
│ └── isInCodeBlock.js
└── validation
│ ├── index.js
│ └── schema.js
├── package.json
├── tests
├── all.js
├── backspace-empty-code
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── backspace-start
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── enter-end-offset
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── enter-mid-offset
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── enter-start-offset
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── enter-with-indent
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── hyperscript.js
├── isincodeblock-true
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── on-exit-block
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── paste-middle
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-multiline-text
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-no-inlines
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-no-marks
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-no-multiple-orphan-lines
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-no-orphan-lines-wrapped
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-no-single-orphan-line
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-only-lines-empty
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-only-lines
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-only-text-join
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-only-text-with-blocks
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-only-text
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── schema-single-text
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── select-all
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── shift-tab-middle-offset
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── shift-tab-multilines
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── simulate-key.js
├── tab-middle-offset
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── tab-multilines
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── tab-start-offset
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── togglecodeblock-code
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── togglecodeblock-normal
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── unwrapcodeblock-multi-lines
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── unwrapcodeblock-normal
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── unwrapcodeblock-selection
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── wrapcodeblock-normal
│ ├── change.js
│ ├── expected.js
│ └── input.js
├── wrapcodeblock-selection
│ ├── change.js
│ ├── expected.js
│ └── input.js
└── wrapcodeblock-with-inline
│ ├── change.js
│ ├── expected.js
│ └── input.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-0",
5 | "react"
6 | ],
7 | "plugins": [
8 | "transform-flow-strip-types"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | example/bundle.js
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "gitbook"
5 | ],
6 | "rules": {
7 | "no-param-reassign": 1,
8 | "import/no-commonjs": 2
9 | },
10 | "plugins": [
11 | "flowtype"
12 | ],
13 | "env": {
14 | "mocha": true
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [options]
2 | emoji=true
3 | esproposal.decorators=ignore
4 | unsafe.enable_getters_and_setters=true
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | dist
40 | example/bundle.js
--------------------------------------------------------------------------------
/.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 | # Release notes
2 |
3 | All notable changes to this project will be documented in this file. This
4 | project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ### 0.15.7
7 |
8 | * Accept `immutable@4` as peer dependency
9 |
10 | ### 0.15.6
11 |
12 | * Improve normalization of non text inside code lines
13 |
14 | ### 0.15.4, 0.15.5
15 |
16 | * Fix normalization of non lines inside code container.
17 |
18 | ### 0.15.3
19 |
20 | * Reduce the likelihood of Maximum call stack exceeded, by avoiding
21 | `change.withoutNormalization`.
22 |
23 | ### 0.15.2
24 |
25 | * Small monkey patch to support the GitBook's fork of Slate.
26 |
27 | ### 0.15.1
28 |
29 | * Fix and improve orphan lines normalization.
30 |
31 | ### 0.15.0
32 |
33 | * Upgrade to be compatible with Slate > 0.33.x
34 | * Use the new schema definition for improved performance
35 |
36 | ### 0.14.0
37 |
38 | * Upgrade to be compatible with Slate > 0.32.x
39 |
40 | ### 0.13.3
41 |
42 | * Normalize multiline texts in code to be split into the appropriate number of
43 | code lines.
44 |
45 | ### 0.13.2
46 |
47 | * Add `getIndent` option to customize the indent unit used.
48 | * Fix Ctrl shortcuts on Windows/Linux
49 |
50 | ### 0.13.1
51 |
52 | * Properly declare `immutable` as a peer dependency (already a peer dependency
53 | of slate)
54 |
55 | ### 0.13.0
56 |
57 | * Upgrade to be compatible with Slate > 0.29.x
58 |
59 | ### 0.12.0
60 |
61 | * Upgrade to be compatible with Slate > 0.27.x
62 |
63 | ### 0.11.0
64 |
65 | * Upgrade to be compatable with Slate after the `expose-transform` branch went
66 | in.
67 | * change all instances of `transform` to `change`
68 | * change the namespace of `plugin.transforms` to `plugin.changes`
69 |
70 | ### 0.10.4
71 |
72 | * Added `onExit(transform: Transform): ?Transform` option
73 |
74 | ### 0.10.2
75 |
76 | * Upgrade to slate^0.19.7
77 |
78 | ### 0.10.1
79 |
80 | * Added `isInCodeBlock` utils
81 | * Added `wrapCodeBlock` and `wrapCodeBlockByKey` transforms
82 | * Added `unwrapCodeBlock` and `unwrapCodeBlockByKey` transforms
83 |
84 | ### 0.10.0
85 |
86 | * Added: Backspace in empty code container will convert it to default
87 | `exitBlockType`
88 |
89 | ### 0.9.2
90 |
91 | * Fix case-insensitive slate require
92 |
93 | ### 0.9.1
94 |
95 | * Export utils.deserializeCode that deserialize a text into a code block
96 |
97 | ### 0.9.0
98 |
99 | * _Breaking change_ Renamed option `shiftEnterBlockType` to `exitBlockType`.
100 | * Shift+Enter shortcut is now assigned to Mod+Enter, as before.
101 |
102 | ### 0.8.2
103 |
104 | * Fixed onPaste
105 |
106 | ### 0.8.1
107 |
108 | * Removed unused dependency
109 |
110 | ### 0.8.0
111 |
112 | * _Breaking change_ : Changed the structure of code blocks. A code block is now
113 | made of a container, and a list of lines.
114 | * Removed option `onlyIn`
115 | * Add option `containerType`
116 | * Add option `lineType`
117 | * Added option `shiftEnterBlockType` to determine the default block type when
118 | exiting a code block.
119 | * Added support for multi-lines Tab and Shift+Tab
120 |
121 | ### 0.7.0
122 |
123 | * Add option `selectAll`
124 |
125 | ### 0.6.2
126 |
127 | * Update slate peed dependency to prevent NPM warnings when used with `0.15.x`
128 |
129 | ### 0.6.1
130 |
131 | * Move slate to `peerDependencies`
132 |
133 | ### 0.6.0
134 |
135 | * Adapt for Slate 0.15
136 |
137 | ### 0.5.0
138 |
139 | * Pressing Ctrl+A / Cmd+A in a code block, select only the text in
140 | the block
141 |
142 | ### 0.4.0
143 |
144 | * Add schema to normalize code blocks
145 |
146 | ### 0.3.0
147 |
148 | * Pressing Tab with a extended selection will indent all lines in the
149 | selection
150 |
--------------------------------------------------------------------------------
/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-code
7 |
8 | [](http://badge.fury.io/js/slate-edit-code)
9 | [](https://travis-ci.org/GitbookIO/slate-edit-code)
10 |
11 | A Slate plugin to handle code block editing.
12 |
13 | ### Install
14 |
15 | ```js
16 | npm install slate-edit-code
17 | ```
18 |
19 | ### Features
20 |
21 | - Pressing Enter insert a new line starting with the right indentation
22 | - Pressing Tab insert the right indentation if selection is collapsed or indent all lines in selection
23 | - Pressing Delete remove the indentation before cursor if possible
24 | - Pressing Mod+Enter exits the code block
25 | - Pressing Mod+A selects all the text in the block
26 |
27 | > Mod means Ctrl on Windows/Linux and Command on Mac.
28 |
29 | ### Structure
30 |
31 | This plugin uses the following structure for code blocks:
32 |
33 | ```html
34 |
35 | A code block is made of
36 | several code lines
37 |
38 | ```
39 |
40 | Texts inside `code_blocks` that contain newlines `\n` are automatically split into the appropriate number of `code_lines`.
41 |
42 |
43 | ### Simple Usage
44 |
45 | ```js
46 | import EditCode from 'slate-edit-code'
47 |
48 | const plugins = [
49 | EditCode()
50 | ]
51 | ```
52 |
53 | #### Options arguments
54 |
55 | - `containerType = 'code_block' : string` — The type of the code containers
56 | - `lineType = 'code_line' : string` — The type of the code lines
57 | - `exitBlockType = 'paragraph' : null | string` — Mod+Enter will exit the code container, into the given block type. Backspace at start of an empty code container will convert it to the given block type. Pass `null` to disable this behavior.
58 | - `onExit: (Change) => void | Change` — Change to do when the user hits Mod+Enter . Defaults to exiting the code block, into a new `exitBlockType` block.
59 | - `selectAll = true : boolean` — True to select all code inside a code container on Mod+A
60 | - `allowMarks = false : boolean` — False disallow marks in code blocks by normalizing them away.
61 | - `getIndent: (Value) => string` — Returns the indent unit as a string. The current value is passed as context.
62 |
63 | #### Suppressing onKeyDown behavior
64 |
65 | Some behavior implemented by this plugins have no corresponding option. While there is an option `selectAll` to disable the behavior on `Mod+A`, If you would like to fine tune these behavior, you can always redefine the exported `onKeyDown` function.
66 |
67 | The following example disable all indent behavior
68 |
69 | ```js
70 | import EditCode from 'slate-edit-code'
71 |
72 | const options = { ... };
73 |
74 | const basePlugin = EditCode(options);
75 |
76 | const customPlugin = {
77 | ...basePlugin,
78 | onKeyDown(event, change, editor) {
79 | if (event.key === 'Tab') {
80 | // Bypass the original plugin behavior on `Tab`
81 | return;
82 | } else {
83 | return basePlugin.onKeyDown(event, change, editor);
84 | }
85 | }
86 | }
87 |
88 | // Use customPlugin later on
89 | ```
90 |
91 | ### Utilities and Changes
92 |
93 | `slate-edit-code` exports utilities, accessible like so:
94 |
95 | ``` js
96 | const plugin = EditCode()
97 |
98 | // Access exported utilities there
99 | plugin.utils
100 | ```
101 |
102 | #### `utils.deserializeCode`
103 |
104 | `plugin.utils.deserializeCode(text: String) => Block`
105 |
106 | Split a text string into lines, and deserialize them to a `code_container` `Block`, with one children `code_line` `Block` per line.
107 |
108 |
109 | #### `changes.toggleCodeBlock`
110 |
111 | `plugin.changes.toggleCodeBlock(change: Change, type: String) => Change`
112 |
113 | Toggle a block into a code block or a normal block (defined by `type`).
114 |
115 | #### `changes.wrapCodeBlockByKey`
116 |
117 | `plugin.changes.wrapCodeBlockByKey(change: Change, key: String) => Change`
118 |
119 | Convert a block (paragraph, etc) into a code block.
120 |
121 | #### `changes.wrapCodeBlock`
122 |
123 | `plugin.changes.wrapCodeBlock(change: Change) => Change`
124 |
125 | Convert current block (paragraph, etc) into a code block.
126 |
127 | #### `changes.unwrapCodeBlockByKey`
128 | `plugin.changes.unwrapCodeBlockByKey(change: Change, key: String, type: String) => Change`
129 |
130 | Convert a code block into a normal block (paragraph, etc).
131 |
132 | #### `changes.unwrapCodeBlock`
133 |
134 | `plugin.changes.unwrapCodeBlock(change: Change, type: String) => Change`
135 |
136 | Convert current code block into a normal block (paragraph, etc).
137 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Slate • Code Edition
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/main.css:
--------------------------------------------------------------------------------
1 | #example {
2 | width: 100%;
3 | max-width: 600px;
4 | margin: 20px auto;
5 | }
6 |
7 | #example .code {
8 | background: #f5f5f5;
9 | padding: 5px;
10 | margin-bottom: 20px;
11 | }
12 |
13 | #example pre {
14 | background: #f5f5f5;
15 | margin: 0;
16 | }
17 |
--------------------------------------------------------------------------------
/example/main.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* global document */
4 | import * as React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { Editor } from '@gitbook/slate-react';
7 | import PluginEditCode from '../lib/';
8 |
9 | import INITIAL_VALUE from './value';
10 |
11 | const plugin = PluginEditCode();
12 | const plugins = [plugin];
13 |
14 | function renderNode(props: *) {
15 | const { node, children, attributes } = props;
16 |
17 | switch (node.type) {
18 | case 'code_block':
19 | return (
20 |
21 | {children}
22 |
23 | );
24 | case 'code_line':
25 | return {children} ;
26 | case 'paragraph':
27 | return {children}
;
28 | case 'heading':
29 | return {children} ;
30 | default:
31 | return null;
32 | }
33 | }
34 |
35 | class Example extends React.Component<*, *> {
36 | state = {
37 | value: INITIAL_VALUE
38 | };
39 |
40 | onChange = ({ value }) => {
41 | this.setState({
42 | value
43 | });
44 | };
45 |
46 | onToggleCode = () => {
47 | const { value } = this.state;
48 |
49 | this.onChange(
50 | plugin.changes.toggleCodeBlock(value.change(), 'paragraph').focus()
51 | );
52 | };
53 |
54 | render() {
55 | const { value } = this.state;
56 |
57 | return (
58 |
59 |
60 | {plugin.utils.isInCodeBlock(value)
61 | ? 'Paragraph'
62 | : 'Code Block'}
63 |
64 |
71 |
72 | );
73 | }
74 | }
75 |
76 | // $FlowFixMe
77 | ReactDOM.render( , document.getElementById('example'));
78 |
--------------------------------------------------------------------------------
/example/value.js:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | // @flow
3 | // eslint-disable-next-line
4 | import { createHyperscript } from '@gitbook/slate-hyperscript';
5 |
6 | const h = createHyperscript({
7 | blocks: {
8 | heading: 'heading',
9 | paragraph: 'paragraph',
10 | code_block: 'code_block',
11 | code_line: 'code_line'
12 | }
13 | });
14 |
15 | const value = (
16 |
17 |
18 | Slate + Code Editing
19 |
20 | {
21 | 'This page is a basic example of Slate + slate-edit-code plugin. Press Tab to indent code. Shift+Tab to unindent. Press Enter to carry indentation onto the newline. Press Mod (Cmd on Mac, Ctrl on Windows) + Enter to exit the code block.'
22 | }
23 |
24 |
25 | {'// Some JavaScript'}
26 | {'function hello() {'}
27 | {" console.log('Hello World')"}
28 | {'}'}
29 |
30 | End paragraph
31 |
32 |
33 | );
34 |
35 | export default value;
36 |
--------------------------------------------------------------------------------
/lib/changes/dedentLines.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 |
6 | // Return the index of the first character that differs between both string, or
7 | // the smallest string length otherwise.
8 | function firstDifferentCharacter(a: string, b: string): number {
9 | if (a.length > b.length) {
10 | return firstDifferentCharacter(b, a);
11 | }
12 |
13 | const indexes = Array(a.length)
14 | .fill()
15 | .map((v, i) => i);
16 | const index = indexes.find(i => a[i] !== b[i]);
17 |
18 | return index == null ? a.length : index;
19 | }
20 |
21 | /**
22 | * Dedent all lines in selection
23 | */
24 | function dedentLines(
25 | opts: Options,
26 | change: Change,
27 | // Indent to remove
28 | indent: string
29 | ): Change {
30 | const { value } = change;
31 | const { document, selection } = value;
32 | const lines = document
33 | .getBlocksAtRange(selection)
34 | .filter(node => node.type === opts.lineType);
35 |
36 | return lines.reduce((c, line) => {
37 | // Remove a level of indent from the start of line
38 | const textNode = line.nodes.first();
39 | const lengthToRemove = firstDifferentCharacter(textNode.text, indent);
40 | return c.removeTextByKey(textNode.key, 0, lengthToRemove);
41 | }, change);
42 | }
43 |
44 | export default dedentLines;
45 |
--------------------------------------------------------------------------------
/lib/changes/indentLines.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 |
6 | /**
7 | * Indent all lines in selection
8 | */
9 | function indentLines(
10 | opts: Options,
11 | change: Change,
12 | // Indent to add
13 | indent: string
14 | ): Change {
15 | const { value } = change;
16 | const { document, selection } = value;
17 | const lines = document
18 | .getBlocksAtRange(selection)
19 | .filter(node => node.type === opts.lineType);
20 |
21 | return lines.reduce((c, line) => {
22 | // Insert an indent at start of line
23 | const text = line.nodes.first();
24 | return c.insertTextByKey(text.key, 0, indent);
25 | }, change);
26 | }
27 |
28 | export default indentLines;
29 |
--------------------------------------------------------------------------------
/lib/changes/index.js:
--------------------------------------------------------------------------------
1 | import dedentLines from './dedentLines';
2 | import indentLines from './indentLines';
3 | import toggleCodeBlock from './toggleCodeBlock';
4 | import unwrapCodeBlock from './unwrapCodeBlock';
5 | import unwrapCodeBlockByKey from './unwrapCodeBlockByKey';
6 | import wrapCodeBlock from './wrapCodeBlock';
7 | import wrapCodeBlockByKey from './wrapCodeBlockByKey';
8 |
9 | export {
10 | dedentLines,
11 | indentLines,
12 | toggleCodeBlock,
13 | unwrapCodeBlock,
14 | unwrapCodeBlockByKey,
15 | wrapCodeBlock,
16 | wrapCodeBlockByKey
17 | };
18 |
--------------------------------------------------------------------------------
/lib/changes/toggleCodeBlock.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 | import { isInCodeBlock } from '../utils';
6 |
7 | import wrapCodeBlock from './wrapCodeBlock';
8 | import unwrapCodeBlock from './unwrapCodeBlock';
9 |
10 | /**
11 | * Toggle code block / paragraph.
12 | */
13 | function toggleCodeBlock(
14 | opts: Options,
15 | change: Change,
16 | // When toggling a code block off, type to convert to
17 | type: string
18 | ): Change {
19 | if (isInCodeBlock(opts, change.value)) {
20 | return unwrapCodeBlock(opts, change, type);
21 | }
22 | return wrapCodeBlock(opts, change);
23 | }
24 |
25 | export default toggleCodeBlock;
26 |
--------------------------------------------------------------------------------
/lib/changes/unwrapCodeBlock.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 | import { getCurrentCode } from '../utils';
6 |
7 | import unwrapCodeBlockByKey from './unwrapCodeBlockByKey';
8 |
9 | /**
10 | * Convert a code block to a normal block.
11 | */
12 | function unwrapCodeBlock(opts: Options, change: Change, type: string): Change {
13 | const { value } = change;
14 |
15 | const codeBlock = getCurrentCode(opts, value);
16 |
17 | if (!codeBlock) {
18 | return change;
19 | }
20 |
21 | // Convert to paragraph
22 | unwrapCodeBlockByKey(opts, change, codeBlock.key, type);
23 |
24 | return change;
25 | }
26 |
27 | export default unwrapCodeBlock;
28 |
--------------------------------------------------------------------------------
/lib/changes/unwrapCodeBlockByKey.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 |
6 | /**
7 | * Unwrap a code block into a normal block.
8 | */
9 | function unwrapCodeBlockByKey(
10 | opts: Options,
11 | change: Change,
12 | key: string,
13 | type: string
14 | ): Change {
15 | const { value } = change;
16 | const { document } = value;
17 |
18 | // Get the code block
19 | const codeBlock = document.getDescendant(key);
20 |
21 | if (!codeBlock || codeBlock.type != opts.containerType) {
22 | throw new Error(
23 | 'Block passed to unwrapCodeBlockByKey should be a code block container'
24 | );
25 | }
26 |
27 | // change lines into paragraph
28 | codeBlock.nodes.forEach(line =>
29 | change
30 | .setNodeByKey(line.key, { type }, { normalize: false })
31 | .unwrapNodeByKey(line.key, { normalize: false })
32 | );
33 |
34 | return change;
35 | }
36 |
37 | export default unwrapCodeBlockByKey;
38 |
--------------------------------------------------------------------------------
/lib/changes/wrapCodeBlock.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 |
6 | import wrapCodeBlockByKey from './wrapCodeBlockByKey';
7 |
8 | /**
9 | * Wrap current block into a code block.
10 | */
11 | function wrapCodeBlock(opts: Options, change: Change): Change {
12 | const { value } = change;
13 | const { startBlock, selection } = value;
14 |
15 | // Convert to code block
16 | wrapCodeBlockByKey(opts, change, startBlock.key);
17 |
18 | // Move selection back in the block
19 | change
20 | .collapseToStartOf(change.value.document.getDescendant(startBlock.key))
21 | .moveOffsetsTo(selection.startOffset);
22 |
23 | return change;
24 | }
25 |
26 | export default wrapCodeBlock;
27 |
--------------------------------------------------------------------------------
/lib/changes/wrapCodeBlockByKey.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 | import { deserializeCode } from '../utils';
6 |
7 | /**
8 | * Wrap a block into a code block.
9 | */
10 | function wrapCodeBlockByKey(
11 | opts: Options,
12 | change: Change,
13 | key: string
14 | ): Change {
15 | const { value } = change;
16 | const { document } = value;
17 |
18 | const startBlock = document.getDescendant(key);
19 | const text = startBlock.text;
20 |
21 | // Remove all child
22 | startBlock.nodes.forEach(node => {
23 | change.removeNodeByKey(node.key, { normalize: false });
24 | });
25 |
26 | // Insert new text
27 | const toInsert = deserializeCode(opts, text);
28 |
29 | toInsert.nodes.forEach((node, i) => {
30 | change.insertNodeByKey(startBlock.key, i, node, { normalize: false });
31 | });
32 |
33 | // Set node type
34 | change.setNodeByKey(startBlock.key, {
35 | type: opts.containerType
36 | });
37 |
38 | return change;
39 | }
40 |
41 | export default wrapCodeBlockByKey;
42 |
--------------------------------------------------------------------------------
/lib/core.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import Options, { type OptionsFormat } from './options';
3 | import { deserializeCode, isInCodeBlock } from './utils';
4 | import {
5 | wrapCodeBlockByKey,
6 | unwrapCodeBlockByKey,
7 | wrapCodeBlock,
8 | unwrapCodeBlock,
9 | toggleCodeBlock
10 | } from './changes';
11 |
12 | import { schema } from './validation';
13 |
14 | /**
15 | * The core of the plugin, which does not relies on `slate-react`, and includes
16 | * everything but behavior and rendering logic.
17 | */
18 | function core(optsParam: OptionsFormat): Object {
19 | const opts = new Options(optsParam);
20 |
21 | return {
22 | schema: schema(opts),
23 |
24 | changes: {
25 | unwrapCodeBlockByKey: unwrapCodeBlockByKey.bind(null, opts),
26 | wrapCodeBlockByKey: wrapCodeBlockByKey.bind(null, opts),
27 | wrapCodeBlock: wrapCodeBlock.bind(null, opts),
28 | unwrapCodeBlock: unwrapCodeBlock.bind(null, opts),
29 | toggleCodeBlock: toggleCodeBlock.bind(null, opts)
30 | },
31 |
32 | utils: {
33 | isInCodeBlock: isInCodeBlock.bind(null, opts),
34 | deserializeCode: deserializeCode.bind(null, opts)
35 | }
36 | };
37 | }
38 |
39 | export default core;
40 |
--------------------------------------------------------------------------------
/lib/handlers/index.js:
--------------------------------------------------------------------------------
1 | import onTab from './onTab';
2 | import onShiftTab from './onShiftTab';
3 | import onEnter from './onEnter';
4 | import onModEnter from './onModEnter';
5 | import onBackspace from './onBackspace';
6 | import onSelectAll from './onSelectAll';
7 | import onPaste from './onPaste';
8 | import onKeyDown from './onKeyDown';
9 |
10 | export {
11 | onTab,
12 | onShiftTab,
13 | onEnter,
14 | onModEnter,
15 | onBackspace,
16 | onSelectAll,
17 | onPaste,
18 | onKeyDown
19 | };
20 |
--------------------------------------------------------------------------------
/lib/handlers/onBackspace.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { type Change } from '@gitbook/slate';
4 | import endsWith from 'ends-with';
5 |
6 | import { getCurrentIndent, getCurrentCode } from '../utils';
7 | import type Options from '../options';
8 |
9 | /**
10 | * User pressed Delete in an editor:
11 | * Remove last idnentation before cursor
12 | */
13 | function onBackspace(
14 | opts: Options,
15 | event: *,
16 | change: Change,
17 | editor: *
18 | ): void | Change {
19 | const { value } = change;
20 | if (value.isExpanded) {
21 | return undefined;
22 | }
23 |
24 | const { startOffset, startText } = value;
25 |
26 | const currentLine = value.startBlock;
27 |
28 | // Detect and remove indentation at cursor
29 | const indent = getCurrentIndent(opts, value);
30 | const beforeSelection = currentLine.text.slice(0, startOffset);
31 |
32 | // If the line before selection ending with the indentation?
33 | if (endsWith(beforeSelection, indent)) {
34 | // Remove indent
35 | event.preventDefault();
36 |
37 | return change.deleteBackward(indent.length).focus();
38 | } else if (opts.exitBlockType) {
39 | // Otherwise check if we are in an empty code container...
40 | const currentCode = getCurrentCode(opts, value);
41 | const isStartOfCode =
42 | startOffset === 0 && currentCode.getFirstText() === startText;
43 | // PERF: avoid checking for whole currentCode.text
44 | const isEmpty =
45 | currentCode.nodes.size === 1 && currentLine.text.length === 0;
46 |
47 | if (isStartOfCode && isEmpty) {
48 | event.preventDefault();
49 | // Convert it to default exit type
50 | return change
51 | .setBlocks(opts.exitBlockType, { normalize: false })
52 | .unwrapNodeByKey(currentLine.key);
53 | }
54 | }
55 | return undefined;
56 | }
57 |
58 | export default onBackspace;
59 |
--------------------------------------------------------------------------------
/lib/handlers/onEnter.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import { getIndent } from '../utils';
5 | import type Options from '../options';
6 |
7 | /**
8 | * User pressed Enter in an editor:
9 | * Insert a new code line and start it with the indentation from previous line
10 | */
11 | function onEnter(
12 | opts: Options,
13 | event: *,
14 | change: Change,
15 | editor: *
16 | ): void | Change {
17 | const { value } = change;
18 | if (!value.isCollapsed) {
19 | return undefined;
20 | }
21 |
22 | event.preventDefault();
23 |
24 | const { startBlock } = value;
25 | const currentLineText = startBlock.text;
26 | const indent = getIndent(currentLineText, '');
27 |
28 | return change
29 | .splitBlock()
30 | .insertText(indent)
31 | .focus();
32 | }
33 |
34 | export default onEnter;
35 |
--------------------------------------------------------------------------------
/lib/handlers/onKeyDown.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { isKeyHotkey } from 'is-hotkey';
3 | import { type Change } from '@gitbook/slate';
4 |
5 | import { getCurrentCode } from '../utils';
6 | import type Options from '../options';
7 |
8 | import onTab from './onTab';
9 | import onShiftTab from './onShiftTab';
10 | import onEnter from './onEnter';
11 | import onModEnter from './onModEnter';
12 | import onBackspace from './onBackspace';
13 | import onSelectAll from './onSelectAll';
14 |
15 | const isModA = isKeyHotkey('mod+a');
16 | const isShiftTab = isKeyHotkey('shift+tab');
17 | const isTab = isKeyHotkey('tab');
18 | const isModEnter = isKeyHotkey('mod+enter');
19 | const isEnter = isKeyHotkey('enter');
20 | const isBackspace = isKeyHotkey('backspace');
21 |
22 | /**
23 | * User is pressing a key in the editor
24 | */
25 | function onKeyDown(
26 | opts: Options,
27 | event: *,
28 | change: Change,
29 | editor: *
30 | ): void | Change {
31 | const { value } = change;
32 | const currentCode = getCurrentCode(opts, value);
33 |
34 | // Inside code ?
35 | if (!currentCode) {
36 | return undefined;
37 | }
38 |
39 | // Add opts in the argument list
40 | const args = [opts, event, change, editor];
41 |
42 | // Select all the code in the block (Mod+a)
43 | if (opts.selectAll && isModA(event)) {
44 | return onSelectAll(...args);
45 | } else if (isShiftTab(event)) {
46 | // User is pressing Shift+Tab
47 | return onShiftTab(...args);
48 | } else if (isTab(event)) {
49 | // User is pressing Tab
50 | return onTab(...args);
51 | } else if (opts.exitBlockType && isModEnter(event)) {
52 | // User is pressing Mod+Enter
53 | return onModEnter(...args);
54 | } else if (isEnter(event)) {
55 | // User is pressing Enter
56 | return onEnter(...args);
57 | } else if (isBackspace(event)) {
58 | // User is pressing Backspace
59 | return onBackspace(...args);
60 | }
61 | return undefined;
62 | }
63 |
64 | export default onKeyDown;
65 |
--------------------------------------------------------------------------------
/lib/handlers/onModEnter.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 |
6 | /**
7 | * User pressed Mod+Enter in an editor
8 | * Exit the current code block
9 | */
10 | function onModEnter(
11 | opts: Options,
12 | event: *,
13 | change: Change,
14 | editor: *
15 | ): void | Change {
16 | const { value } = change;
17 | if (!value.isCollapsed) {
18 | return undefined;
19 | }
20 |
21 | event.preventDefault();
22 |
23 | // Exit the code block
24 | return opts.resolvedOnExit(change);
25 | }
26 |
27 | export default onModEnter;
28 |
--------------------------------------------------------------------------------
/lib/handlers/onPaste.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { Document, type Change } from '@gitbook/slate';
3 | import { getEventTransfer } from '@gitbook/slate-react';
4 | import { getCurrentCode, deserializeCode } from '../utils';
5 | import type Options from '../options';
6 |
7 | /**
8 | * User is pasting content, insert it as text
9 | */
10 | function onPaste(
11 | opts: Options,
12 | event: *,
13 | change: Change,
14 | editor: *
15 | ): void | Change {
16 | const { value } = change;
17 | const data = getEventTransfer(event);
18 | const currentCode = getCurrentCode(opts, value);
19 |
20 | // Only handle paste when selection is completely a code block
21 | const { endBlock } = value;
22 | if (!currentCode || !currentCode.hasDescendant(endBlock.key)) {
23 | return undefined;
24 | }
25 |
26 | // Convert to text if needed
27 | let text;
28 | if (data.type === 'fragment') {
29 | text = data.fragment
30 | .getTexts()
31 | .map(t => t.text)
32 | .join('\n');
33 | } else {
34 | text = data.text;
35 | }
36 |
37 | // Convert the text to code lines
38 | const lines = deserializeCode(opts, text).nodes;
39 |
40 | const fragment = Document.create({ nodes: lines });
41 |
42 | return change.insertFragment(fragment);
43 | }
44 |
45 | export default onPaste;
46 |
--------------------------------------------------------------------------------
/lib/handlers/onSelectAll.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import { getCurrentCode } from '../utils';
5 | import type Options from '../options';
6 |
7 | /**
8 | * User is Cmd+A to select all text
9 | */
10 | function onSelectAll(
11 | opts: Options,
12 | event: *,
13 | change: Change,
14 | editor: *
15 | ): void | Change {
16 | const { value } = change;
17 | event.preventDefault();
18 |
19 | const currentCode = getCurrentCode(opts, value);
20 | return change
21 | .collapseToStartOf(currentCode.getFirstText())
22 | .extendToEndOf(currentCode.getLastText());
23 | }
24 |
25 | export default onSelectAll;
26 |
--------------------------------------------------------------------------------
/lib/handlers/onShiftTab.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 | import { getCurrentIndent } from '../utils';
4 | import { dedentLines } from '../changes';
5 | import type Options from '../options';
6 |
7 | /**
8 | * User pressed Shift+Tab in an editor:
9 | * Reduce indentation in the selected lines.
10 | */
11 | function onShiftTab(
12 | opts: Options,
13 | event: *,
14 | change: Change,
15 | editor: *
16 | ): void | Change {
17 | const { value } = change;
18 | event.preventDefault();
19 | event.stopPropagation();
20 |
21 | const indent = getCurrentIndent(opts, value);
22 |
23 | // We dedent all selected lines
24 | return dedentLines(opts, change, indent);
25 | }
26 |
27 | export default onShiftTab;
28 |
--------------------------------------------------------------------------------
/lib/handlers/onTab.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change } from '@gitbook/slate';
3 |
4 | import { getCurrentIndent } from '../utils';
5 | import { indentLines } from '../changes';
6 |
7 | import type Options from '../options';
8 |
9 | /**
10 | * User pressed Tab in an editor:
11 | * Insert a tab after detecting it from code block content.
12 | */
13 | function onTab(
14 | opts: Options,
15 | event: *,
16 | change: Change,
17 | editor: *
18 | ): void | Change {
19 | const { value } = change;
20 | event.preventDefault();
21 | event.stopPropagation();
22 |
23 | const { isCollapsed } = value;
24 | const indent = getCurrentIndent(opts, value);
25 |
26 | // Selection is collapsed, we just insert an indent at cursor
27 | if (isCollapsed) {
28 | return change.insertText(indent).focus();
29 | }
30 |
31 | // We indent all selected lines
32 | return indentLines(opts, change, indent);
33 | }
34 |
35 | export default onTab;
36 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import Options, { type OptionsFormat } from './options';
3 | import { onKeyDown, onPaste } from './handlers';
4 | import core from './core';
5 |
6 | /**
7 | * A Slate plugin to handle keyboard events in code blocks.
8 | */
9 |
10 | function EditCode(optsParam?: OptionsFormat = {}): Object {
11 | const opts = new Options(optsParam);
12 |
13 | const corePlugin = core(opts);
14 | return {
15 | ...corePlugin,
16 |
17 | onKeyDown: onKeyDown.bind(null, opts),
18 | onPaste: onPaste.bind(null, opts)
19 | };
20 | }
21 |
22 | export default EditCode;
23 |
--------------------------------------------------------------------------------
/lib/options.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Change, type Value, Block, Text } from '@gitbook/slate';
3 | import { Record } from 'immutable';
4 |
5 | const DEFAULTS = {
6 | containerType: 'code_block',
7 | lineType: 'code_line',
8 | exitBlockType: 'paragraph',
9 | selectAll: true,
10 | allowMarks: false,
11 | getIndent: null,
12 | onExit: null
13 | };
14 |
15 | /**
16 | * The plugin options container
17 | */
18 | class Options extends Record(DEFAULTS) {
19 | containerType: string;
20 | lineType: string;
21 | exitBlockType: string;
22 | selectAll: boolean;
23 | allowMarks: boolean;
24 | getIndent: ?(Value) => string;
25 | onExit: ?(Change) => ?Change;
26 |
27 | resolvedOnExit(change: Change): ?Change {
28 | if (this.onExit) {
29 | // Custom onExit option
30 | return this.onExit(change);
31 | }
32 | // Default behavior: insert an exit block
33 | const range = change.value.selection;
34 |
35 | const exitBlock = Block.create({
36 | type: this.exitBlockType,
37 | nodes: [Text.create()]
38 | });
39 |
40 | change.deleteAtRange(range, { normalize: false });
41 | change.insertBlockAtRange(change.value.selection, exitBlock, {
42 | normalize: false
43 | });
44 | // Exit the code block
45 | change.unwrapNodeByKey(exitBlock.key);
46 |
47 | return change.collapseToStartOf(exitBlock);
48 | }
49 | }
50 |
51 | export type OptionsFormat = {
52 | // Type of the code containers
53 | containerType?: string,
54 | // Type of the code lines
55 | lineType?: string,
56 |
57 | // Mod+Enter will exit the code container, into the given block type.
58 | // Backspace at start of empty code container, will turn it into the given block type.
59 | exitBlockType?: string,
60 | // Should the plugin handle the select all inside a code container
61 | selectAll?: boolean,
62 | // Allow marks inside code blocks
63 | allowMarks?: boolean,
64 | // Returns the indent unit to use at the given selection, as a string
65 | getIndent?: Value => string,
66 | // Custom exit handler
67 | // exitBlockType option is useless if onExit is provided
68 | onExit?: Change => Change
69 | };
70 |
71 | export default Options;
72 |
--------------------------------------------------------------------------------
/lib/utils/deserializeCode.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { Block, Text } from '@gitbook/slate';
3 | import { List } from 'immutable';
4 | import detectNewline from 'detect-newline';
5 |
6 | import type Options from '../options';
7 |
8 | const DEFAULT_NEWLINE = '\n';
9 |
10 | /**
11 | * Deserialize a text into a code block
12 | */
13 | function deserializeCode(opts: Options, text: string): Block {
14 | const sep = detectNewline(text) || DEFAULT_NEWLINE;
15 |
16 | const lines = List(text.split(sep)).map(line =>
17 | Block.create({
18 | type: opts.lineType,
19 | nodes: [Text.create(line)]
20 | })
21 | );
22 |
23 | const code = Block.create({
24 | type: opts.containerType,
25 | nodes: lines
26 | });
27 |
28 | return code;
29 | }
30 |
31 | export default deserializeCode;
32 |
--------------------------------------------------------------------------------
/lib/utils/getCurrentCode.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { type Value, type Block } from '@gitbook/slate';
3 |
4 | import type Options from '../options';
5 |
6 | /**
7 | * Return the current code block, from current selection or from a node key.
8 | */
9 | function getCurrentCode(opts: Options, value: Value, key?: string): ?Block {
10 | const { document } = value;
11 |
12 | let currentBlock;
13 | if (key) {
14 | currentBlock = value.document.getDescendant(key);
15 | } else {
16 | if (!value.selection.startKey) return null;
17 | currentBlock = value.startBlock;
18 | }
19 |
20 | // The structure is always code_block -> code_line -> text
21 | // So the parent of the currentBlock should be the code_block
22 | const parent = document.getParent(currentBlock.key);
23 | if (parent && parent.type === opts.containerType) {
24 | return parent;
25 | }
26 | return null;
27 | }
28 |
29 | export default getCurrentCode;
30 |
--------------------------------------------------------------------------------
/lib/utils/getCurrentIndent.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { type Value } from '@gitbook/slate';
4 |
5 | import type Options from '../options';
6 | import getIndent from './getIndent';
7 | import getCurrentCode from './getCurrentCode';
8 |
9 | /**
10 | * Detect indentation in the current code block
11 | */
12 | function getCurrentIndent(opts: Options, value: Value): string {
13 | if (opts.getIndent) {
14 | return opts.getIndent(value);
15 | }
16 |
17 | const currentCode = getCurrentCode(opts, value);
18 | if (!currentCode) {
19 | return '';
20 | }
21 |
22 | const text = currentCode
23 | .getTexts()
24 | .map(t => t.text)
25 | .join('\n');
26 | return getIndent(text);
27 | }
28 |
29 | export default getCurrentIndent;
30 |
--------------------------------------------------------------------------------
/lib/utils/getIndent.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import detectIndent from 'detect-indent';
3 |
4 | const DEFAULT_INDENTATION = ' ';
5 |
6 | /**
7 | * Detect indentation in a text
8 | */
9 | function getIndent(
10 | text: string,
11 | defaultValue?: string = DEFAULT_INDENTATION
12 | ): string {
13 | return detectIndent(text).indent || defaultValue;
14 | }
15 |
16 | export default getIndent;
17 |
--------------------------------------------------------------------------------
/lib/utils/index.js:
--------------------------------------------------------------------------------
1 | import deserializeCode from './deserializeCode';
2 | import getCurrentCode from './getCurrentCode';
3 | import getCurrentIndent from './getCurrentIndent';
4 | import getIndent from './getIndent';
5 | import isInCodeBlock from './isInCodeBlock';
6 |
7 | export {
8 | deserializeCode,
9 | getCurrentCode,
10 | getCurrentIndent,
11 | getIndent,
12 | isInCodeBlock
13 | };
14 |
--------------------------------------------------------------------------------
/lib/utils/isInCodeBlock.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { type Value } from '@gitbook/slate';
4 |
5 | import type Options from '../options';
6 |
7 | /**
8 | * Test if current selection is in a code block.
9 | */
10 | function isInCodeBlock(opts: Options, value: Value): boolean {
11 | const { document, startKey } = value;
12 | const codeBlock = document.getClosest(
13 | startKey,
14 | block => block.type === opts.containerType
15 | );
16 |
17 | return Boolean(codeBlock);
18 | }
19 |
20 | export default isInCodeBlock;
21 |
--------------------------------------------------------------------------------
/lib/validation/index.js:
--------------------------------------------------------------------------------
1 | import schema from './schema';
2 |
3 | export { schema };
4 |
--------------------------------------------------------------------------------
/lib/validation/schema.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { Block, type Change, type Node } from '@gitbook/slate';
4 | import {
5 | CHILD_TYPE_INVALID,
6 | CHILD_INVALID,
7 | PARENT_TYPE_INVALID,
8 | PARENT_INVALID,
9 | CHILD_OBJECT_INVALID
10 | } from '@gitbook/slate-schema-violations';
11 | import { List } from 'immutable';
12 |
13 | import type Options from '../options';
14 | import { deserializeCode } from '../utils';
15 |
16 | /**
17 | * Create a schema definition with rules to normalize code blocks
18 | */
19 | function schema(opts: Options): Object {
20 | const baseSchema = {
21 | blocks: {
22 | [opts.containerType]: {
23 | nodes: [{ types: [opts.lineType] }],
24 | normalize(change: Change, violation: string, context: Object) {
25 | switch (violation) {
26 | case CHILD_INVALID:
27 | case CHILD_TYPE_INVALID:
28 | return onlyLine(opts, change, context);
29 | default:
30 | return undefined;
31 | }
32 | }
33 | },
34 | [opts.lineType]: {
35 | nodes: [{ objects: ['text'], min: 1 }],
36 | parent: { types: [opts.containerType] },
37 | normalize(change: Change, violation: string, context: Object) {
38 | switch (violation) {
39 | // This constant does not exist yet in
40 | // official Slate, but exists in GitBook's
41 | // fork. Until the PR is merged, we accept both
42 | // https://github.com/ianstormtaylor/slate/pull/1842
43 | case PARENT_INVALID:
44 | case PARENT_TYPE_INVALID:
45 | return noOrphanLine(opts, change, context);
46 | case CHILD_INVALID:
47 | case CHILD_OBJECT_INVALID:
48 | return onlyTextInCode(opts, change, context);
49 | default:
50 | return undefined;
51 | }
52 | }
53 | }
54 | }
55 | };
56 |
57 | if (!opts.allowMarks) {
58 | baseSchema.blocks[opts.lineType].marks = [];
59 | }
60 |
61 | return baseSchema;
62 | }
63 |
64 | /**
65 | * Return a list of group of nodes matching the given filter.
66 | */
67 | function getSuccessiveNodes(
68 | nodes: List,
69 | match: Node => boolean
70 | ): List> {
71 | const nonLines = nodes.takeUntil(match);
72 | const afterNonLines = nodes.skip(nonLines.size);
73 | if (afterNonLines.isEmpty()) {
74 | return List();
75 | }
76 |
77 | const firstGroup = afterNonLines.takeWhile(match);
78 | const restOfNodes = afterNonLines.skip(firstGroup.size);
79 |
80 | return List([firstGroup]).concat(getSuccessiveNodes(restOfNodes, match));
81 | }
82 |
83 | /**
84 | * A rule that ensure code blocks only contain lines of code, and no marks
85 | */
86 | function onlyLine(opts: Options, change: Change, context: Object) {
87 | const isNotLine = n => n.type !== opts.lineType;
88 | const nonLineGroups = getSuccessiveNodes(context.node.nodes, isNotLine);
89 |
90 | nonLineGroups.filter(group => !group.isEmpty()).forEach(nonLineGroup => {
91 | // Convert text to code lines
92 | const text = nonLineGroup.map(n => n.text).join('');
93 | const codeLines = deserializeCode(opts, text).nodes;
94 |
95 | // Insert them in place of the invalid node
96 | const first = nonLineGroup.first();
97 | const parent = change.value.document.getParent(first.key);
98 | const invalidNodeIndex = parent.nodes.indexOf(first);
99 |
100 | codeLines.forEach((codeLine, index) => {
101 | change.insertNodeByKey(
102 | parent.key,
103 | invalidNodeIndex + index,
104 | codeLine,
105 | {
106 | normalize: false
107 | }
108 | );
109 | });
110 |
111 | // Remove the block
112 | nonLineGroup.forEach(n =>
113 | change.removeNodeByKey(n.key, { normalize: false })
114 | );
115 | });
116 |
117 | return change;
118 | }
119 |
120 | /**
121 | * A rule that ensure code lines only contain text
122 | */
123 | function onlyTextInCode(
124 | opts: Options,
125 | change: Change,
126 | context: Object
127 | ): ?Change {
128 | const { node } = context;
129 |
130 | if (node.object === 'inline' || node.object === 'block') {
131 | node.nodes.forEach(child => {
132 | change.unwrapNodeByKey(child.key, { normalize: false });
133 | });
134 |
135 | return change;
136 | }
137 |
138 | return undefined;
139 | }
140 |
141 | /**
142 | * A rule that ensure code lines are always children
143 | * of a code block.
144 | */
145 | function noOrphanLine(opts: Options, change: Change, context: Object): ?Change {
146 | const { parent } = context;
147 |
148 | const isLine = n => n.type === opts.lineType;
149 |
150 | const linesGroup = getSuccessiveNodes(parent.nodes, isLine);
151 |
152 | linesGroup.forEach(group => {
153 | const container = Block.create({ type: opts.containerType, nodes: [] });
154 | const firstLineIndex = parent.nodes.indexOf(group.first());
155 |
156 | change.insertNodeByKey(parent.key, firstLineIndex, container, {
157 | normalize: false
158 | });
159 |
160 | group.forEach((line, index) =>
161 | change.moveNodeByKey(line.key, container.key, index, {
162 | normalize: false
163 | })
164 | );
165 | });
166 | }
167 |
168 | export default schema;
169 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gitbook/slate-edit-code",
3 | "description": "A Slate plugin to handle code blocks editing.",
4 | "version": "0.15.7",
5 | "license": "Apache-2.0",
6 | "repository": "git://github.com/GitbookIO/slate-edit-code.git",
7 | "main": "./dist/index.js",
8 | "dependencies": {
9 | "detect-indent": "^4.0.0",
10 | "detect-newline": "^2.1.0",
11 | "ends-with": "^0.2.0",
12 | "is-hotkey": "^0.1.1"
13 | },
14 | "peerDependencies": {
15 | "immutable": "^3.8.2 || ^4.0.0-rc.12",
16 | "@gitbook/slate": "^0.34.7",
17 | "@gitbook/slate-react": "^0.13.4",
18 | "@gitbook/slate-schema-violations": "^0.1.20"
19 | },
20 | "devDependencies": {
21 | "babel-cli": "^6.26.0",
22 | "babel-core": "^6.26.0",
23 | "babel-eslint": "^8.0.2",
24 | "babel-plugin-transform-flow-strip-types": "^6.22.0",
25 | "babel-preset-es2015": "^6.24.1",
26 | "babel-preset-react": "^6.24.1",
27 | "babel-preset-stage-0": "^6.24.1",
28 | "babelify": "^8.0.0",
29 | "browserify": "^13.3.0",
30 | "eslint": "^4.10.0",
31 | "eslint-config-gitbook": "^2.0.3",
32 | "eslint-plugin-import": "^2.8.0",
33 | "eslint-plugin-prettier": "^2.6.0",
34 | "expect": "^1.20.2",
35 | "flow-bin": "^0.57.3",
36 | "gh-pages": "^0.11.0",
37 | "http-server": "^0.9.0",
38 | "immutable": "^4.0.0-rc.12",
39 | "mocha": "^3.0.1",
40 | "react": "^15.3.0",
41 | "react-dom": "^15.3.0",
42 | "prettier": "^1.13.3",
43 | "@gitbook/slate": "^0.34.11",
44 | "slate-hyperprint": "^2.2.4",
45 | "@gitbook/slate-hyperscript": "^0.5.26",
46 | "@gitbook/slate-react": "^0.13.9",
47 | "@gitbook/slate-schema-violations": "^0.1.24"
48 | },
49 | "scripts": {
50 | "prepublish": "babel ./lib --out-dir ./dist",
51 | "lint": "eslint ./",
52 | "test": "./node_modules/.bin/mocha ./tests/all.js --compilers js:babel-register --reporter=list",
53 | "postpublish": "npm run deploy-example",
54 | "build-example": "browserify ./example/main.js -o ./example/bundle.js -t [ babelify --presets [ es2015 react stage-0 ] ]",
55 | "serve-example": "http-server ./example/ -p 8081",
56 | "start": "npm run build-example && npm run serve-example",
57 | "deploy-example": "npm run build-example && gh-pages -d ./example"
58 | },
59 | "keywords": [
60 | "slate"
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/tests/all.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | /* eslint-disable import/no-dynamic-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 | import EditCode from '../lib';
9 |
10 | const PLUGIN = EditCode();
11 | const SCHEMA = Slate.Schema.create({
12 | plugins: [PLUGIN]
13 | });
14 |
15 | function deserializeValue(value) {
16 | return Slate.Value.fromJS(
17 | {
18 | document: value.document,
19 | selection: value.selection,
20 | schema: SCHEMA
21 | },
22 | { normalize: false }
23 | );
24 | }
25 |
26 | describe('slate-edit-code', () => {
27 | const tests = fs.readdirSync(__dirname);
28 |
29 | tests.forEach(test => {
30 | if (test[0] === '.' || path.extname(test).length > 0) return;
31 |
32 | it(test, () => {
33 | Slate.resetKeyGenerator();
34 | const dir = path.resolve(__dirname, test);
35 | const input = require(path.resolve(dir, 'input.js')).default;
36 | const expectedPath = path.resolve(dir, 'expected.js');
37 | const expected =
38 | fs.existsSync(expectedPath) && require(expectedPath).default;
39 |
40 | const runChange = require(path.resolve(dir, 'change.js')).default;
41 |
42 | const valueInput = deserializeValue(input);
43 |
44 | const newChange = runChange(PLUGIN, valueInput.change());
45 |
46 | if (expected) {
47 | const newDoc = hyperprint(newChange.value.document, {
48 | strict: true
49 | });
50 | expect(newDoc).toEqual(
51 | hyperprint(expected.document, { strict: true })
52 | );
53 | }
54 | });
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/tests/backspace-empty-code/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('backspace'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/backspace-empty-code/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/tests/backspace-empty-code/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/backspace-start/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('backspace'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/backspace-start/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/backspace-start/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | {' '}
10 |
11 | Line 1
12 |
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/tests/enter-end-offset/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('enter'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/enter-end-offset/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some code
9 |
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/tests/enter-end-offset/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Some code
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/enter-mid-offset/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('enter'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/enter-mid-offset/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some
9 | code
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/tests/enter-mid-offset/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Some
10 | code
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/enter-start-offset/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('enter'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/enter-start-offset/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Some code
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/tests/enter-start-offset/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some code
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/enter-with-indent/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('enter'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/enter-with-indent/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 |
10 | {' '}
11 | Li
12 |
13 |
14 | {' '}
15 | ne 2
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/tests/enter-with-indent/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 |
10 | {' '}
11 | Li
12 |
13 | ne 2
14 |
15 |
16 |
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/tests/hyperscript.js:
--------------------------------------------------------------------------------
1 | import { createHyperscript } from '@gitbook/slate-hyperscript';
2 |
3 | const h = createHyperscript({
4 | blocks: {
5 | code_block: 'code_block',
6 | code_line: 'code_line',
7 | paragraph: 'paragraph',
8 | default: 'default'
9 | },
10 | inlines: {
11 | link: 'link',
12 | 'unknown-inline': 'unknown-inline'
13 | },
14 | marks: {
15 | italic: 'italic'
16 | }
17 | });
18 |
19 | export default h;
20 |
--------------------------------------------------------------------------------
/tests/isincodeblock-true/change.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 |
3 | export default function(plugin, change) {
4 | assert.equal(plugin.utils.isInCodeBlock(change.value), true);
5 |
6 | return change;
7 | }
8 |
--------------------------------------------------------------------------------
/tests/isincodeblock-true/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/isincodeblock-true/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/on-exit-block/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('mod+enter'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/on-exit-block/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Li
9 |
10 |
11 |
12 | ne 1
13 |
14 | {' '}
15 | Line 2
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/tests/on-exit-block/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Li
10 |
11 | ne 1
12 |
13 |
14 | {' '}
15 | Line 2
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/tests/paste-middle/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | return plugin.onPaste(
3 | {
4 | preventDefault() {},
5 | stopPropagation() {},
6 | clipboardData: {
7 | // Simulate a text data from IE
8 | // https://github.com/ianstormtaylor/slate/blob/master/packages/slate-react/src/utils/get-event-transfer.js#L161
9 | getData: () => 'Yes\nNo\nQuestion?'
10 | }
11 | },
12 | change,
13 | {}
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/tests/paste-middle/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | SomeYes
9 | No
10 | Question? code
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/tests/paste-middle/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Some
10 | code
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/schema-multiline-text/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-multiline-text/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | This node must be converted.multiple
9 | lines
10 | of
11 | code and marks
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/schema-multiline-text/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | This node must be converted.
9 | {'multiple\nlines\nof\ncode'}
10 | and marks
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/tests/schema-no-inlines/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-no-inlines/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some link in code
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/schema-no-inlines/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Some link in code
10 |
11 |
12 |
13 |
14 | );
15 |
16 | // BITE
17 |
--------------------------------------------------------------------------------
/tests/schema-no-marks/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-no-marks/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some formatted text
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/schema-no-marks/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Some formatted text
10 |
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/tests/schema-no-multiple-orphan-lines/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-no-multiple-orphan-lines/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 | Line 2
10 |
11 | cut
12 |
13 | Line 3
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/tests/schema-no-multiple-orphan-lines/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Line 1
8 | Line 2
9 | cut
10 | Line 3
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/tests/schema-no-orphan-lines-wrapped/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-no-orphan-lines-wrapped/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Line 1
10 | Line 2
11 | Line 3
12 |
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/tests/schema-no-orphan-lines-wrapped/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 | Line 2
10 | Line 3
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/tests/schema-no-single-orphan-line/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-no-single-orphan-line/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some orphan code
9 |
10 |
11 | Some other code
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/schema-no-single-orphan-line/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Some orphan code
8 |
9 | Some other code
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/tests/schema-only-lines-empty/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-only-lines-empty/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/schema-only-lines-empty/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/schema-only-lines/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-only-lines/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Hello
10 | invalid
11 | World
12 | invalid
13 | again
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/tests/schema-only-lines/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | Hello
10 | invalid
11 | World
12 | {'invalid\n again'}
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/tests/schema-only-text-join/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-only-text-join/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | One textTwo text
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/schema-only-text-join/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | One text
10 | Two text
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/schema-only-text-with-blocks/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-only-text-with-blocks/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | One textTwo text
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/schema-only-text-with-blocks/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import { Value } from '@gitbook/slate';
3 | import hyperscript from '../hyperscript';
4 |
5 | export default Value.fromJS(
6 | {
7 | document: (
8 |
9 |
10 |
11 | One text
12 | Unwanted text
13 | Two text
14 |
15 |
16 |
17 | )
18 | },
19 | { normalize: false }
20 | );
21 |
--------------------------------------------------------------------------------
/tests/schema-only-text/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-only-text/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some
9 | {' code'}
10 |
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/tests/schema-only-text/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | {'Some\n code'}
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/schema-single-text/change.js:
--------------------------------------------------------------------------------
1 | import Slate from '@gitbook/slate';
2 |
3 | export default function(plugin, change) {
4 | const schema = new Slate.Schema(plugin.schema);
5 | return change.normalize(schema);
6 | }
7 |
--------------------------------------------------------------------------------
/tests/schema-single-text/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | One textTwo textThree text
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/schema-single-text/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | One textTwo textThree text
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/select-all/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('mod+a'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/select-all/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 | Line 2
10 | Line 3
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/tests/select-all/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 | Line 2
10 | Line 3
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/tests/shift-tab-middle-offset/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('shift+tab'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/shift-tab-middle-offset/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | {' '}
10 | Line 1
11 |
12 | Line 2
13 |
14 | {' '}
15 | Line 3
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/tests/shift-tab-middle-offset/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | {' '}
10 | Line 1
11 |
12 |
13 | {' '}L
14 | ine 2
15 |
16 |
17 | {' '}
18 | Line 3
19 |
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/tests/shift-tab-multilines/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('shift+tab'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/shift-tab-multilines/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 | Line 2
10 |
11 | {' '}
12 | Line 3
13 |
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/tests/shift-tab-multilines/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 |
10 | {' '}
11 | Line 1
12 |
13 |
14 | {' '}
15 | Li
16 |
17 | ne 2
18 |
19 |
20 | {' '}
21 | Line 3
22 |
23 |
24 |
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/tests/simulate-key.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { parseHotkey } from 'is-hotkey';
3 |
4 | // Returns a fake Event object for the given hotkey
5 | export default function simulateKey(hotkey: string): Event {
6 | return {
7 | preventDefault() {},
8 | stopPropagation() {},
9 | ...parseHotkey(hotkey, { byKey: true })
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/tests/tab-middle-offset/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('tab'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/tab-middle-offset/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 |
10 | {' '}
11 | Line 2
12 |
13 |
14 | {' '}
15 | Line 3
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/tests/tab-middle-offset/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Line 1
9 |
10 | {' '}
11 |
12 | Line 2
13 |
14 |
15 | {' '}
16 | Line 3
17 |
18 |
19 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/tests/tab-multilines/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | return plugin.onKeyDown(simulateKey('tab'), change, {});
5 | }
6 |
--------------------------------------------------------------------------------
/tests/tab-multilines/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | {' '}
10 | Line 1
11 |
12 |
13 | {' '}
14 | Line 2
15 |
16 | Line 3
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/tests/tab-multilines/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 |
10 | Line 1
11 |
12 |
13 | Line 2
14 |
15 | Line 3
16 |
17 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/tests/tab-start-offset/change.js:
--------------------------------------------------------------------------------
1 | import simulateKey from '../simulate-key';
2 |
3 | export default function(plugin, change) {
4 | const { value } = change;
5 | const block = value.document.findDescendant(
6 | node => node.type == 'code_block'
7 | );
8 |
9 | change.collapseToStartOf(block).moveOffsetsTo(0);
10 |
11 | return plugin.onKeyDown(simulateKey('tab'), change, {});
12 | }
13 |
--------------------------------------------------------------------------------
/tests/tab-start-offset/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 |
9 | {' '}
10 | Some code
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/tests/tab-start-offset/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Some code
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/togglecodeblock-code/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | return plugin.changes.toggleCodeBlock(change, 'paragraph');
3 | }
4 |
--------------------------------------------------------------------------------
/tests/togglecodeblock-code/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/togglecodeblock-code/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Hello world
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/tests/togglecodeblock-normal/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | return plugin.changes.toggleCodeBlock(change, 'paragraph');
3 | }
4 |
--------------------------------------------------------------------------------
/tests/togglecodeblock-normal/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Hello world
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/tests/togglecodeblock-normal/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-multi-lines/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | return plugin.changes.unwrapCodeBlock(change, 'paragraph');
3 | }
4 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-multi-lines/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Hello
8 | world
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-multi-lines/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello
9 | world
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-normal/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | return plugin.changes.unwrapCodeBlock(change, 'paragraph');
3 | }
4 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-normal/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Hello world
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-normal/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-selection/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | const newValue = plugin.changes.unwrapCodeBlock(change, 'paragraph');
3 |
4 | return newValue;
5 | }
6 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-selection/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Hello world
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/tests/unwrapcodeblock-selection/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-normal/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | return plugin.changes.wrapCodeBlock(change);
3 | }
4 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-normal/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-normal/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 | Hello world
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-selection/change.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 |
3 | export default function(plugin, change) {
4 | plugin.changes.wrapCodeBlock(change);
5 |
6 | assert.equal(change.value.startOffset, 5);
7 |
8 | return change;
9 | }
10 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-selection/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-selection/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello
9 | world
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-with-inline/change.js:
--------------------------------------------------------------------------------
1 | export default function(plugin, change) {
2 | return plugin.changes.wrapCodeBlock(change);
3 | }
4 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-with-inline/expected.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | import hyperscript from '../hyperscript';
3 |
4 | export default (
5 |
6 |
7 |
8 | Hello world
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/tests/wrapcodeblock-with-inline/input.js:
--------------------------------------------------------------------------------
1 | /** @jsx hyperscript */
2 | /* eslint-disable react/void-dom-elements-no-children */
3 | import hyperscript from '../hyperscript';
4 |
5 | export default (
6 |
7 |
8 |
9 | Hello world
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------