fix: resolve TypeScript errors in frontend build
This commit is contained in:
8
node_modules/prosemirror-commands/.tern-project
generated
vendored
Normal file
8
node_modules/prosemirror-commands/.tern-project
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"libs": ["browser"],
|
||||
"plugins": {
|
||||
"node": {},
|
||||
"complete_strings": {},
|
||||
"es_modules": {}
|
||||
}
|
||||
}
|
||||
322
node_modules/prosemirror-commands/CHANGELOG.md
generated
vendored
Normal file
322
node_modules/prosemirror-commands/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
## 1.7.1 (2025-04-13)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix a regression in `splitBlock` that would cause it to crash, rather than return false, when no split is possible.
|
||||
|
||||
## 1.7.0 (2025-02-20)
|
||||
|
||||
### New features
|
||||
|
||||
`toggleMark` now accepts an `includeWhitespace` option that controls whether it affects leading/trailing space.
|
||||
|
||||
## 1.6.2 (2024-10-24)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Make `splitBlock` smart enough to split blocks when the cursor is inside a nested inline node.
|
||||
|
||||
## 1.6.1 (2024-10-11)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
`joinBackward` will, when moving a node into a block, no longer join that block with the block after when the two have compatible content but aren't of the same type.
|
||||
|
||||
Fix an issue in `splitBlock` that caused it to return true without doing anything when the schema makes splitting at the cursor impossible.
|
||||
|
||||
Support implicit conversion between hard break nodes and newlines in the `joinForward` and `joinBackward` commands.
|
||||
|
||||
## 1.6.0 (2024-07-26)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix an issue where `joinBackward` couldn't lift the block with the cursor when the block before it was isolating.
|
||||
|
||||
### New features
|
||||
|
||||
`toggleMark` now takes an option that controls its behavior when only part of the selection has the mark already.
|
||||
|
||||
The function given to `splitBlockAs` now has access to the split position via a third parameter.
|
||||
|
||||
`toggleMark` now takes an `enterInlineAtoms` option that controls whether it descends into atom nodes.
|
||||
|
||||
## 1.5.2 (2023-05-17)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Include CommonJS type declarations in the package to please new TypeScript resolution settings.
|
||||
|
||||
## 1.5.1 (2023-03-01)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix `joinTextblockBackward` not applying when the textblock before was wrapped in another node.
|
||||
|
||||
## 1.5.0 (2022-12-05)
|
||||
|
||||
### New features
|
||||
|
||||
The new `splitBlockAs` command-builder allows you to pass in custom logic to determine the type of block that should be split off.
|
||||
|
||||
## 1.4.0 (2022-12-01)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Make `setBlockType` act on all selection ranges in selections that have them.
|
||||
|
||||
### New features
|
||||
|
||||
The new `joinTextblockForward` and `joinTextblockBackward` commands provide a more primitive command for delete/backspace behavior when you don't want the extra strategies implemented by `joinForward`/`joinBackward`.
|
||||
|
||||
## 1.3.1 (2022-09-08)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Make sure `toggleMark` doesn't add marks to top nodes with non-inline content.
|
||||
|
||||
## 1.3.0 (2022-05-30)
|
||||
|
||||
### New features
|
||||
|
||||
Include TypeScript type declarations.
|
||||
|
||||
## 1.2.2 (2022-03-16)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Don't override behavior of Home and End keys in base keymap.
|
||||
|
||||
## 1.2.1 (2022-01-20)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix an issue where `joinBackward` and `joinForward` would return true when activated with the cursor in an empty but undeletable block, but not make any change.
|
||||
|
||||
## 1.2.0 (2022-01-17)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Add a workaround for a bug on macOS where Ctrl-a and Ctrl-e getting stuck at the edge of inline nodes.
|
||||
|
||||
### New features
|
||||
|
||||
The new `selectTextblockEnd` and `selectTextblockStart` commands move the cursor to the start/end of the textblock, when inside one.
|
||||
|
||||
Ctrl-a/e on macOS and Home/End on other platforms are now bound to `selectTextblockEnd` and `selectTextblockStart`.
|
||||
|
||||
## 1.1.12 (2021-10-29)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix issue where the default PC keymap was used on recent versions of iPhone or iPad operating systems.
|
||||
|
||||
## 1.1.11 (2021-10-06)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Add a binding for Shift-Backspace to the base keymap, so that shift or caps-lock won't interfere with backspace behavior.
|
||||
|
||||
Fix an issue in `autoJoin` that made it ignore a third argument if it was passed one.
|
||||
|
||||
## 1.1.10 (2021-07-05)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Make `joinBackward` capable of joining textblocks wrapped in parent nodes when the parent nodes themselves can't be joined (for example two list items which allow only a single paragraph).
|
||||
|
||||
## 1.1.9 (2021-06-07)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix a regression where `splitBlock` could crash when splitting at the end of a non-default block.
|
||||
|
||||
## 1.1.8 (2021-05-22)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix a crash in `splitBlock` that occurred with certain types of schemas.
|
||||
|
||||
## 1.1.7 (2021-02-22)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix a regression where `createParagraphNear` no longer fired for gap cursor selections.
|
||||
|
||||
## 1.1.6 (2021-02-10)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Improve behavior of enter when the entire document is selected.
|
||||
|
||||
## 1.1.5 (2021-01-14)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
`joinBackward` and `joinForward` will now, when the textblock after the cut can't be moved into the structure before the cut, try to just join the inline content onto the last child in the structure before the cut.
|
||||
|
||||
`toggleMark` will now skip whitespace at the start and end of the selection when adding a mark.
|
||||
|
||||
## 1.1.4 (2020-04-15)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
`selectNodeForward` and `selectNodeBackward` will now also select nodes next to a gap cursor (or other custom empty selection type).
|
||||
|
||||
## 1.1.3 (2020-01-03)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix an issue where, since version 1.7.4 of prosemirror-model, `splitBlock` fails to create the expected new textblock in some schemas.
|
||||
|
||||
## 1.1.2 (2019-11-20)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Rename ES module files to use a .js extension, since Webpack gets confused by .mjs
|
||||
|
||||
## 1.1.1 (2019-11-19)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
The file referred to in the package's `module` field now is compiled down to ES5.
|
||||
|
||||
## 1.1.0 (2019-11-08)
|
||||
|
||||
### New features
|
||||
|
||||
Add a `module` field to package json file.
|
||||
|
||||
## 1.0.8 (2019-05-14)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix a crash caused by using a position potentially outside the document in [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock).
|
||||
|
||||
## 1.0.7 (2018-04-09)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fixes an issue where [`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward) might create a selection pointing into the old document.
|
||||
|
||||
## 1.0.6 (2018-04-04)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
The [`setBlockType` command](https://prosemirror.net/docs/ref/#commands.setBlockType) command is now considered applicable when _any_ of the selected textblocks can be changed (it used to only look at the first one).
|
||||
|
||||
Fix crash when calling [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) when the selection isn't in a block node (by disabling the command in that case).
|
||||
|
||||
Fixes an issue where [`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward) might create a selection pointing into the old document.
|
||||
|
||||
## 1.0.5 (2018-01-30)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix crash in [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) when `defaultContentType` returns null.
|
||||
|
||||
## 1.0.4 (2018-01-18)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Pressing delete in front of an [isolating](https://prosemirror.net/docs/ref/#model.NodeSpec.isolating) node no longer opens it.
|
||||
|
||||
## 1.0.3 (2017-12-19)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix issue where [`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward) would sometimes create an invalid selection.
|
||||
|
||||
## 1.0.2 (2017-11-21)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
[`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) no longer crashes when used in a block that's it's parent node's only allowed child.
|
||||
|
||||
## 1.0.0 (2017-10-13)
|
||||
|
||||
### New features
|
||||
|
||||
The [`setBlockType` command](https://prosemirror.net/docs/ref/#commands.setBlockType) can now be used to change the types of multiple selected textblocks (rather than only one).
|
||||
|
||||
The platform-dependent versions of the [base keymap](https://prosemirror.net/docs/ref/#commands.baseKeymap) are now exported separately as [`pcBaseKeymap`](https://prosemirror.net/docs/ref/#commands.pcBaseKeymap) and [`macBaseKeymap`](https://prosemirror.net/docs/ref/#commands.macBaseKeymap).
|
||||
|
||||
## 0.23.0 (2017-09-13)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
`joinForward` and `joinBackward` no longer fall back to selecting the next node when no other behavior is possible. There are now separate commands `selectNodeForward` and `selectNodeBackward` that do this, which the base keymap binds as fallback behavior.
|
||||
|
||||
[`baseKeymap`](https://prosemirror.net/docs/ref/version/0.23.0.html#commands.baseKeymap) no longer binds keys for `joinUp`, `joinDown`, `lift`, and `selectParentNode`.
|
||||
|
||||
### New features
|
||||
|
||||
New commands [`selectNodeForward`](https://prosemirror.net/docs/ref/version/0.23.0.html#commands.selectNodeForward) and [`selectNodeBackward`](https://prosemirror.net/docs/ref/version/0.23.0.html#commands.selectNodeBackward) added.
|
||||
|
||||
## 0.20.0 (2017-04-03)
|
||||
|
||||
### New features
|
||||
|
||||
The new [`selectAll` command](https://prosemirror.net/docs/ref/version/0.20.0.html#commands.selectAll), bound to Mod-a in the base keymap, sets the selection to an [`AllSelection`](https://prosemirror.net/docs/ref/version/0.20.0.html#state.AllSelection).
|
||||
|
||||
## 0.19.0 (2017-03-16)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Calling `joinBackward` at the start of a node that can't be joined no longer raises an error.
|
||||
|
||||
## 0.18.0 (2017-02-24)
|
||||
|
||||
### New features
|
||||
|
||||
New command [`splitBlockKeepMarks`](https://prosemirror.net/docs/ref/version/0.18.0.html#commands.splitBlockKeepMarks) which splits a block but preserves the marks at the cursor.
|
||||
|
||||
## 0.17.1 (2017-01-16)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Make sure [`toggleMark`](https://prosemirror.net/docs/ref/version/0.17.0.html#commands.toggleMark) also works in the top-level node (when it is a textblock).
|
||||
|
||||
## 0.17.0 (2017-01-05)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
The `dispatch` function passed to commands is now passed a [`Transaction`](https://prosemirror.net/docs/ref/version/0.17.0.html#state.Transaction), not an action object.
|
||||
|
||||
## 0.15.0 (2016-12-10)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
Drops suppport for `delete(Char|Word)(Before|After)` and `move(Back|For)ward`, since we are now letting the browser handle those natively.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
The [`joinForward`](https://prosemirror.net/docs/ref/version/0.15.0.html#commands.joinForward) and [`joinBackward`](https://prosemirror.net/docs/ref/version/0.15.0.html#commands.joinBackward) commands can now strip out markup and nodes that aren't allowed in the joined node.
|
||||
|
||||
### New features
|
||||
|
||||
A new command [`exitCode`](https://prosemirror.net/docs/ref/version/0.15.0.html#commands.exitCode) allows a user to exit a code block by creating a new paragraph below it.
|
||||
|
||||
The [`joinForward`](https://prosemirror.net/docs/ref/version/0.15.0.html#commands.joinForward) and [`joinBackward`](https://prosemirror.net/docs/ref/version/0.15.0.html#commands.joinBackward) commands now use a bidirectional-text-aware way to determine whether the cursor is at the proper side of its parent textblock when they are passed a view.
|
||||
|
||||
## 0.13.0 (2016-11-11)
|
||||
|
||||
### New features
|
||||
|
||||
The [`autoJoin`](https://prosemirror.net/docs/ref/version/0.13.0.html#commands.autoJoin) function allows you to wrap command functions so that when the command makes nodes of a certain type occur next to each other, they are automatically joined.
|
||||
|
||||
## 0.12.0 (2016-10-21)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix crash when backspacing into nodes with complex content
|
||||
expressions.
|
||||
|
||||
## 0.11.0 (2016-09-21)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
Moved into a separate module.
|
||||
|
||||
The interface for command functions was changed to work with the new
|
||||
[state](https://prosemirror.net/docs/ref/version/0.11.0.html#state.EditorState)/[action](https://prosemirror.net/docs/ref/version/0.11.0.html#state.Action) abstractions.
|
||||
|
||||
104
node_modules/prosemirror-commands/CONTRIBUTING.md
generated
vendored
Normal file
104
node_modules/prosemirror-commands/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# How to contribute
|
||||
|
||||
- [Getting help](#getting-help)
|
||||
- [Submitting bug reports](#submitting-bug-reports)
|
||||
- [Contributing code](#contributing-code)
|
||||
|
||||
## Getting help
|
||||
|
||||
Community discussion, questions, and informal bug reporting is done on the
|
||||
[discuss.ProseMirror forum](http://discuss.prosemirror.net).
|
||||
|
||||
## Submitting bug reports
|
||||
|
||||
Report bugs on the
|
||||
[GitHub issue tracker](http://github.com/prosemirror/prosemirror/issues).
|
||||
Before reporting a bug, please read these pointers.
|
||||
|
||||
- The issue tracker is for *bugs*, not requests for help. Questions
|
||||
should be asked on the [forum](http://discuss.prosemirror.net).
|
||||
|
||||
- Include information about the version of the code that exhibits the
|
||||
problem. For browser-related issues, include the browser and browser
|
||||
version on which the problem occurred.
|
||||
|
||||
- Mention very precisely what went wrong. "X is broken" is not a good
|
||||
bug report. What did you expect to happen? What happened instead?
|
||||
Describe the exact steps a maintainer has to take to make the
|
||||
problem occur. A screencast can be useful, but is no substitute for
|
||||
a textual description.
|
||||
|
||||
- A great way to make it easy to reproduce your problem, if it can not
|
||||
be trivially reproduced on the website demos, is to submit a script
|
||||
that triggers the issue.
|
||||
|
||||
## Contributing code
|
||||
|
||||
If you want to make a change that involves a significant overhaul of
|
||||
the code or introduces a user-visible new feature, create an
|
||||
[RFC](https://github.com/ProseMirror/rfcs/) first with your proposal.
|
||||
|
||||
- Make sure you have a [GitHub Account](https://github.com/signup/free)
|
||||
|
||||
- Fork the relevant repository
|
||||
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
||||
|
||||
- Create a local checkout of the code. You can use the
|
||||
[main repository](https://github.com/prosemirror/prosemirror) to
|
||||
easily check out all core modules.
|
||||
|
||||
- Make your changes, and commit them
|
||||
|
||||
- Follow the code style of the rest of the project (see below). Run
|
||||
`npm run lint` (in the main repository checkout) to make sure that
|
||||
the linter is happy.
|
||||
|
||||
- If your changes are easy to test or likely to regress, add tests in
|
||||
the relevant `test/` directory. Either put them in an existing
|
||||
`test-*.js` file, if they fit there, or add a new file.
|
||||
|
||||
- Make sure all tests pass. Run `npm run test` to verify tests pass
|
||||
(you will need Node.js v6+).
|
||||
|
||||
- Submit a pull request ([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
|
||||
Don't put more than one feature/fix in a single pull request.
|
||||
|
||||
By contributing code to ProseMirror you
|
||||
|
||||
- Agree to license the contributed code under the project's [MIT
|
||||
license](https://github.com/ProseMirror/prosemirror/blob/master/LICENSE).
|
||||
|
||||
- Confirm that you have the right to contribute and license the code
|
||||
in question. (Either you hold all rights on the code, or the rights
|
||||
holder has explicitly granted the right to use it like this,
|
||||
through a compatible open source license or through a direct
|
||||
agreement with you.)
|
||||
|
||||
### Coding standards
|
||||
|
||||
- ES6 syntax, targeting an ES5 runtime (i.e. don't use library
|
||||
elements added by ES6, don't use ES7/ES.next syntax).
|
||||
|
||||
- 2 spaces per indentation level, no tabs.
|
||||
|
||||
- No semicolons except when necessary.
|
||||
|
||||
- Follow the surrounding code when it comes to spacing, brace
|
||||
placement, etc.
|
||||
|
||||
- Brace-less single-statement bodies are encouraged (whenever they
|
||||
don't impact readability).
|
||||
|
||||
- [getdocs](https://github.com/marijnh/getdocs)-style doc comments
|
||||
above items that are part of the public API.
|
||||
|
||||
- When documenting non-public items, you can put the type after a
|
||||
single colon, so that getdocs doesn't pick it up and add it to the
|
||||
API reference.
|
||||
|
||||
- The linter (`npm run lint`) complains about unused variables and
|
||||
functions. Prefix their names with an underscore to muffle it.
|
||||
|
||||
- ProseMirror does *not* follow JSHint or JSLint prescribed style.
|
||||
Patches that try to 'fix' code to pass one of these linters will not
|
||||
be accepted.
|
||||
19
node_modules/prosemirror-commands/LICENSE
generated
vendored
Normal file
19
node_modules/prosemirror-commands/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
28
node_modules/prosemirror-commands/README.md
generated
vendored
Normal file
28
node_modules/prosemirror-commands/README.md
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# prosemirror-commands
|
||||
|
||||
[ [**WEBSITE**](https://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**CHANGELOG**](https://github.com/ProseMirror/prosemirror-commands/blob/master/CHANGELOG.md) ]
|
||||
|
||||
This is a [core module](https://prosemirror.net/docs/ref/#commands) of [ProseMirror](https://prosemirror.net).
|
||||
ProseMirror is a well-behaved rich semantic content editor based on
|
||||
contentEditable, with support for collaborative editing and custom
|
||||
document schemas.
|
||||
|
||||
This [module](https://prosemirror.net/docs/ref/#commands) implements a
|
||||
number of editing commands, which are functions that abstract editing
|
||||
actions which can be bound to keys.
|
||||
|
||||
The [project page](https://prosemirror.net) has more information, a
|
||||
number of [examples](https://prosemirror.net/examples/) and the
|
||||
[documentation](https://prosemirror.net/docs/).
|
||||
|
||||
This code is released under an
|
||||
[MIT license](https://github.com/prosemirror/prosemirror/tree/master/LICENSE).
|
||||
There's a [forum](http://discuss.prosemirror.net) for general
|
||||
discussion and support requests, and the
|
||||
[Github bug tracker](https://github.com/prosemirror/prosemirror/issues)
|
||||
is the place to report issues.
|
||||
|
||||
We aim to be an inclusive, welcoming community. To make that explicit,
|
||||
we have a [code of
|
||||
conduct](http://contributor-covenant.org/version/1/1/0/) that applies
|
||||
to communication around the project.
|
||||
687
node_modules/prosemirror-commands/dist/index.cjs
generated
vendored
Normal file
687
node_modules/prosemirror-commands/dist/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,687 @@
|
||||
'use strict';
|
||||
|
||||
var prosemirrorTransform = require('prosemirror-transform');
|
||||
var prosemirrorModel = require('prosemirror-model');
|
||||
var prosemirrorState = require('prosemirror-state');
|
||||
var deleteSelection = function deleteSelection(state, dispatch) {
|
||||
if (state.selection.empty) return false;
|
||||
if (dispatch) dispatch(state.tr.deleteSelection().scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function atBlockStart(state, view) {
|
||||
var $cursor = state.selection.$cursor;
|
||||
if (!$cursor || (view ? !view.endOfTextblock("backward", state) : $cursor.parentOffset > 0)) return null;
|
||||
return $cursor;
|
||||
}
|
||||
var joinBackward = function joinBackward(state, dispatch, view) {
|
||||
var $cursor = atBlockStart(state, view);
|
||||
if (!$cursor) return false;
|
||||
var $cut = findCutBefore($cursor);
|
||||
if (!$cut) {
|
||||
var range = $cursor.blockRange(),
|
||||
target = range && prosemirrorTransform.liftTarget(range);
|
||||
if (target == null) return false;
|
||||
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
var before = $cut.nodeBefore;
|
||||
if (deleteBarrier(state, $cut, dispatch, -1)) return true;
|
||||
if ($cursor.parent.content.size == 0 && (textblockAt(before, "end") || prosemirrorState.NodeSelection.isSelectable(before))) {
|
||||
for (var depth = $cursor.depth;; depth--) {
|
||||
var delStep = prosemirrorTransform.replaceStep(state.doc, $cursor.before(depth), $cursor.after(depth), prosemirrorModel.Slice.empty);
|
||||
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
|
||||
if (dispatch) {
|
||||
var tr = state.tr.step(delStep);
|
||||
tr.setSelection(textblockAt(before, "end") ? prosemirrorState.Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1) : prosemirrorState.NodeSelection.create(tr.doc, $cut.pos - before.nodeSize));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (depth == 1 || $cursor.node(depth - 1).childCount > 1) break;
|
||||
}
|
||||
}
|
||||
if (before.isAtom && $cut.depth == $cursor.depth - 1) {
|
||||
if (dispatch) dispatch(state.tr["delete"]($cut.pos - before.nodeSize, $cut.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
var joinTextblockBackward = function joinTextblockBackward(state, dispatch, view) {
|
||||
var $cursor = atBlockStart(state, view);
|
||||
if (!$cursor) return false;
|
||||
var $cut = findCutBefore($cursor);
|
||||
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
|
||||
};
|
||||
var joinTextblockForward = function joinTextblockForward(state, dispatch, view) {
|
||||
var $cursor = atBlockEnd(state, view);
|
||||
if (!$cursor) return false;
|
||||
var $cut = findCutAfter($cursor);
|
||||
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
|
||||
};
|
||||
function joinTextblocksAround(state, $cut, dispatch) {
|
||||
var before = $cut.nodeBefore,
|
||||
beforeText = before,
|
||||
beforePos = $cut.pos - 1;
|
||||
for (; !beforeText.isTextblock; beforePos--) {
|
||||
if (beforeText.type.spec.isolating) return false;
|
||||
var child = beforeText.lastChild;
|
||||
if (!child) return false;
|
||||
beforeText = child;
|
||||
}
|
||||
var after = $cut.nodeAfter,
|
||||
afterText = after,
|
||||
afterPos = $cut.pos + 1;
|
||||
for (; !afterText.isTextblock; afterPos++) {
|
||||
if (afterText.type.spec.isolating) return false;
|
||||
var _child = afterText.firstChild;
|
||||
if (!_child) return false;
|
||||
afterText = _child;
|
||||
}
|
||||
var step = prosemirrorTransform.replaceStep(state.doc, beforePos, afterPos, prosemirrorModel.Slice.empty);
|
||||
if (!step || step.from != beforePos || step instanceof prosemirrorTransform.ReplaceStep && step.slice.size >= afterPos - beforePos) return false;
|
||||
if (dispatch) {
|
||||
var tr = state.tr.step(step);
|
||||
tr.setSelection(prosemirrorState.TextSelection.create(tr.doc, beforePos));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function textblockAt(node, side) {
|
||||
var only = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
||||
for (var scan = node; scan; scan = side == "start" ? scan.firstChild : scan.lastChild) {
|
||||
if (scan.isTextblock) return true;
|
||||
if (only && scan.childCount != 1) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var selectNodeBackward = function selectNodeBackward(state, dispatch, view) {
|
||||
var _state$selection = state.selection,
|
||||
$head = _state$selection.$head,
|
||||
empty = _state$selection.empty,
|
||||
$cut = $head;
|
||||
if (!empty) return false;
|
||||
if ($head.parent.isTextblock) {
|
||||
if (view ? !view.endOfTextblock("backward", state) : $head.parentOffset > 0) return false;
|
||||
$cut = findCutBefore($head);
|
||||
}
|
||||
var node = $cut && $cut.nodeBefore;
|
||||
if (!node || !prosemirrorState.NodeSelection.isSelectable(node)) return false;
|
||||
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, $cut.pos - node.nodeSize)).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function findCutBefore($pos) {
|
||||
if (!$pos.parent.type.spec.isolating) for (var i = $pos.depth - 1; i >= 0; i--) {
|
||||
if ($pos.index(i) > 0) return $pos.doc.resolve($pos.before(i + 1));
|
||||
if ($pos.node(i).type.spec.isolating) break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function atBlockEnd(state, view) {
|
||||
var $cursor = state.selection.$cursor;
|
||||
if (!$cursor || (view ? !view.endOfTextblock("forward", state) : $cursor.parentOffset < $cursor.parent.content.size)) return null;
|
||||
return $cursor;
|
||||
}
|
||||
var joinForward = function joinForward(state, dispatch, view) {
|
||||
var $cursor = atBlockEnd(state, view);
|
||||
if (!$cursor) return false;
|
||||
var $cut = findCutAfter($cursor);
|
||||
if (!$cut) return false;
|
||||
var after = $cut.nodeAfter;
|
||||
if (deleteBarrier(state, $cut, dispatch, 1)) return true;
|
||||
if ($cursor.parent.content.size == 0 && (textblockAt(after, "start") || prosemirrorState.NodeSelection.isSelectable(after))) {
|
||||
var delStep = prosemirrorTransform.replaceStep(state.doc, $cursor.before(), $cursor.after(), prosemirrorModel.Slice.empty);
|
||||
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
|
||||
if (dispatch) {
|
||||
var tr = state.tr.step(delStep);
|
||||
tr.setSelection(textblockAt(after, "start") ? prosemirrorState.Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1) : prosemirrorState.NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (after.isAtom && $cut.depth == $cursor.depth - 1) {
|
||||
if (dispatch) dispatch(state.tr["delete"]($cut.pos, $cut.pos + after.nodeSize).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
var selectNodeForward = function selectNodeForward(state, dispatch, view) {
|
||||
var _state$selection2 = state.selection,
|
||||
$head = _state$selection2.$head,
|
||||
empty = _state$selection2.empty,
|
||||
$cut = $head;
|
||||
if (!empty) return false;
|
||||
if ($head.parent.isTextblock) {
|
||||
if (view ? !view.endOfTextblock("forward", state) : $head.parentOffset < $head.parent.content.size) return false;
|
||||
$cut = findCutAfter($head);
|
||||
}
|
||||
var node = $cut && $cut.nodeAfter;
|
||||
if (!node || !prosemirrorState.NodeSelection.isSelectable(node)) return false;
|
||||
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, $cut.pos)).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function findCutAfter($pos) {
|
||||
if (!$pos.parent.type.spec.isolating) for (var i = $pos.depth - 1; i >= 0; i--) {
|
||||
var parent = $pos.node(i);
|
||||
if ($pos.index(i) + 1 < parent.childCount) return $pos.doc.resolve($pos.after(i + 1));
|
||||
if (parent.type.spec.isolating) break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var joinUp = function joinUp(state, dispatch) {
|
||||
var sel = state.selection,
|
||||
nodeSel = sel instanceof prosemirrorState.NodeSelection,
|
||||
point;
|
||||
if (nodeSel) {
|
||||
if (sel.node.isTextblock || !prosemirrorTransform.canJoin(state.doc, sel.from)) return false;
|
||||
point = sel.from;
|
||||
} else {
|
||||
point = prosemirrorTransform.joinPoint(state.doc, sel.from, -1);
|
||||
if (point == null) return false;
|
||||
}
|
||||
if (dispatch) {
|
||||
var tr = state.tr.join(point);
|
||||
if (nodeSel) tr.setSelection(prosemirrorState.NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore.nodeSize));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
var joinDown = function joinDown(state, dispatch) {
|
||||
var sel = state.selection,
|
||||
point;
|
||||
if (sel instanceof prosemirrorState.NodeSelection) {
|
||||
if (sel.node.isTextblock || !prosemirrorTransform.canJoin(state.doc, sel.to)) return false;
|
||||
point = sel.to;
|
||||
} else {
|
||||
point = prosemirrorTransform.joinPoint(state.doc, sel.to, 1);
|
||||
if (point == null) return false;
|
||||
}
|
||||
if (dispatch) dispatch(state.tr.join(point).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
var lift = function lift(state, dispatch) {
|
||||
var _state$selection3 = state.selection,
|
||||
$from = _state$selection3.$from,
|
||||
$to = _state$selection3.$to;
|
||||
var range = $from.blockRange($to),
|
||||
target = range && prosemirrorTransform.liftTarget(range);
|
||||
if (target == null) return false;
|
||||
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
var newlineInCode = function newlineInCode(state, dispatch) {
|
||||
var _state$selection4 = state.selection,
|
||||
$head = _state$selection4.$head,
|
||||
$anchor = _state$selection4.$anchor;
|
||||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false;
|
||||
if (dispatch) dispatch(state.tr.insertText("\n").scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function defaultBlockAt(match) {
|
||||
for (var i = 0; i < match.edgeCount; i++) {
|
||||
var _match$edge = match.edge(i),
|
||||
type = _match$edge.type;
|
||||
if (type.isTextblock && !type.hasRequiredAttrs()) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var exitCode = function exitCode(state, dispatch) {
|
||||
var _state$selection5 = state.selection,
|
||||
$head = _state$selection5.$head,
|
||||
$anchor = _state$selection5.$anchor;
|
||||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false;
|
||||
var above = $head.node(-1),
|
||||
after = $head.indexAfter(-1),
|
||||
type = defaultBlockAt(above.contentMatchAt(after));
|
||||
if (!type || !above.canReplaceWith(after, after, type)) return false;
|
||||
if (dispatch) {
|
||||
var pos = $head.after(),
|
||||
tr = state.tr.replaceWith(pos, pos, type.createAndFill());
|
||||
tr.setSelection(prosemirrorState.Selection.near(tr.doc.resolve(pos), 1));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
var createParagraphNear = function createParagraphNear(state, dispatch) {
|
||||
var sel = state.selection,
|
||||
$from = sel.$from,
|
||||
$to = sel.$to;
|
||||
if (sel instanceof prosemirrorState.AllSelection || $from.parent.inlineContent || $to.parent.inlineContent) return false;
|
||||
var type = defaultBlockAt($to.parent.contentMatchAt($to.indexAfter()));
|
||||
if (!type || !type.isTextblock) return false;
|
||||
if (dispatch) {
|
||||
var side = (!$from.parentOffset && $to.index() < $to.parent.childCount ? $from : $to).pos;
|
||||
var tr = state.tr.insert(side, type.createAndFill());
|
||||
tr.setSelection(prosemirrorState.TextSelection.create(tr.doc, side + 1));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
var liftEmptyBlock = function liftEmptyBlock(state, dispatch) {
|
||||
var $cursor = state.selection.$cursor;
|
||||
if (!$cursor || $cursor.parent.content.size) return false;
|
||||
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
|
||||
var before = $cursor.before();
|
||||
if (prosemirrorTransform.canSplit(state.doc, before)) {
|
||||
if (dispatch) dispatch(state.tr.split(before).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var range = $cursor.blockRange(),
|
||||
target = range && prosemirrorTransform.liftTarget(range);
|
||||
if (target == null) return false;
|
||||
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function splitBlockAs(splitNode) {
|
||||
return function (state, dispatch) {
|
||||
var _state$selection6 = state.selection,
|
||||
$from = _state$selection6.$from,
|
||||
$to = _state$selection6.$to;
|
||||
if (state.selection instanceof prosemirrorState.NodeSelection && state.selection.node.isBlock) {
|
||||
if (!$from.parentOffset || !prosemirrorTransform.canSplit(state.doc, $from.pos)) return false;
|
||||
if (dispatch) dispatch(state.tr.split($from.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
if (!$from.depth) return false;
|
||||
var types = [];
|
||||
var splitDepth,
|
||||
deflt,
|
||||
atEnd = false,
|
||||
atStart = false;
|
||||
for (var d = $from.depth;; d--) {
|
||||
var node = $from.node(d);
|
||||
if (node.isBlock) {
|
||||
atEnd = $from.end(d) == $from.pos + ($from.depth - d);
|
||||
atStart = $from.start(d) == $from.pos - ($from.depth - d);
|
||||
deflt = defaultBlockAt($from.node(d - 1).contentMatchAt($from.indexAfter(d - 1)));
|
||||
var splitType = splitNode && splitNode($to.parent, atEnd, $from);
|
||||
types.unshift(splitType || (atEnd && deflt ? {
|
||||
type: deflt
|
||||
} : null));
|
||||
splitDepth = d;
|
||||
break;
|
||||
} else {
|
||||
if (d == 1) return false;
|
||||
types.unshift(null);
|
||||
}
|
||||
}
|
||||
var tr = state.tr;
|
||||
if (state.selection instanceof prosemirrorState.TextSelection || state.selection instanceof prosemirrorState.AllSelection) tr.deleteSelection();
|
||||
var splitPos = tr.mapping.map($from.pos);
|
||||
var can = prosemirrorTransform.canSplit(tr.doc, splitPos, types.length, types);
|
||||
if (!can) {
|
||||
types[0] = deflt ? {
|
||||
type: deflt
|
||||
} : null;
|
||||
can = prosemirrorTransform.canSplit(tr.doc, splitPos, types.length, types);
|
||||
}
|
||||
if (!can) return false;
|
||||
tr.split(splitPos, types.length, types);
|
||||
if (!atEnd && atStart && $from.node(splitDepth).type != deflt) {
|
||||
var first = tr.mapping.map($from.before(splitDepth)),
|
||||
$first = tr.doc.resolve(first);
|
||||
if (deflt && $from.node(splitDepth - 1).canReplaceWith($first.index(), $first.index() + 1, deflt)) tr.setNodeMarkup(tr.mapping.map($from.before(splitDepth)), deflt);
|
||||
}
|
||||
if (dispatch) dispatch(tr.scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
}
|
||||
var splitBlock = splitBlockAs();
|
||||
var splitBlockKeepMarks = function splitBlockKeepMarks(state, dispatch) {
|
||||
return splitBlock(state, dispatch && function (tr) {
|
||||
var marks = state.storedMarks || state.selection.$to.parentOffset && state.selection.$from.marks();
|
||||
if (marks) tr.ensureMarks(marks);
|
||||
dispatch(tr);
|
||||
});
|
||||
};
|
||||
var selectParentNode = function selectParentNode(state, dispatch) {
|
||||
var _state$selection7 = state.selection,
|
||||
$from = _state$selection7.$from,
|
||||
to = _state$selection7.to,
|
||||
pos;
|
||||
var same = $from.sharedDepth(to);
|
||||
if (same == 0) return false;
|
||||
pos = $from.before(same);
|
||||
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, pos)));
|
||||
return true;
|
||||
};
|
||||
var selectAll = function selectAll(state, dispatch) {
|
||||
if (dispatch) dispatch(state.tr.setSelection(new prosemirrorState.AllSelection(state.doc)));
|
||||
return true;
|
||||
};
|
||||
function joinMaybeClear(state, $pos, dispatch) {
|
||||
var before = $pos.nodeBefore,
|
||||
after = $pos.nodeAfter,
|
||||
index = $pos.index();
|
||||
if (!before || !after || !before.type.compatibleContent(after.type)) return false;
|
||||
if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
|
||||
if (dispatch) dispatch(state.tr["delete"]($pos.pos - before.nodeSize, $pos.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || prosemirrorTransform.canJoin(state.doc, $pos.pos))) return false;
|
||||
if (dispatch) dispatch(state.tr.join($pos.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
function deleteBarrier(state, $cut, dispatch, dir) {
|
||||
var before = $cut.nodeBefore,
|
||||
after = $cut.nodeAfter,
|
||||
conn,
|
||||
match;
|
||||
var isolated = before.type.spec.isolating || after.type.spec.isolating;
|
||||
if (!isolated && joinMaybeClear(state, $cut, dispatch)) return true;
|
||||
var canDelAfter = !isolated && $cut.parent.canReplace($cut.index(), $cut.index() + 1);
|
||||
if (canDelAfter && (conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) && match.matchType(conn[0] || after.type).validEnd) {
|
||||
if (dispatch) {
|
||||
var end = $cut.pos + after.nodeSize,
|
||||
wrap = prosemirrorModel.Fragment.empty;
|
||||
for (var i = conn.length - 1; i >= 0; i--) wrap = prosemirrorModel.Fragment.from(conn[i].create(null, wrap));
|
||||
wrap = prosemirrorModel.Fragment.from(before.copy(wrap));
|
||||
var tr = state.tr.step(new prosemirrorTransform.ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new prosemirrorModel.Slice(wrap, 1, 0), conn.length, true));
|
||||
var $joinAt = tr.doc.resolve(end + 2 * conn.length);
|
||||
if ($joinAt.nodeAfter && $joinAt.nodeAfter.type == before.type && prosemirrorTransform.canJoin(tr.doc, $joinAt.pos)) tr.join($joinAt.pos);
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
var selAfter = after.type.spec.isolating || dir > 0 && isolated ? null : prosemirrorState.Selection.findFrom($cut, 1);
|
||||
var range = selAfter && selAfter.$from.blockRange(selAfter.$to),
|
||||
target = range && prosemirrorTransform.liftTarget(range);
|
||||
if (target != null && target >= $cut.depth) {
|
||||
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
if (canDelAfter && textblockAt(after, "start", true) && textblockAt(before, "end")) {
|
||||
var at = before,
|
||||
_wrap = [];
|
||||
for (;;) {
|
||||
_wrap.push(at);
|
||||
if (at.isTextblock) break;
|
||||
at = at.lastChild;
|
||||
}
|
||||
var afterText = after,
|
||||
afterDepth = 1;
|
||||
for (; !afterText.isTextblock; afterText = afterText.firstChild) afterDepth++;
|
||||
if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
|
||||
if (dispatch) {
|
||||
var _end = prosemirrorModel.Fragment.empty;
|
||||
for (var _i = _wrap.length - 1; _i >= 0; _i--) _end = prosemirrorModel.Fragment.from(_wrap[_i].copy(_end));
|
||||
var _tr = state.tr.step(new prosemirrorTransform.ReplaceAroundStep($cut.pos - _wrap.length, $cut.pos + after.nodeSize, $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth, new prosemirrorModel.Slice(_end, _wrap.length, 0), 0, true));
|
||||
dispatch(_tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function selectTextblockSide(side) {
|
||||
return function (state, dispatch) {
|
||||
var sel = state.selection,
|
||||
$pos = side < 0 ? sel.$from : sel.$to;
|
||||
var depth = $pos.depth;
|
||||
while ($pos.node(depth).isInline) {
|
||||
if (!depth) return false;
|
||||
depth--;
|
||||
}
|
||||
if (!$pos.node(depth).isTextblock) return false;
|
||||
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.TextSelection.create(state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
var selectTextblockStart = selectTextblockSide(-1);
|
||||
var selectTextblockEnd = selectTextblockSide(1);
|
||||
function wrapIn(nodeType) {
|
||||
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
||||
return function (state, dispatch) {
|
||||
var _state$selection8 = state.selection,
|
||||
$from = _state$selection8.$from,
|
||||
$to = _state$selection8.$to;
|
||||
var range = $from.blockRange($to),
|
||||
wrapping = range && prosemirrorTransform.findWrapping(range, nodeType, attrs);
|
||||
if (!wrapping) return false;
|
||||
if (dispatch) dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
}
|
||||
function setBlockType(nodeType) {
|
||||
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
||||
return function (state, dispatch) {
|
||||
var applicable = false;
|
||||
for (var i = 0; i < state.selection.ranges.length && !applicable; i++) {
|
||||
var _state$selection$rang = state.selection.ranges[i],
|
||||
from = _state$selection$rang.$from.pos,
|
||||
to = _state$selection$rang.$to.pos;
|
||||
state.doc.nodesBetween(from, to, function (node, pos) {
|
||||
if (applicable) return false;
|
||||
if (!node.isTextblock || node.hasMarkup(nodeType, attrs)) return;
|
||||
if (node.type == nodeType) {
|
||||
applicable = true;
|
||||
} else {
|
||||
var $pos = state.doc.resolve(pos),
|
||||
index = $pos.index();
|
||||
applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!applicable) return false;
|
||||
if (dispatch) {
|
||||
var tr = state.tr;
|
||||
for (var _i2 = 0; _i2 < state.selection.ranges.length; _i2++) {
|
||||
var _state$selection$rang2 = state.selection.ranges[_i2],
|
||||
_from = _state$selection$rang2.$from.pos,
|
||||
_to = _state$selection$rang2.$to.pos;
|
||||
tr.setBlockType(_from, _to, nodeType, attrs);
|
||||
}
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
function markApplies(doc, ranges, type, enterAtoms) {
|
||||
var _loop = function _loop() {
|
||||
var _ranges$i = ranges[i],
|
||||
$from = _ranges$i.$from,
|
||||
$to = _ranges$i.$to;
|
||||
var can = $from.depth == 0 ? doc.inlineContent && doc.type.allowsMarkType(type) : false;
|
||||
doc.nodesBetween($from.pos, $to.pos, function (node, pos) {
|
||||
if (can || !enterAtoms && node.isAtom && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos) return false;
|
||||
can = node.inlineContent && node.type.allowsMarkType(type);
|
||||
});
|
||||
if (can) return {
|
||||
v: true
|
||||
};
|
||||
},
|
||||
_ret;
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
_ret = _loop();
|
||||
if (_ret) return _ret.v;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function removeInlineAtoms(ranges) {
|
||||
var result = [];
|
||||
var _loop2 = function _loop2() {
|
||||
var _ranges$i2 = ranges[i],
|
||||
$from = _ranges$i2.$from,
|
||||
$to = _ranges$i2.$to;
|
||||
$from.doc.nodesBetween($from.pos, $to.pos, function (node, pos) {
|
||||
if (node.isAtom && node.content.size && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos) {
|
||||
if (pos + 1 > $from.pos) result.push(new prosemirrorState.SelectionRange($from, $from.doc.resolve(pos + 1)));
|
||||
$from = $from.doc.resolve(pos + 1 + node.content.size);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if ($from.pos < $to.pos) result.push(new prosemirrorState.SelectionRange($from, $to));
|
||||
};
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
_loop2();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function toggleMark(markType) {
|
||||
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
||||
var options = arguments.length > 2 ? arguments[2] : undefined;
|
||||
var removeWhenPresent = (options && options.removeWhenPresent) !== false;
|
||||
var enterAtoms = (options && options.enterInlineAtoms) !== false;
|
||||
var dropSpace = !(options && options.includeWhitespace);
|
||||
return function (state, dispatch) {
|
||||
var _state$selection9 = state.selection,
|
||||
empty = _state$selection9.empty,
|
||||
$cursor = _state$selection9.$cursor,
|
||||
ranges = _state$selection9.ranges;
|
||||
if (empty && !$cursor || !markApplies(state.doc, ranges, markType, enterAtoms)) return false;
|
||||
if (dispatch) {
|
||||
if ($cursor) {
|
||||
if (markType.isInSet(state.storedMarks || $cursor.marks())) dispatch(state.tr.removeStoredMark(markType));else dispatch(state.tr.addStoredMark(markType.create(attrs)));
|
||||
} else {
|
||||
var add,
|
||||
tr = state.tr;
|
||||
if (!enterAtoms) ranges = removeInlineAtoms(ranges);
|
||||
if (removeWhenPresent) {
|
||||
add = !ranges.some(function (r) {
|
||||
return state.doc.rangeHasMark(r.$from.pos, r.$to.pos, markType);
|
||||
});
|
||||
} else {
|
||||
add = !ranges.every(function (r) {
|
||||
var missing = false;
|
||||
tr.doc.nodesBetween(r.$from.pos, r.$to.pos, function (node, pos, parent) {
|
||||
if (missing) return false;
|
||||
missing = !markType.isInSet(node.marks) && !!parent && parent.type.allowsMarkType(markType) && !(node.isText && /^\s*$/.test(node.textBetween(Math.max(0, r.$from.pos - pos), Math.min(node.nodeSize, r.$to.pos - pos))));
|
||||
});
|
||||
return !missing;
|
||||
});
|
||||
}
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var _ranges$i3 = ranges[i],
|
||||
$from = _ranges$i3.$from,
|
||||
$to = _ranges$i3.$to;
|
||||
if (!add) {
|
||||
tr.removeMark($from.pos, $to.pos, markType);
|
||||
} else {
|
||||
var from = $from.pos,
|
||||
to = $to.pos,
|
||||
start = $from.nodeAfter,
|
||||
end = $to.nodeBefore;
|
||||
var spaceStart = dropSpace && start && start.isText ? /^\s*/.exec(start.text)[0].length : 0;
|
||||
var spaceEnd = dropSpace && end && end.isText ? /\s*$/.exec(end.text)[0].length : 0;
|
||||
if (from + spaceStart < to) {
|
||||
from += spaceStart;
|
||||
to -= spaceEnd;
|
||||
}
|
||||
tr.addMark(from, to, markType.create(attrs));
|
||||
}
|
||||
}
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
function wrapDispatchForJoin(dispatch, isJoinable) {
|
||||
return function (tr) {
|
||||
if (!tr.isGeneric) return dispatch(tr);
|
||||
var ranges = [];
|
||||
for (var i = 0; i < tr.mapping.maps.length; i++) {
|
||||
var map = tr.mapping.maps[i];
|
||||
for (var j = 0; j < ranges.length; j++) ranges[j] = map.map(ranges[j]);
|
||||
map.forEach(function (_s, _e, from, to) {
|
||||
return ranges.push(from, to);
|
||||
});
|
||||
}
|
||||
var joinable = [];
|
||||
for (var _i3 = 0; _i3 < ranges.length; _i3 += 2) {
|
||||
var from = ranges[_i3],
|
||||
to = ranges[_i3 + 1];
|
||||
var $from = tr.doc.resolve(from),
|
||||
depth = $from.sharedDepth(to),
|
||||
parent = $from.node(depth);
|
||||
for (var index = $from.indexAfter(depth), pos = $from.after(depth + 1); pos <= to; ++index) {
|
||||
var after = parent.maybeChild(index);
|
||||
if (!after) break;
|
||||
if (index && joinable.indexOf(pos) == -1) {
|
||||
var before = parent.child(index - 1);
|
||||
if (before.type == after.type && isJoinable(before, after)) joinable.push(pos);
|
||||
}
|
||||
pos += after.nodeSize;
|
||||
}
|
||||
}
|
||||
joinable.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
for (var _i4 = joinable.length - 1; _i4 >= 0; _i4--) {
|
||||
if (prosemirrorTransform.canJoin(tr.doc, joinable[_i4])) tr.join(joinable[_i4]);
|
||||
}
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
function autoJoin(command, isJoinable) {
|
||||
var canJoin = Array.isArray(isJoinable) ? function (node) {
|
||||
return isJoinable.indexOf(node.type.name) > -1;
|
||||
} : isJoinable;
|
||||
return function (state, dispatch, view) {
|
||||
return command(state, dispatch && wrapDispatchForJoin(dispatch, canJoin), view);
|
||||
};
|
||||
}
|
||||
function chainCommands() {
|
||||
for (var _len = arguments.length, commands = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||
commands[_key] = arguments[_key];
|
||||
}
|
||||
return function (state, dispatch, view) {
|
||||
for (var i = 0; i < commands.length; i++) if (commands[i](state, dispatch, view)) return true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
var backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
|
||||
var del = chainCommands(deleteSelection, joinForward, selectNodeForward);
|
||||
var pcBaseKeymap = {
|
||||
"Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock),
|
||||
"Mod-Enter": exitCode,
|
||||
"Backspace": backspace,
|
||||
"Mod-Backspace": backspace,
|
||||
"Shift-Backspace": backspace,
|
||||
"Delete": del,
|
||||
"Mod-Delete": del,
|
||||
"Mod-a": selectAll
|
||||
};
|
||||
var macBaseKeymap = {
|
||||
"Ctrl-h": pcBaseKeymap["Backspace"],
|
||||
"Alt-Backspace": pcBaseKeymap["Mod-Backspace"],
|
||||
"Ctrl-d": pcBaseKeymap["Delete"],
|
||||
"Ctrl-Alt-Backspace": pcBaseKeymap["Mod-Delete"],
|
||||
"Alt-Delete": pcBaseKeymap["Mod-Delete"],
|
||||
"Alt-d": pcBaseKeymap["Mod-Delete"],
|
||||
"Ctrl-a": selectTextblockStart,
|
||||
"Ctrl-e": selectTextblockEnd
|
||||
};
|
||||
for (var key in pcBaseKeymap) macBaseKeymap[key] = pcBaseKeymap[key];
|
||||
var mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : typeof os != "undefined" && os.platform ? os.platform() == "darwin" : false;
|
||||
var baseKeymap = mac ? macBaseKeymap : pcBaseKeymap;
|
||||
exports.autoJoin = autoJoin;
|
||||
exports.baseKeymap = baseKeymap;
|
||||
exports.chainCommands = chainCommands;
|
||||
exports.createParagraphNear = createParagraphNear;
|
||||
exports.deleteSelection = deleteSelection;
|
||||
exports.exitCode = exitCode;
|
||||
exports.joinBackward = joinBackward;
|
||||
exports.joinDown = joinDown;
|
||||
exports.joinForward = joinForward;
|
||||
exports.joinTextblockBackward = joinTextblockBackward;
|
||||
exports.joinTextblockForward = joinTextblockForward;
|
||||
exports.joinUp = joinUp;
|
||||
exports.lift = lift;
|
||||
exports.liftEmptyBlock = liftEmptyBlock;
|
||||
exports.macBaseKeymap = macBaseKeymap;
|
||||
exports.newlineInCode = newlineInCode;
|
||||
exports.pcBaseKeymap = pcBaseKeymap;
|
||||
exports.selectAll = selectAll;
|
||||
exports.selectNodeBackward = selectNodeBackward;
|
||||
exports.selectNodeForward = selectNodeForward;
|
||||
exports.selectParentNode = selectParentNode;
|
||||
exports.selectTextblockEnd = selectTextblockEnd;
|
||||
exports.selectTextblockStart = selectTextblockStart;
|
||||
exports.setBlockType = setBlockType;
|
||||
exports.splitBlock = splitBlock;
|
||||
exports.splitBlockAs = splitBlockAs;
|
||||
exports.splitBlockKeepMarks = splitBlockKeepMarks;
|
||||
exports.toggleMark = toggleMark;
|
||||
exports.wrapIn = wrapIn;
|
||||
215
node_modules/prosemirror-commands/dist/index.d.cts
generated
vendored
Normal file
215
node_modules/prosemirror-commands/dist/index.d.cts
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
import { Node, ResolvedPos, NodeType, Attrs, MarkType } from 'prosemirror-model';
|
||||
import { Command } from 'prosemirror-state';
|
||||
|
||||
/**
|
||||
Delete the selection, if there is one.
|
||||
*/
|
||||
declare const deleteSelection: Command;
|
||||
/**
|
||||
If the selection is empty and at the start of a textblock, try to
|
||||
reduce the distance between that block and the one before it—if
|
||||
there's a block directly before it that can be joined, join them.
|
||||
If not, try to move the selected block closer to the next one in
|
||||
the document structure by lifting it out of its parent or moving it
|
||||
into a parent of the previous block. Will use the view for accurate
|
||||
(bidi-aware) start-of-textblock detection if given.
|
||||
*/
|
||||
declare const joinBackward: Command;
|
||||
/**
|
||||
A more limited form of [`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward)
|
||||
that only tries to join the current textblock to the one before
|
||||
it, if the cursor is at the start of a textblock.
|
||||
*/
|
||||
declare const joinTextblockBackward: Command;
|
||||
/**
|
||||
A more limited form of [`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward)
|
||||
that only tries to join the current textblock to the one after
|
||||
it, if the cursor is at the end of a textblock.
|
||||
*/
|
||||
declare const joinTextblockForward: Command;
|
||||
/**
|
||||
When the selection is empty and at the start of a textblock, select
|
||||
the node before that textblock, if possible. This is intended to be
|
||||
bound to keys like backspace, after
|
||||
[`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward) or other deleting
|
||||
commands, as a fall-back behavior when the schema doesn't allow
|
||||
deletion at the selected point.
|
||||
*/
|
||||
declare const selectNodeBackward: Command;
|
||||
/**
|
||||
If the selection is empty and the cursor is at the end of a
|
||||
textblock, try to reduce or remove the boundary between that block
|
||||
and the one after it, either by joining them or by moving the other
|
||||
block closer to this one in the tree structure. Will use the view
|
||||
for accurate start-of-textblock detection if given.
|
||||
*/
|
||||
declare const joinForward: Command;
|
||||
/**
|
||||
When the selection is empty and at the end of a textblock, select
|
||||
the node coming after that textblock, if possible. This is intended
|
||||
to be bound to keys like delete, after
|
||||
[`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward) and similar deleting
|
||||
commands, to provide a fall-back behavior when the schema doesn't
|
||||
allow deletion at the selected point.
|
||||
*/
|
||||
declare const selectNodeForward: Command;
|
||||
/**
|
||||
Join the selected block or, if there is a text selection, the
|
||||
closest ancestor block of the selection that can be joined, with
|
||||
the sibling above it.
|
||||
*/
|
||||
declare const joinUp: Command;
|
||||
/**
|
||||
Join the selected block, or the closest ancestor of the selection
|
||||
that can be joined, with the sibling after it.
|
||||
*/
|
||||
declare const joinDown: Command;
|
||||
/**
|
||||
Lift the selected block, or the closest ancestor block of the
|
||||
selection that can be lifted, out of its parent node.
|
||||
*/
|
||||
declare const lift: Command;
|
||||
/**
|
||||
If the selection is in a node whose type has a truthy
|
||||
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, replace the
|
||||
selection with a newline character.
|
||||
*/
|
||||
declare const newlineInCode: Command;
|
||||
/**
|
||||
When the selection is in a node with a truthy
|
||||
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, create a
|
||||
default block after the code block, and move the cursor there.
|
||||
*/
|
||||
declare const exitCode: Command;
|
||||
/**
|
||||
If a block node is selected, create an empty paragraph before (if
|
||||
it is its parent's first child) or after it.
|
||||
*/
|
||||
declare const createParagraphNear: Command;
|
||||
/**
|
||||
If the cursor is in an empty textblock that can be lifted, lift the
|
||||
block.
|
||||
*/
|
||||
declare const liftEmptyBlock: Command;
|
||||
/**
|
||||
Create a variant of [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) that uses
|
||||
a custom function to determine the type of the newly split off block.
|
||||
*/
|
||||
declare function splitBlockAs(splitNode?: (node: Node, atEnd: boolean, $from: ResolvedPos) => {
|
||||
type: NodeType;
|
||||
attrs?: Attrs;
|
||||
} | null): Command;
|
||||
/**
|
||||
Split the parent block of the selection. If the selection is a text
|
||||
selection, also delete its content.
|
||||
*/
|
||||
declare const splitBlock: Command;
|
||||
/**
|
||||
Acts like [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock), but without
|
||||
resetting the set of active marks at the cursor.
|
||||
*/
|
||||
declare const splitBlockKeepMarks: Command;
|
||||
/**
|
||||
Move the selection to the node wrapping the current selection, if
|
||||
any. (Will not select the document node.)
|
||||
*/
|
||||
declare const selectParentNode: Command;
|
||||
/**
|
||||
Select the whole document.
|
||||
*/
|
||||
declare const selectAll: Command;
|
||||
/**
|
||||
Moves the cursor to the start of current text block.
|
||||
*/
|
||||
declare const selectTextblockStart: Command;
|
||||
/**
|
||||
Moves the cursor to the end of current text block.
|
||||
*/
|
||||
declare const selectTextblockEnd: Command;
|
||||
/**
|
||||
Wrap the selection in a node of the given type with the given
|
||||
attributes.
|
||||
*/
|
||||
declare function wrapIn(nodeType: NodeType, attrs?: Attrs | null): Command;
|
||||
/**
|
||||
Returns a command that tries to set the selected textblocks to the
|
||||
given node type with the given attributes.
|
||||
*/
|
||||
declare function setBlockType(nodeType: NodeType, attrs?: Attrs | null): Command;
|
||||
/**
|
||||
Create a command function that toggles the given mark with the
|
||||
given attributes. Will return `false` when the current selection
|
||||
doesn't support that mark. This will remove the mark if any marks
|
||||
of that type exist in the selection, or add it otherwise. If the
|
||||
selection is empty, this applies to the [stored
|
||||
marks](https://prosemirror.net/docs/ref/#state.EditorState.storedMarks) instead of a range of the
|
||||
document.
|
||||
*/
|
||||
declare function toggleMark(markType: MarkType, attrs?: Attrs | null, options?: {
|
||||
/**
|
||||
Controls whether, when part of the selected range has the mark
|
||||
already and part doesn't, the mark is removed (`true`, the
|
||||
default) or added (`false`).
|
||||
*/
|
||||
removeWhenPresent?: boolean;
|
||||
/**
|
||||
When set to false, this will prevent the command from acting on
|
||||
the content of inline nodes marked as
|
||||
[atoms](https://prosemirror.net/docs/ref/#model.NodeSpec.atom) that are completely covered by a
|
||||
selection range.
|
||||
*/
|
||||
enterInlineAtoms?: boolean;
|
||||
/**
|
||||
By default, this command doesn't apply to leading and trailing
|
||||
whitespace in the selection. Set this to `true` to change that.
|
||||
*/
|
||||
includeWhitespace?: boolean;
|
||||
}): Command;
|
||||
/**
|
||||
Wrap a command so that, when it produces a transform that causes
|
||||
two joinable nodes to end up next to each other, those are joined.
|
||||
Nodes are considered joinable when they are of the same type and
|
||||
when the `isJoinable` predicate returns true for them or, if an
|
||||
array of strings was passed, if their node type name is in that
|
||||
array.
|
||||
*/
|
||||
declare function autoJoin(command: Command, isJoinable: ((before: Node, after: Node) => boolean) | readonly string[]): Command;
|
||||
/**
|
||||
Combine a number of command functions into a single function (which
|
||||
calls them one by one until one returns true).
|
||||
*/
|
||||
declare function chainCommands(...commands: readonly Command[]): Command;
|
||||
/**
|
||||
A basic keymap containing bindings not specific to any schema.
|
||||
Binds the following keys (when multiple commands are listed, they
|
||||
are chained with [`chainCommands`](https://prosemirror.net/docs/ref/#commands.chainCommands)):
|
||||
|
||||
* **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock`
|
||||
* **Mod-Enter** to `exitCode`
|
||||
* **Backspace** and **Mod-Backspace** to `deleteSelection`, `joinBackward`, `selectNodeBackward`
|
||||
* **Delete** and **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
* **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
* **Mod-a** to `selectAll`
|
||||
*/
|
||||
declare const pcBaseKeymap: {
|
||||
[key: string]: Command;
|
||||
};
|
||||
/**
|
||||
A copy of `pcBaseKeymap` that also binds **Ctrl-h** like Backspace,
|
||||
**Ctrl-d** like Delete, **Alt-Backspace** like Ctrl-Backspace, and
|
||||
**Ctrl-Alt-Backspace**, **Alt-Delete**, and **Alt-d** like
|
||||
Ctrl-Delete.
|
||||
*/
|
||||
declare const macBaseKeymap: {
|
||||
[key: string]: Command;
|
||||
};
|
||||
/**
|
||||
Depending on the detected platform, this will hold
|
||||
[`pcBasekeymap`](https://prosemirror.net/docs/ref/#commands.pcBaseKeymap) or
|
||||
[`macBaseKeymap`](https://prosemirror.net/docs/ref/#commands.macBaseKeymap).
|
||||
*/
|
||||
declare const baseKeymap: {
|
||||
[key: string]: Command;
|
||||
};
|
||||
|
||||
export { autoJoin, baseKeymap, chainCommands, createParagraphNear, deleteSelection, exitCode, joinBackward, joinDown, joinForward, joinTextblockBackward, joinTextblockForward, joinUp, lift, liftEmptyBlock, macBaseKeymap, newlineInCode, pcBaseKeymap, selectAll, selectNodeBackward, selectNodeForward, selectParentNode, selectTextblockEnd, selectTextblockStart, setBlockType, splitBlock, splitBlockAs, splitBlockKeepMarks, toggleMark, wrapIn };
|
||||
215
node_modules/prosemirror-commands/dist/index.d.ts
generated
vendored
Normal file
215
node_modules/prosemirror-commands/dist/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
import { Node, ResolvedPos, NodeType, Attrs, MarkType } from 'prosemirror-model';
|
||||
import { Command } from 'prosemirror-state';
|
||||
|
||||
/**
|
||||
Delete the selection, if there is one.
|
||||
*/
|
||||
declare const deleteSelection: Command;
|
||||
/**
|
||||
If the selection is empty and at the start of a textblock, try to
|
||||
reduce the distance between that block and the one before it—if
|
||||
there's a block directly before it that can be joined, join them.
|
||||
If not, try to move the selected block closer to the next one in
|
||||
the document structure by lifting it out of its parent or moving it
|
||||
into a parent of the previous block. Will use the view for accurate
|
||||
(bidi-aware) start-of-textblock detection if given.
|
||||
*/
|
||||
declare const joinBackward: Command;
|
||||
/**
|
||||
A more limited form of [`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward)
|
||||
that only tries to join the current textblock to the one before
|
||||
it, if the cursor is at the start of a textblock.
|
||||
*/
|
||||
declare const joinTextblockBackward: Command;
|
||||
/**
|
||||
A more limited form of [`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward)
|
||||
that only tries to join the current textblock to the one after
|
||||
it, if the cursor is at the end of a textblock.
|
||||
*/
|
||||
declare const joinTextblockForward: Command;
|
||||
/**
|
||||
When the selection is empty and at the start of a textblock, select
|
||||
the node before that textblock, if possible. This is intended to be
|
||||
bound to keys like backspace, after
|
||||
[`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward) or other deleting
|
||||
commands, as a fall-back behavior when the schema doesn't allow
|
||||
deletion at the selected point.
|
||||
*/
|
||||
declare const selectNodeBackward: Command;
|
||||
/**
|
||||
If the selection is empty and the cursor is at the end of a
|
||||
textblock, try to reduce or remove the boundary between that block
|
||||
and the one after it, either by joining them or by moving the other
|
||||
block closer to this one in the tree structure. Will use the view
|
||||
for accurate start-of-textblock detection if given.
|
||||
*/
|
||||
declare const joinForward: Command;
|
||||
/**
|
||||
When the selection is empty and at the end of a textblock, select
|
||||
the node coming after that textblock, if possible. This is intended
|
||||
to be bound to keys like delete, after
|
||||
[`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward) and similar deleting
|
||||
commands, to provide a fall-back behavior when the schema doesn't
|
||||
allow deletion at the selected point.
|
||||
*/
|
||||
declare const selectNodeForward: Command;
|
||||
/**
|
||||
Join the selected block or, if there is a text selection, the
|
||||
closest ancestor block of the selection that can be joined, with
|
||||
the sibling above it.
|
||||
*/
|
||||
declare const joinUp: Command;
|
||||
/**
|
||||
Join the selected block, or the closest ancestor of the selection
|
||||
that can be joined, with the sibling after it.
|
||||
*/
|
||||
declare const joinDown: Command;
|
||||
/**
|
||||
Lift the selected block, or the closest ancestor block of the
|
||||
selection that can be lifted, out of its parent node.
|
||||
*/
|
||||
declare const lift: Command;
|
||||
/**
|
||||
If the selection is in a node whose type has a truthy
|
||||
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, replace the
|
||||
selection with a newline character.
|
||||
*/
|
||||
declare const newlineInCode: Command;
|
||||
/**
|
||||
When the selection is in a node with a truthy
|
||||
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, create a
|
||||
default block after the code block, and move the cursor there.
|
||||
*/
|
||||
declare const exitCode: Command;
|
||||
/**
|
||||
If a block node is selected, create an empty paragraph before (if
|
||||
it is its parent's first child) or after it.
|
||||
*/
|
||||
declare const createParagraphNear: Command;
|
||||
/**
|
||||
If the cursor is in an empty textblock that can be lifted, lift the
|
||||
block.
|
||||
*/
|
||||
declare const liftEmptyBlock: Command;
|
||||
/**
|
||||
Create a variant of [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) that uses
|
||||
a custom function to determine the type of the newly split off block.
|
||||
*/
|
||||
declare function splitBlockAs(splitNode?: (node: Node, atEnd: boolean, $from: ResolvedPos) => {
|
||||
type: NodeType;
|
||||
attrs?: Attrs;
|
||||
} | null): Command;
|
||||
/**
|
||||
Split the parent block of the selection. If the selection is a text
|
||||
selection, also delete its content.
|
||||
*/
|
||||
declare const splitBlock: Command;
|
||||
/**
|
||||
Acts like [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock), but without
|
||||
resetting the set of active marks at the cursor.
|
||||
*/
|
||||
declare const splitBlockKeepMarks: Command;
|
||||
/**
|
||||
Move the selection to the node wrapping the current selection, if
|
||||
any. (Will not select the document node.)
|
||||
*/
|
||||
declare const selectParentNode: Command;
|
||||
/**
|
||||
Select the whole document.
|
||||
*/
|
||||
declare const selectAll: Command;
|
||||
/**
|
||||
Moves the cursor to the start of current text block.
|
||||
*/
|
||||
declare const selectTextblockStart: Command;
|
||||
/**
|
||||
Moves the cursor to the end of current text block.
|
||||
*/
|
||||
declare const selectTextblockEnd: Command;
|
||||
/**
|
||||
Wrap the selection in a node of the given type with the given
|
||||
attributes.
|
||||
*/
|
||||
declare function wrapIn(nodeType: NodeType, attrs?: Attrs | null): Command;
|
||||
/**
|
||||
Returns a command that tries to set the selected textblocks to the
|
||||
given node type with the given attributes.
|
||||
*/
|
||||
declare function setBlockType(nodeType: NodeType, attrs?: Attrs | null): Command;
|
||||
/**
|
||||
Create a command function that toggles the given mark with the
|
||||
given attributes. Will return `false` when the current selection
|
||||
doesn't support that mark. This will remove the mark if any marks
|
||||
of that type exist in the selection, or add it otherwise. If the
|
||||
selection is empty, this applies to the [stored
|
||||
marks](https://prosemirror.net/docs/ref/#state.EditorState.storedMarks) instead of a range of the
|
||||
document.
|
||||
*/
|
||||
declare function toggleMark(markType: MarkType, attrs?: Attrs | null, options?: {
|
||||
/**
|
||||
Controls whether, when part of the selected range has the mark
|
||||
already and part doesn't, the mark is removed (`true`, the
|
||||
default) or added (`false`).
|
||||
*/
|
||||
removeWhenPresent?: boolean;
|
||||
/**
|
||||
When set to false, this will prevent the command from acting on
|
||||
the content of inline nodes marked as
|
||||
[atoms](https://prosemirror.net/docs/ref/#model.NodeSpec.atom) that are completely covered by a
|
||||
selection range.
|
||||
*/
|
||||
enterInlineAtoms?: boolean;
|
||||
/**
|
||||
By default, this command doesn't apply to leading and trailing
|
||||
whitespace in the selection. Set this to `true` to change that.
|
||||
*/
|
||||
includeWhitespace?: boolean;
|
||||
}): Command;
|
||||
/**
|
||||
Wrap a command so that, when it produces a transform that causes
|
||||
two joinable nodes to end up next to each other, those are joined.
|
||||
Nodes are considered joinable when they are of the same type and
|
||||
when the `isJoinable` predicate returns true for them or, if an
|
||||
array of strings was passed, if their node type name is in that
|
||||
array.
|
||||
*/
|
||||
declare function autoJoin(command: Command, isJoinable: ((before: Node, after: Node) => boolean) | readonly string[]): Command;
|
||||
/**
|
||||
Combine a number of command functions into a single function (which
|
||||
calls them one by one until one returns true).
|
||||
*/
|
||||
declare function chainCommands(...commands: readonly Command[]): Command;
|
||||
/**
|
||||
A basic keymap containing bindings not specific to any schema.
|
||||
Binds the following keys (when multiple commands are listed, they
|
||||
are chained with [`chainCommands`](https://prosemirror.net/docs/ref/#commands.chainCommands)):
|
||||
|
||||
* **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock`
|
||||
* **Mod-Enter** to `exitCode`
|
||||
* **Backspace** and **Mod-Backspace** to `deleteSelection`, `joinBackward`, `selectNodeBackward`
|
||||
* **Delete** and **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
* **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
* **Mod-a** to `selectAll`
|
||||
*/
|
||||
declare const pcBaseKeymap: {
|
||||
[key: string]: Command;
|
||||
};
|
||||
/**
|
||||
A copy of `pcBaseKeymap` that also binds **Ctrl-h** like Backspace,
|
||||
**Ctrl-d** like Delete, **Alt-Backspace** like Ctrl-Backspace, and
|
||||
**Ctrl-Alt-Backspace**, **Alt-Delete**, and **Alt-d** like
|
||||
Ctrl-Delete.
|
||||
*/
|
||||
declare const macBaseKeymap: {
|
||||
[key: string]: Command;
|
||||
};
|
||||
/**
|
||||
Depending on the detected platform, this will hold
|
||||
[`pcBasekeymap`](https://prosemirror.net/docs/ref/#commands.pcBaseKeymap) or
|
||||
[`macBaseKeymap`](https://prosemirror.net/docs/ref/#commands.macBaseKeymap).
|
||||
*/
|
||||
declare const baseKeymap: {
|
||||
[key: string]: Command;
|
||||
};
|
||||
|
||||
export { autoJoin, baseKeymap, chainCommands, createParagraphNear, deleteSelection, exitCode, joinBackward, joinDown, joinForward, joinTextblockBackward, joinTextblockForward, joinUp, lift, liftEmptyBlock, macBaseKeymap, newlineInCode, pcBaseKeymap, selectAll, selectNodeBackward, selectNodeForward, selectParentNode, selectTextblockEnd, selectTextblockStart, setBlockType, splitBlock, splitBlockAs, splitBlockKeepMarks, toggleMark, wrapIn };
|
||||
850
node_modules/prosemirror-commands/dist/index.js
generated
vendored
Normal file
850
node_modules/prosemirror-commands/dist/index.js
generated
vendored
Normal file
@@ -0,0 +1,850 @@
|
||||
import { liftTarget, replaceStep, ReplaceStep, canJoin, joinPoint, canSplit, ReplaceAroundStep, findWrapping } from 'prosemirror-transform';
|
||||
import { Slice, Fragment } from 'prosemirror-model';
|
||||
import { NodeSelection, Selection, TextSelection, AllSelection, SelectionRange } from 'prosemirror-state';
|
||||
|
||||
/**
|
||||
Delete the selection, if there is one.
|
||||
*/
|
||||
const deleteSelection = (state, dispatch) => {
|
||||
if (state.selection.empty)
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.deleteSelection().scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function atBlockStart(state, view) {
|
||||
let { $cursor } = state.selection;
|
||||
if (!$cursor || (view ? !view.endOfTextblock("backward", state)
|
||||
: $cursor.parentOffset > 0))
|
||||
return null;
|
||||
return $cursor;
|
||||
}
|
||||
/**
|
||||
If the selection is empty and at the start of a textblock, try to
|
||||
reduce the distance between that block and the one before it—if
|
||||
there's a block directly before it that can be joined, join them.
|
||||
If not, try to move the selected block closer to the next one in
|
||||
the document structure by lifting it out of its parent or moving it
|
||||
into a parent of the previous block. Will use the view for accurate
|
||||
(bidi-aware) start-of-textblock detection if given.
|
||||
*/
|
||||
const joinBackward = (state, dispatch, view) => {
|
||||
let $cursor = atBlockStart(state, view);
|
||||
if (!$cursor)
|
||||
return false;
|
||||
let $cut = findCutBefore($cursor);
|
||||
// If there is no node before this, try to lift
|
||||
if (!$cut) {
|
||||
let range = $cursor.blockRange(), target = range && liftTarget(range);
|
||||
if (target == null)
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
let before = $cut.nodeBefore;
|
||||
// Apply the joining algorithm
|
||||
if (deleteBarrier(state, $cut, dispatch, -1))
|
||||
return true;
|
||||
// If the node below has no content and the node above is
|
||||
// selectable, delete the node below and select the one above.
|
||||
if ($cursor.parent.content.size == 0 &&
|
||||
(textblockAt(before, "end") || NodeSelection.isSelectable(before))) {
|
||||
for (let depth = $cursor.depth;; depth--) {
|
||||
let delStep = replaceStep(state.doc, $cursor.before(depth), $cursor.after(depth), Slice.empty);
|
||||
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
|
||||
if (dispatch) {
|
||||
let tr = state.tr.step(delStep);
|
||||
tr.setSelection(textblockAt(before, "end")
|
||||
? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1)
|
||||
: NodeSelection.create(tr.doc, $cut.pos - before.nodeSize));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (depth == 1 || $cursor.node(depth - 1).childCount > 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the node before is an atom, delete it
|
||||
if (before.isAtom && $cut.depth == $cursor.depth - 1) {
|
||||
if (dispatch)
|
||||
dispatch(state.tr.delete($cut.pos - before.nodeSize, $cut.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
A more limited form of [`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward)
|
||||
that only tries to join the current textblock to the one before
|
||||
it, if the cursor is at the start of a textblock.
|
||||
*/
|
||||
const joinTextblockBackward = (state, dispatch, view) => {
|
||||
let $cursor = atBlockStart(state, view);
|
||||
if (!$cursor)
|
||||
return false;
|
||||
let $cut = findCutBefore($cursor);
|
||||
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
|
||||
};
|
||||
/**
|
||||
A more limited form of [`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward)
|
||||
that only tries to join the current textblock to the one after
|
||||
it, if the cursor is at the end of a textblock.
|
||||
*/
|
||||
const joinTextblockForward = (state, dispatch, view) => {
|
||||
let $cursor = atBlockEnd(state, view);
|
||||
if (!$cursor)
|
||||
return false;
|
||||
let $cut = findCutAfter($cursor);
|
||||
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
|
||||
};
|
||||
function joinTextblocksAround(state, $cut, dispatch) {
|
||||
let before = $cut.nodeBefore, beforeText = before, beforePos = $cut.pos - 1;
|
||||
for (; !beforeText.isTextblock; beforePos--) {
|
||||
if (beforeText.type.spec.isolating)
|
||||
return false;
|
||||
let child = beforeText.lastChild;
|
||||
if (!child)
|
||||
return false;
|
||||
beforeText = child;
|
||||
}
|
||||
let after = $cut.nodeAfter, afterText = after, afterPos = $cut.pos + 1;
|
||||
for (; !afterText.isTextblock; afterPos++) {
|
||||
if (afterText.type.spec.isolating)
|
||||
return false;
|
||||
let child = afterText.firstChild;
|
||||
if (!child)
|
||||
return false;
|
||||
afterText = child;
|
||||
}
|
||||
let step = replaceStep(state.doc, beforePos, afterPos, Slice.empty);
|
||||
if (!step || step.from != beforePos ||
|
||||
step instanceof ReplaceStep && step.slice.size >= afterPos - beforePos)
|
||||
return false;
|
||||
if (dispatch) {
|
||||
let tr = state.tr.step(step);
|
||||
tr.setSelection(TextSelection.create(tr.doc, beforePos));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function textblockAt(node, side, only = false) {
|
||||
for (let scan = node; scan; scan = (side == "start" ? scan.firstChild : scan.lastChild)) {
|
||||
if (scan.isTextblock)
|
||||
return true;
|
||||
if (only && scan.childCount != 1)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
When the selection is empty and at the start of a textblock, select
|
||||
the node before that textblock, if possible. This is intended to be
|
||||
bound to keys like backspace, after
|
||||
[`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward) or other deleting
|
||||
commands, as a fall-back behavior when the schema doesn't allow
|
||||
deletion at the selected point.
|
||||
*/
|
||||
const selectNodeBackward = (state, dispatch, view) => {
|
||||
let { $head, empty } = state.selection, $cut = $head;
|
||||
if (!empty)
|
||||
return false;
|
||||
if ($head.parent.isTextblock) {
|
||||
if (view ? !view.endOfTextblock("backward", state) : $head.parentOffset > 0)
|
||||
return false;
|
||||
$cut = findCutBefore($head);
|
||||
}
|
||||
let node = $cut && $cut.nodeBefore;
|
||||
if (!node || !NodeSelection.isSelectable(node))
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos - node.nodeSize)).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function findCutBefore($pos) {
|
||||
if (!$pos.parent.type.spec.isolating)
|
||||
for (let i = $pos.depth - 1; i >= 0; i--) {
|
||||
if ($pos.index(i) > 0)
|
||||
return $pos.doc.resolve($pos.before(i + 1));
|
||||
if ($pos.node(i).type.spec.isolating)
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function atBlockEnd(state, view) {
|
||||
let { $cursor } = state.selection;
|
||||
if (!$cursor || (view ? !view.endOfTextblock("forward", state)
|
||||
: $cursor.parentOffset < $cursor.parent.content.size))
|
||||
return null;
|
||||
return $cursor;
|
||||
}
|
||||
/**
|
||||
If the selection is empty and the cursor is at the end of a
|
||||
textblock, try to reduce or remove the boundary between that block
|
||||
and the one after it, either by joining them or by moving the other
|
||||
block closer to this one in the tree structure. Will use the view
|
||||
for accurate start-of-textblock detection if given.
|
||||
*/
|
||||
const joinForward = (state, dispatch, view) => {
|
||||
let $cursor = atBlockEnd(state, view);
|
||||
if (!$cursor)
|
||||
return false;
|
||||
let $cut = findCutAfter($cursor);
|
||||
// If there is no node after this, there's nothing to do
|
||||
if (!$cut)
|
||||
return false;
|
||||
let after = $cut.nodeAfter;
|
||||
// Try the joining algorithm
|
||||
if (deleteBarrier(state, $cut, dispatch, 1))
|
||||
return true;
|
||||
// If the node above has no content and the node below is
|
||||
// selectable, delete the node above and select the one below.
|
||||
if ($cursor.parent.content.size == 0 &&
|
||||
(textblockAt(after, "start") || NodeSelection.isSelectable(after))) {
|
||||
let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty);
|
||||
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
|
||||
if (dispatch) {
|
||||
let tr = state.tr.step(delStep);
|
||||
tr.setSelection(textblockAt(after, "start") ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1)
|
||||
: NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If the next node is an atom, delete it
|
||||
if (after.isAtom && $cut.depth == $cursor.depth - 1) {
|
||||
if (dispatch)
|
||||
dispatch(state.tr.delete($cut.pos, $cut.pos + after.nodeSize).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
When the selection is empty and at the end of a textblock, select
|
||||
the node coming after that textblock, if possible. This is intended
|
||||
to be bound to keys like delete, after
|
||||
[`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward) and similar deleting
|
||||
commands, to provide a fall-back behavior when the schema doesn't
|
||||
allow deletion at the selected point.
|
||||
*/
|
||||
const selectNodeForward = (state, dispatch, view) => {
|
||||
let { $head, empty } = state.selection, $cut = $head;
|
||||
if (!empty)
|
||||
return false;
|
||||
if ($head.parent.isTextblock) {
|
||||
if (view ? !view.endOfTextblock("forward", state) : $head.parentOffset < $head.parent.content.size)
|
||||
return false;
|
||||
$cut = findCutAfter($head);
|
||||
}
|
||||
let node = $cut && $cut.nodeAfter;
|
||||
if (!node || !NodeSelection.isSelectable(node))
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos)).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function findCutAfter($pos) {
|
||||
if (!$pos.parent.type.spec.isolating)
|
||||
for (let i = $pos.depth - 1; i >= 0; i--) {
|
||||
let parent = $pos.node(i);
|
||||
if ($pos.index(i) + 1 < parent.childCount)
|
||||
return $pos.doc.resolve($pos.after(i + 1));
|
||||
if (parent.type.spec.isolating)
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
Join the selected block or, if there is a text selection, the
|
||||
closest ancestor block of the selection that can be joined, with
|
||||
the sibling above it.
|
||||
*/
|
||||
const joinUp = (state, dispatch) => {
|
||||
let sel = state.selection, nodeSel = sel instanceof NodeSelection, point;
|
||||
if (nodeSel) {
|
||||
if (sel.node.isTextblock || !canJoin(state.doc, sel.from))
|
||||
return false;
|
||||
point = sel.from;
|
||||
}
|
||||
else {
|
||||
point = joinPoint(state.doc, sel.from, -1);
|
||||
if (point == null)
|
||||
return false;
|
||||
}
|
||||
if (dispatch) {
|
||||
let tr = state.tr.join(point);
|
||||
if (nodeSel)
|
||||
tr.setSelection(NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore.nodeSize));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
Join the selected block, or the closest ancestor of the selection
|
||||
that can be joined, with the sibling after it.
|
||||
*/
|
||||
const joinDown = (state, dispatch) => {
|
||||
let sel = state.selection, point;
|
||||
if (sel instanceof NodeSelection) {
|
||||
if (sel.node.isTextblock || !canJoin(state.doc, sel.to))
|
||||
return false;
|
||||
point = sel.to;
|
||||
}
|
||||
else {
|
||||
point = joinPoint(state.doc, sel.to, 1);
|
||||
if (point == null)
|
||||
return false;
|
||||
}
|
||||
if (dispatch)
|
||||
dispatch(state.tr.join(point).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
Lift the selected block, or the closest ancestor block of the
|
||||
selection that can be lifted, out of its parent node.
|
||||
*/
|
||||
const lift = (state, dispatch) => {
|
||||
let { $from, $to } = state.selection;
|
||||
let range = $from.blockRange($to), target = range && liftTarget(range);
|
||||
if (target == null)
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
If the selection is in a node whose type has a truthy
|
||||
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, replace the
|
||||
selection with a newline character.
|
||||
*/
|
||||
const newlineInCode = (state, dispatch) => {
|
||||
let { $head, $anchor } = state.selection;
|
||||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor))
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.insertText("\n").scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
function defaultBlockAt(match) {
|
||||
for (let i = 0; i < match.edgeCount; i++) {
|
||||
let { type } = match.edge(i);
|
||||
if (type.isTextblock && !type.hasRequiredAttrs())
|
||||
return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
When the selection is in a node with a truthy
|
||||
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, create a
|
||||
default block after the code block, and move the cursor there.
|
||||
*/
|
||||
const exitCode = (state, dispatch) => {
|
||||
let { $head, $anchor } = state.selection;
|
||||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor))
|
||||
return false;
|
||||
let above = $head.node(-1), after = $head.indexAfter(-1), type = defaultBlockAt(above.contentMatchAt(after));
|
||||
if (!type || !above.canReplaceWith(after, after, type))
|
||||
return false;
|
||||
if (dispatch) {
|
||||
let pos = $head.after(), tr = state.tr.replaceWith(pos, pos, type.createAndFill());
|
||||
tr.setSelection(Selection.near(tr.doc.resolve(pos), 1));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
If a block node is selected, create an empty paragraph before (if
|
||||
it is its parent's first child) or after it.
|
||||
*/
|
||||
const createParagraphNear = (state, dispatch) => {
|
||||
let sel = state.selection, { $from, $to } = sel;
|
||||
if (sel instanceof AllSelection || $from.parent.inlineContent || $to.parent.inlineContent)
|
||||
return false;
|
||||
let type = defaultBlockAt($to.parent.contentMatchAt($to.indexAfter()));
|
||||
if (!type || !type.isTextblock)
|
||||
return false;
|
||||
if (dispatch) {
|
||||
let side = (!$from.parentOffset && $to.index() < $to.parent.childCount ? $from : $to).pos;
|
||||
let tr = state.tr.insert(side, type.createAndFill());
|
||||
tr.setSelection(TextSelection.create(tr.doc, side + 1));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
If the cursor is in an empty textblock that can be lifted, lift the
|
||||
block.
|
||||
*/
|
||||
const liftEmptyBlock = (state, dispatch) => {
|
||||
let { $cursor } = state.selection;
|
||||
if (!$cursor || $cursor.parent.content.size)
|
||||
return false;
|
||||
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
|
||||
let before = $cursor.before();
|
||||
if (canSplit(state.doc, before)) {
|
||||
if (dispatch)
|
||||
dispatch(state.tr.split(before).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let range = $cursor.blockRange(), target = range && liftTarget(range);
|
||||
if (target == null)
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
Create a variant of [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) that uses
|
||||
a custom function to determine the type of the newly split off block.
|
||||
*/
|
||||
function splitBlockAs(splitNode) {
|
||||
return (state, dispatch) => {
|
||||
let { $from, $to } = state.selection;
|
||||
if (state.selection instanceof NodeSelection && state.selection.node.isBlock) {
|
||||
if (!$from.parentOffset || !canSplit(state.doc, $from.pos))
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.split($from.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
if (!$from.depth)
|
||||
return false;
|
||||
let types = [];
|
||||
let splitDepth, deflt, atEnd = false, atStart = false;
|
||||
for (let d = $from.depth;; d--) {
|
||||
let node = $from.node(d);
|
||||
if (node.isBlock) {
|
||||
atEnd = $from.end(d) == $from.pos + ($from.depth - d);
|
||||
atStart = $from.start(d) == $from.pos - ($from.depth - d);
|
||||
deflt = defaultBlockAt($from.node(d - 1).contentMatchAt($from.indexAfter(d - 1)));
|
||||
let splitType = splitNode && splitNode($to.parent, atEnd, $from);
|
||||
types.unshift(splitType || (atEnd && deflt ? { type: deflt } : null));
|
||||
splitDepth = d;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (d == 1)
|
||||
return false;
|
||||
types.unshift(null);
|
||||
}
|
||||
}
|
||||
let tr = state.tr;
|
||||
if (state.selection instanceof TextSelection || state.selection instanceof AllSelection)
|
||||
tr.deleteSelection();
|
||||
let splitPos = tr.mapping.map($from.pos);
|
||||
let can = canSplit(tr.doc, splitPos, types.length, types);
|
||||
if (!can) {
|
||||
types[0] = deflt ? { type: deflt } : null;
|
||||
can = canSplit(tr.doc, splitPos, types.length, types);
|
||||
}
|
||||
if (!can)
|
||||
return false;
|
||||
tr.split(splitPos, types.length, types);
|
||||
if (!atEnd && atStart && $from.node(splitDepth).type != deflt) {
|
||||
let first = tr.mapping.map($from.before(splitDepth)), $first = tr.doc.resolve(first);
|
||||
if (deflt && $from.node(splitDepth - 1).canReplaceWith($first.index(), $first.index() + 1, deflt))
|
||||
tr.setNodeMarkup(tr.mapping.map($from.before(splitDepth)), deflt);
|
||||
}
|
||||
if (dispatch)
|
||||
dispatch(tr.scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Split the parent block of the selection. If the selection is a text
|
||||
selection, also delete its content.
|
||||
*/
|
||||
const splitBlock = splitBlockAs();
|
||||
/**
|
||||
Acts like [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock), but without
|
||||
resetting the set of active marks at the cursor.
|
||||
*/
|
||||
const splitBlockKeepMarks = (state, dispatch) => {
|
||||
return splitBlock(state, dispatch && (tr => {
|
||||
let marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
|
||||
if (marks)
|
||||
tr.ensureMarks(marks);
|
||||
dispatch(tr);
|
||||
}));
|
||||
};
|
||||
/**
|
||||
Move the selection to the node wrapping the current selection, if
|
||||
any. (Will not select the document node.)
|
||||
*/
|
||||
const selectParentNode = (state, dispatch) => {
|
||||
let { $from, to } = state.selection, pos;
|
||||
let same = $from.sharedDepth(to);
|
||||
if (same == 0)
|
||||
return false;
|
||||
pos = $from.before(same);
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, pos)));
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
Select the whole document.
|
||||
*/
|
||||
const selectAll = (state, dispatch) => {
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(new AllSelection(state.doc)));
|
||||
return true;
|
||||
};
|
||||
function joinMaybeClear(state, $pos, dispatch) {
|
||||
let before = $pos.nodeBefore, after = $pos.nodeAfter, index = $pos.index();
|
||||
if (!before || !after || !before.type.compatibleContent(after.type))
|
||||
return false;
|
||||
if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
|
||||
if (dispatch)
|
||||
dispatch(state.tr.delete($pos.pos - before.nodeSize, $pos.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || canJoin(state.doc, $pos.pos)))
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.join($pos.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
function deleteBarrier(state, $cut, dispatch, dir) {
|
||||
let before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match;
|
||||
let isolated = before.type.spec.isolating || after.type.spec.isolating;
|
||||
if (!isolated && joinMaybeClear(state, $cut, dispatch))
|
||||
return true;
|
||||
let canDelAfter = !isolated && $cut.parent.canReplace($cut.index(), $cut.index() + 1);
|
||||
if (canDelAfter &&
|
||||
(conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) &&
|
||||
match.matchType(conn[0] || after.type).validEnd) {
|
||||
if (dispatch) {
|
||||
let end = $cut.pos + after.nodeSize, wrap = Fragment.empty;
|
||||
for (let i = conn.length - 1; i >= 0; i--)
|
||||
wrap = Fragment.from(conn[i].create(null, wrap));
|
||||
wrap = Fragment.from(before.copy(wrap));
|
||||
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap, 1, 0), conn.length, true));
|
||||
let $joinAt = tr.doc.resolve(end + 2 * conn.length);
|
||||
if ($joinAt.nodeAfter && $joinAt.nodeAfter.type == before.type &&
|
||||
canJoin(tr.doc, $joinAt.pos))
|
||||
tr.join($joinAt.pos);
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
let selAfter = after.type.spec.isolating || (dir > 0 && isolated) ? null : Selection.findFrom($cut, 1);
|
||||
let range = selAfter && selAfter.$from.blockRange(selAfter.$to), target = range && liftTarget(range);
|
||||
if (target != null && target >= $cut.depth) {
|
||||
if (dispatch)
|
||||
dispatch(state.tr.lift(range, target).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
if (canDelAfter && textblockAt(after, "start", true) && textblockAt(before, "end")) {
|
||||
let at = before, wrap = [];
|
||||
for (;;) {
|
||||
wrap.push(at);
|
||||
if (at.isTextblock)
|
||||
break;
|
||||
at = at.lastChild;
|
||||
}
|
||||
let afterText = after, afterDepth = 1;
|
||||
for (; !afterText.isTextblock; afterText = afterText.firstChild)
|
||||
afterDepth++;
|
||||
if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
|
||||
if (dispatch) {
|
||||
let end = Fragment.empty;
|
||||
for (let i = wrap.length - 1; i >= 0; i--)
|
||||
end = Fragment.from(wrap[i].copy(end));
|
||||
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - wrap.length, $cut.pos + after.nodeSize, $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth, new Slice(end, wrap.length, 0), 0, true));
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function selectTextblockSide(side) {
|
||||
return function (state, dispatch) {
|
||||
let sel = state.selection, $pos = side < 0 ? sel.$from : sel.$to;
|
||||
let depth = $pos.depth;
|
||||
while ($pos.node(depth).isInline) {
|
||||
if (!depth)
|
||||
return false;
|
||||
depth--;
|
||||
}
|
||||
if (!$pos.node(depth).isTextblock)
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(TextSelection.create(state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Moves the cursor to the start of current text block.
|
||||
*/
|
||||
const selectTextblockStart = selectTextblockSide(-1);
|
||||
/**
|
||||
Moves the cursor to the end of current text block.
|
||||
*/
|
||||
const selectTextblockEnd = selectTextblockSide(1);
|
||||
// Parameterized commands
|
||||
/**
|
||||
Wrap the selection in a node of the given type with the given
|
||||
attributes.
|
||||
*/
|
||||
function wrapIn(nodeType, attrs = null) {
|
||||
return function (state, dispatch) {
|
||||
let { $from, $to } = state.selection;
|
||||
let range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs);
|
||||
if (!wrapping)
|
||||
return false;
|
||||
if (dispatch)
|
||||
dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
|
||||
return true;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Returns a command that tries to set the selected textblocks to the
|
||||
given node type with the given attributes.
|
||||
*/
|
||||
function setBlockType(nodeType, attrs = null) {
|
||||
return function (state, dispatch) {
|
||||
let applicable = false;
|
||||
for (let i = 0; i < state.selection.ranges.length && !applicable; i++) {
|
||||
let { $from: { pos: from }, $to: { pos: to } } = state.selection.ranges[i];
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (applicable)
|
||||
return false;
|
||||
if (!node.isTextblock || node.hasMarkup(nodeType, attrs))
|
||||
return;
|
||||
if (node.type == nodeType) {
|
||||
applicable = true;
|
||||
}
|
||||
else {
|
||||
let $pos = state.doc.resolve(pos), index = $pos.index();
|
||||
applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!applicable)
|
||||
return false;
|
||||
if (dispatch) {
|
||||
let tr = state.tr;
|
||||
for (let i = 0; i < state.selection.ranges.length; i++) {
|
||||
let { $from: { pos: from }, $to: { pos: to } } = state.selection.ranges[i];
|
||||
tr.setBlockType(from, to, nodeType, attrs);
|
||||
}
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
function markApplies(doc, ranges, type, enterAtoms) {
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let { $from, $to } = ranges[i];
|
||||
let can = $from.depth == 0 ? doc.inlineContent && doc.type.allowsMarkType(type) : false;
|
||||
doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
|
||||
if (can || !enterAtoms && node.isAtom && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos)
|
||||
return false;
|
||||
can = node.inlineContent && node.type.allowsMarkType(type);
|
||||
});
|
||||
if (can)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function removeInlineAtoms(ranges) {
|
||||
let result = [];
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let { $from, $to } = ranges[i];
|
||||
$from.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
|
||||
if (node.isAtom && node.content.size && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos) {
|
||||
if (pos + 1 > $from.pos)
|
||||
result.push(new SelectionRange($from, $from.doc.resolve(pos + 1)));
|
||||
$from = $from.doc.resolve(pos + 1 + node.content.size);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if ($from.pos < $to.pos)
|
||||
result.push(new SelectionRange($from, $to));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
Create a command function that toggles the given mark with the
|
||||
given attributes. Will return `false` when the current selection
|
||||
doesn't support that mark. This will remove the mark if any marks
|
||||
of that type exist in the selection, or add it otherwise. If the
|
||||
selection is empty, this applies to the [stored
|
||||
marks](https://prosemirror.net/docs/ref/#state.EditorState.storedMarks) instead of a range of the
|
||||
document.
|
||||
*/
|
||||
function toggleMark(markType, attrs = null, options) {
|
||||
let removeWhenPresent = (options && options.removeWhenPresent) !== false;
|
||||
let enterAtoms = (options && options.enterInlineAtoms) !== false;
|
||||
let dropSpace = !(options && options.includeWhitespace);
|
||||
return function (state, dispatch) {
|
||||
let { empty, $cursor, ranges } = state.selection;
|
||||
if ((empty && !$cursor) || !markApplies(state.doc, ranges, markType, enterAtoms))
|
||||
return false;
|
||||
if (dispatch) {
|
||||
if ($cursor) {
|
||||
if (markType.isInSet(state.storedMarks || $cursor.marks()))
|
||||
dispatch(state.tr.removeStoredMark(markType));
|
||||
else
|
||||
dispatch(state.tr.addStoredMark(markType.create(attrs)));
|
||||
}
|
||||
else {
|
||||
let add, tr = state.tr;
|
||||
if (!enterAtoms)
|
||||
ranges = removeInlineAtoms(ranges);
|
||||
if (removeWhenPresent) {
|
||||
add = !ranges.some(r => state.doc.rangeHasMark(r.$from.pos, r.$to.pos, markType));
|
||||
}
|
||||
else {
|
||||
add = !ranges.every(r => {
|
||||
let missing = false;
|
||||
tr.doc.nodesBetween(r.$from.pos, r.$to.pos, (node, pos, parent) => {
|
||||
if (missing)
|
||||
return false;
|
||||
missing = !markType.isInSet(node.marks) && !!parent && parent.type.allowsMarkType(markType) &&
|
||||
!(node.isText && /^\s*$/.test(node.textBetween(Math.max(0, r.$from.pos - pos), Math.min(node.nodeSize, r.$to.pos - pos))));
|
||||
});
|
||||
return !missing;
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let { $from, $to } = ranges[i];
|
||||
if (!add) {
|
||||
tr.removeMark($from.pos, $to.pos, markType);
|
||||
}
|
||||
else {
|
||||
let from = $from.pos, to = $to.pos, start = $from.nodeAfter, end = $to.nodeBefore;
|
||||
let spaceStart = dropSpace && start && start.isText ? /^\s*/.exec(start.text)[0].length : 0;
|
||||
let spaceEnd = dropSpace && end && end.isText ? /\s*$/.exec(end.text)[0].length : 0;
|
||||
if (from + spaceStart < to) {
|
||||
from += spaceStart;
|
||||
to -= spaceEnd;
|
||||
}
|
||||
tr.addMark(from, to, markType.create(attrs));
|
||||
}
|
||||
}
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
function wrapDispatchForJoin(dispatch, isJoinable) {
|
||||
return (tr) => {
|
||||
if (!tr.isGeneric)
|
||||
return dispatch(tr);
|
||||
let ranges = [];
|
||||
for (let i = 0; i < tr.mapping.maps.length; i++) {
|
||||
let map = tr.mapping.maps[i];
|
||||
for (let j = 0; j < ranges.length; j++)
|
||||
ranges[j] = map.map(ranges[j]);
|
||||
map.forEach((_s, _e, from, to) => ranges.push(from, to));
|
||||
}
|
||||
// Figure out which joinable points exist inside those ranges,
|
||||
// by checking all node boundaries in their parent nodes.
|
||||
let joinable = [];
|
||||
for (let i = 0; i < ranges.length; i += 2) {
|
||||
let from = ranges[i], to = ranges[i + 1];
|
||||
let $from = tr.doc.resolve(from), depth = $from.sharedDepth(to), parent = $from.node(depth);
|
||||
for (let index = $from.indexAfter(depth), pos = $from.after(depth + 1); pos <= to; ++index) {
|
||||
let after = parent.maybeChild(index);
|
||||
if (!after)
|
||||
break;
|
||||
if (index && joinable.indexOf(pos) == -1) {
|
||||
let before = parent.child(index - 1);
|
||||
if (before.type == after.type && isJoinable(before, after))
|
||||
joinable.push(pos);
|
||||
}
|
||||
pos += after.nodeSize;
|
||||
}
|
||||
}
|
||||
// Join the joinable points
|
||||
joinable.sort((a, b) => a - b);
|
||||
for (let i = joinable.length - 1; i >= 0; i--) {
|
||||
if (canJoin(tr.doc, joinable[i]))
|
||||
tr.join(joinable[i]);
|
||||
}
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
/**
|
||||
Wrap a command so that, when it produces a transform that causes
|
||||
two joinable nodes to end up next to each other, those are joined.
|
||||
Nodes are considered joinable when they are of the same type and
|
||||
when the `isJoinable` predicate returns true for them or, if an
|
||||
array of strings was passed, if their node type name is in that
|
||||
array.
|
||||
*/
|
||||
function autoJoin(command, isJoinable) {
|
||||
let canJoin = Array.isArray(isJoinable) ? (node) => isJoinable.indexOf(node.type.name) > -1
|
||||
: isJoinable;
|
||||
return (state, dispatch, view) => command(state, dispatch && wrapDispatchForJoin(dispatch, canJoin), view);
|
||||
}
|
||||
/**
|
||||
Combine a number of command functions into a single function (which
|
||||
calls them one by one until one returns true).
|
||||
*/
|
||||
function chainCommands(...commands) {
|
||||
return function (state, dispatch, view) {
|
||||
for (let i = 0; i < commands.length; i++)
|
||||
if (commands[i](state, dispatch, view))
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
let backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
|
||||
let del = chainCommands(deleteSelection, joinForward, selectNodeForward);
|
||||
/**
|
||||
A basic keymap containing bindings not specific to any schema.
|
||||
Binds the following keys (when multiple commands are listed, they
|
||||
are chained with [`chainCommands`](https://prosemirror.net/docs/ref/#commands.chainCommands)):
|
||||
|
||||
* **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock`
|
||||
* **Mod-Enter** to `exitCode`
|
||||
* **Backspace** and **Mod-Backspace** to `deleteSelection`, `joinBackward`, `selectNodeBackward`
|
||||
* **Delete** and **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
* **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
* **Mod-a** to `selectAll`
|
||||
*/
|
||||
const pcBaseKeymap = {
|
||||
"Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock),
|
||||
"Mod-Enter": exitCode,
|
||||
"Backspace": backspace,
|
||||
"Mod-Backspace": backspace,
|
||||
"Shift-Backspace": backspace,
|
||||
"Delete": del,
|
||||
"Mod-Delete": del,
|
||||
"Mod-a": selectAll
|
||||
};
|
||||
/**
|
||||
A copy of `pcBaseKeymap` that also binds **Ctrl-h** like Backspace,
|
||||
**Ctrl-d** like Delete, **Alt-Backspace** like Ctrl-Backspace, and
|
||||
**Ctrl-Alt-Backspace**, **Alt-Delete**, and **Alt-d** like
|
||||
Ctrl-Delete.
|
||||
*/
|
||||
const macBaseKeymap = {
|
||||
"Ctrl-h": pcBaseKeymap["Backspace"],
|
||||
"Alt-Backspace": pcBaseKeymap["Mod-Backspace"],
|
||||
"Ctrl-d": pcBaseKeymap["Delete"],
|
||||
"Ctrl-Alt-Backspace": pcBaseKeymap["Mod-Delete"],
|
||||
"Alt-Delete": pcBaseKeymap["Mod-Delete"],
|
||||
"Alt-d": pcBaseKeymap["Mod-Delete"],
|
||||
"Ctrl-a": selectTextblockStart,
|
||||
"Ctrl-e": selectTextblockEnd
|
||||
};
|
||||
for (let key in pcBaseKeymap)
|
||||
macBaseKeymap[key] = pcBaseKeymap[key];
|
||||
const mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform)
|
||||
// @ts-ignore
|
||||
: typeof os != "undefined" && os.platform ? os.platform() == "darwin" : false;
|
||||
/**
|
||||
Depending on the detected platform, this will hold
|
||||
[`pcBasekeymap`](https://prosemirror.net/docs/ref/#commands.pcBaseKeymap) or
|
||||
[`macBaseKeymap`](https://prosemirror.net/docs/ref/#commands.macBaseKeymap).
|
||||
*/
|
||||
const baseKeymap = mac ? macBaseKeymap : pcBaseKeymap;
|
||||
|
||||
export { autoJoin, baseKeymap, chainCommands, createParagraphNear, deleteSelection, exitCode, joinBackward, joinDown, joinForward, joinTextblockBackward, joinTextblockForward, joinUp, lift, liftEmptyBlock, macBaseKeymap, newlineInCode, pcBaseKeymap, selectAll, selectNodeBackward, selectNodeForward, selectParentNode, selectTextblockEnd, selectTextblockStart, setBlockType, splitBlock, splitBlockAs, splitBlockKeepMarks, toggleMark, wrapIn };
|
||||
39
node_modules/prosemirror-commands/package.json
generated
vendored
Normal file
39
node_modules/prosemirror-commands/package.json
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "prosemirror-commands",
|
||||
"version": "1.7.1",
|
||||
"description": "Editing commands for ProseMirror",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"license": "MIT",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Marijn Haverbeke",
|
||||
"email": "marijn@haverbeke.berlin",
|
||||
"web": "http://marijnhaverbeke.nl"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/prosemirror/prosemirror-commands.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-transform": "^1.10.2",
|
||||
"prosemirror-state": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@prosemirror/buildhelper": "^0.1.5",
|
||||
"prosemirror-test-builder": "^1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "pm-runtests",
|
||||
"prepare": "pm-buildhelper src/commands.ts"
|
||||
}
|
||||
}
|
||||
40
node_modules/prosemirror-commands/src/README.md
generated
vendored
Normal file
40
node_modules/prosemirror-commands/src/README.md
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
This module exports a number of _commands_, which are building block
|
||||
functions that encapsulate an editing action. A command function takes
|
||||
an editor state, _optionally_ a `dispatch` function that it can use
|
||||
to dispatch a transaction and _optionally_ an `EditorView` instance.
|
||||
It should return a boolean that indicates whether it could perform any
|
||||
action. When no `dispatch` callback is passed, the command should do a
|
||||
'dry run', determining whether it is applicable, but not actually doing
|
||||
anything.
|
||||
|
||||
These are mostly used to bind keys and define menu items.
|
||||
|
||||
@chainCommands
|
||||
@deleteSelection
|
||||
@joinBackward
|
||||
@selectNodeBackward
|
||||
@joinTextblockBackward
|
||||
@joinForward
|
||||
@selectNodeForward
|
||||
@joinTextblockForward
|
||||
@joinUp
|
||||
@joinDown
|
||||
@lift
|
||||
@newlineInCode
|
||||
@exitCode
|
||||
@createParagraphNear
|
||||
@liftEmptyBlock
|
||||
@splitBlock
|
||||
@splitBlockAs
|
||||
@splitBlockKeepMarks
|
||||
@selectParentNode
|
||||
@selectAll
|
||||
@selectTextblockStart
|
||||
@selectTextblockEnd
|
||||
@wrapIn
|
||||
@setBlockType
|
||||
@toggleMark
|
||||
@autoJoin
|
||||
@baseKeymap
|
||||
@pcBaseKeymap
|
||||
@macBaseKeymap
|
||||
783
node_modules/prosemirror-commands/src/commands.ts
generated
vendored
Normal file
783
node_modules/prosemirror-commands/src/commands.ts
generated
vendored
Normal file
@@ -0,0 +1,783 @@
|
||||
import {joinPoint, canJoin, findWrapping, liftTarget, canSplit,
|
||||
ReplaceStep, ReplaceAroundStep, replaceStep} from "prosemirror-transform"
|
||||
import {Slice, Fragment, Node, NodeType, Attrs, MarkType, ResolvedPos, ContentMatch} from "prosemirror-model"
|
||||
import {Selection, EditorState, Transaction, TextSelection, NodeSelection,
|
||||
SelectionRange, AllSelection, Command} from "prosemirror-state"
|
||||
import {EditorView} from "prosemirror-view"
|
||||
|
||||
/// Delete the selection, if there is one.
|
||||
export const deleteSelection: Command = (state, dispatch) => {
|
||||
if (state.selection.empty) return false
|
||||
if (dispatch) dispatch(state.tr.deleteSelection().scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
function atBlockStart(state: EditorState, view?: EditorView): ResolvedPos | null {
|
||||
let {$cursor} = state.selection as TextSelection
|
||||
if (!$cursor || (view ? !view.endOfTextblock("backward", state)
|
||||
: $cursor.parentOffset > 0))
|
||||
return null
|
||||
return $cursor
|
||||
}
|
||||
|
||||
/// If the selection is empty and at the start of a textblock, try to
|
||||
/// reduce the distance between that block and the one before it—if
|
||||
/// there's a block directly before it that can be joined, join them.
|
||||
/// If not, try to move the selected block closer to the next one in
|
||||
/// the document structure by lifting it out of its parent or moving it
|
||||
/// into a parent of the previous block. Will use the view for accurate
|
||||
/// (bidi-aware) start-of-textblock detection if given.
|
||||
export const joinBackward: Command = (state, dispatch, view) => {
|
||||
let $cursor = atBlockStart(state, view)
|
||||
if (!$cursor) return false
|
||||
|
||||
let $cut = findCutBefore($cursor)
|
||||
|
||||
// If there is no node before this, try to lift
|
||||
if (!$cut) {
|
||||
let range = $cursor.blockRange(), target = range && liftTarget(range)
|
||||
if (target == null) return false
|
||||
if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
let before = $cut.nodeBefore!
|
||||
// Apply the joining algorithm
|
||||
if (deleteBarrier(state, $cut, dispatch, -1)) return true
|
||||
|
||||
// If the node below has no content and the node above is
|
||||
// selectable, delete the node below and select the one above.
|
||||
if ($cursor.parent.content.size == 0 &&
|
||||
(textblockAt(before, "end") || NodeSelection.isSelectable(before))) {
|
||||
for (let depth = $cursor.depth;; depth--) {
|
||||
let delStep = replaceStep(state.doc, $cursor.before(depth), $cursor.after(depth), Slice.empty)
|
||||
if (delStep && (delStep as ReplaceStep).slice.size < (delStep as ReplaceStep).to - (delStep as ReplaceStep).from) {
|
||||
if (dispatch) {
|
||||
let tr = state.tr.step(delStep)
|
||||
tr.setSelection(textblockAt(before, "end")
|
||||
? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1)!
|
||||
: NodeSelection.create(tr.doc, $cut.pos - before.nodeSize))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (depth == 1 || $cursor.node(depth - 1).childCount > 1) break
|
||||
}
|
||||
}
|
||||
|
||||
// If the node before is an atom, delete it
|
||||
if (before.isAtom && $cut.depth == $cursor.depth - 1) {
|
||||
if (dispatch) dispatch(state.tr.delete($cut.pos - before.nodeSize, $cut.pos).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/// A more limited form of [`joinBackward`](#commands.joinBackward)
|
||||
/// that only tries to join the current textblock to the one before
|
||||
/// it, if the cursor is at the start of a textblock.
|
||||
export const joinTextblockBackward: Command = (state, dispatch, view) => {
|
||||
let $cursor = atBlockStart(state, view)
|
||||
if (!$cursor) return false
|
||||
let $cut = findCutBefore($cursor)
|
||||
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false
|
||||
}
|
||||
|
||||
/// A more limited form of [`joinForward`](#commands.joinForward)
|
||||
/// that only tries to join the current textblock to the one after
|
||||
/// it, if the cursor is at the end of a textblock.
|
||||
export const joinTextblockForward: Command = (state, dispatch, view) => {
|
||||
let $cursor = atBlockEnd(state, view)
|
||||
if (!$cursor) return false
|
||||
let $cut = findCutAfter($cursor)
|
||||
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false
|
||||
}
|
||||
|
||||
function joinTextblocksAround(state: EditorState, $cut: ResolvedPos, dispatch?: (tr: Transaction) => void) {
|
||||
let before = $cut.nodeBefore!, beforeText = before, beforePos = $cut.pos - 1
|
||||
for (; !beforeText.isTextblock; beforePos--) {
|
||||
if (beforeText.type.spec.isolating) return false
|
||||
let child = beforeText.lastChild
|
||||
if (!child) return false
|
||||
beforeText = child
|
||||
}
|
||||
let after = $cut.nodeAfter!, afterText = after, afterPos = $cut.pos + 1
|
||||
for (; !afterText.isTextblock; afterPos++) {
|
||||
if (afterText.type.spec.isolating) return false
|
||||
let child = afterText.firstChild
|
||||
if (!child) return false
|
||||
afterText = child
|
||||
}
|
||||
let step = replaceStep(state.doc, beforePos, afterPos, Slice.empty) as ReplaceStep | null
|
||||
if (!step || step.from != beforePos ||
|
||||
step instanceof ReplaceStep && step.slice.size >= afterPos - beforePos) return false
|
||||
if (dispatch) {
|
||||
let tr = state.tr.step(step)
|
||||
tr.setSelection(TextSelection.create(tr.doc, beforePos))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
function textblockAt(node: Node, side: "start" | "end", only = false) {
|
||||
for (let scan: Node | null = node; scan; scan = (side == "start" ? scan.firstChild : scan.lastChild)) {
|
||||
if (scan.isTextblock) return true
|
||||
if (only && scan.childCount != 1) return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// When the selection is empty and at the start of a textblock, select
|
||||
/// the node before that textblock, if possible. This is intended to be
|
||||
/// bound to keys like backspace, after
|
||||
/// [`joinBackward`](#commands.joinBackward) or other deleting
|
||||
/// commands, as a fall-back behavior when the schema doesn't allow
|
||||
/// deletion at the selected point.
|
||||
export const selectNodeBackward: Command = (state, dispatch, view) => {
|
||||
let {$head, empty} = state.selection, $cut: ResolvedPos | null = $head
|
||||
if (!empty) return false
|
||||
|
||||
if ($head.parent.isTextblock) {
|
||||
if (view ? !view.endOfTextblock("backward", state) : $head.parentOffset > 0) return false
|
||||
$cut = findCutBefore($head)
|
||||
}
|
||||
let node = $cut && $cut.nodeBefore
|
||||
if (!node || !NodeSelection.isSelectable(node)) return false
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut!.pos - node.nodeSize)).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
function findCutBefore($pos: ResolvedPos): ResolvedPos | null {
|
||||
if (!$pos.parent.type.spec.isolating) for (let i = $pos.depth - 1; i >= 0; i--) {
|
||||
if ($pos.index(i) > 0) return $pos.doc.resolve($pos.before(i + 1))
|
||||
if ($pos.node(i).type.spec.isolating) break
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function atBlockEnd(state: EditorState, view?: EditorView): ResolvedPos | null {
|
||||
let {$cursor} = state.selection as TextSelection
|
||||
if (!$cursor || (view ? !view.endOfTextblock("forward", state)
|
||||
: $cursor.parentOffset < $cursor.parent.content.size))
|
||||
return null
|
||||
return $cursor
|
||||
}
|
||||
|
||||
/// If the selection is empty and the cursor is at the end of a
|
||||
/// textblock, try to reduce or remove the boundary between that block
|
||||
/// and the one after it, either by joining them or by moving the other
|
||||
/// block closer to this one in the tree structure. Will use the view
|
||||
/// for accurate start-of-textblock detection if given.
|
||||
export const joinForward: Command = (state, dispatch, view) => {
|
||||
let $cursor = atBlockEnd(state, view)
|
||||
if (!$cursor) return false
|
||||
|
||||
let $cut = findCutAfter($cursor)
|
||||
// If there is no node after this, there's nothing to do
|
||||
if (!$cut) return false
|
||||
|
||||
let after = $cut.nodeAfter!
|
||||
// Try the joining algorithm
|
||||
if (deleteBarrier(state, $cut, dispatch, 1)) return true
|
||||
|
||||
// If the node above has no content and the node below is
|
||||
// selectable, delete the node above and select the one below.
|
||||
if ($cursor.parent.content.size == 0 &&
|
||||
(textblockAt(after, "start") || NodeSelection.isSelectable(after))) {
|
||||
let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty)
|
||||
if (delStep && (delStep as ReplaceStep).slice.size < (delStep as ReplaceStep).to - (delStep as ReplaceStep).from) {
|
||||
if (dispatch) {
|
||||
let tr = state.tr.step(delStep)
|
||||
tr.setSelection(textblockAt(after, "start") ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1)!
|
||||
: NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If the next node is an atom, delete it
|
||||
if (after.isAtom && $cut.depth == $cursor.depth - 1) {
|
||||
if (dispatch) dispatch(state.tr.delete($cut.pos, $cut.pos + after.nodeSize).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/// When the selection is empty and at the end of a textblock, select
|
||||
/// the node coming after that textblock, if possible. This is intended
|
||||
/// to be bound to keys like delete, after
|
||||
/// [`joinForward`](#commands.joinForward) and similar deleting
|
||||
/// commands, to provide a fall-back behavior when the schema doesn't
|
||||
/// allow deletion at the selected point.
|
||||
export const selectNodeForward: Command = (state, dispatch, view) => {
|
||||
let {$head, empty} = state.selection, $cut: ResolvedPos | null = $head
|
||||
if (!empty) return false
|
||||
if ($head.parent.isTextblock) {
|
||||
if (view ? !view.endOfTextblock("forward", state) : $head.parentOffset < $head.parent.content.size)
|
||||
return false
|
||||
$cut = findCutAfter($head)
|
||||
}
|
||||
let node = $cut && $cut.nodeAfter
|
||||
if (!node || !NodeSelection.isSelectable(node)) return false
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut!.pos)).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
function findCutAfter($pos: ResolvedPos) {
|
||||
if (!$pos.parent.type.spec.isolating) for (let i = $pos.depth - 1; i >= 0; i--) {
|
||||
let parent = $pos.node(i)
|
||||
if ($pos.index(i) + 1 < parent.childCount) return $pos.doc.resolve($pos.after(i + 1))
|
||||
if (parent.type.spec.isolating) break
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/// Join the selected block or, if there is a text selection, the
|
||||
/// closest ancestor block of the selection that can be joined, with
|
||||
/// the sibling above it.
|
||||
export const joinUp: Command = (state, dispatch) => {
|
||||
let sel = state.selection, nodeSel = sel instanceof NodeSelection, point
|
||||
if (nodeSel) {
|
||||
if ((sel as NodeSelection).node.isTextblock || !canJoin(state.doc, sel.from)) return false
|
||||
point = sel.from
|
||||
} else {
|
||||
point = joinPoint(state.doc, sel.from, -1)
|
||||
if (point == null) return false
|
||||
}
|
||||
if (dispatch) {
|
||||
let tr = state.tr.join(point)
|
||||
if (nodeSel) tr.setSelection(NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore!.nodeSize))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// Join the selected block, or the closest ancestor of the selection
|
||||
/// that can be joined, with the sibling after it.
|
||||
export const joinDown: Command = (state, dispatch) => {
|
||||
let sel = state.selection, point
|
||||
if (sel instanceof NodeSelection) {
|
||||
if (sel.node.isTextblock || !canJoin(state.doc, sel.to)) return false
|
||||
point = sel.to
|
||||
} else {
|
||||
point = joinPoint(state.doc, sel.to, 1)
|
||||
if (point == null) return false
|
||||
}
|
||||
if (dispatch)
|
||||
dispatch(state.tr.join(point).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
/// Lift the selected block, or the closest ancestor block of the
|
||||
/// selection that can be lifted, out of its parent node.
|
||||
export const lift: Command = (state, dispatch) => {
|
||||
let {$from, $to} = state.selection
|
||||
let range = $from.blockRange($to), target = range && liftTarget(range)
|
||||
if (target == null) return false
|
||||
if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
/// If the selection is in a node whose type has a truthy
|
||||
/// [`code`](#model.NodeSpec.code) property in its spec, replace the
|
||||
/// selection with a newline character.
|
||||
export const newlineInCode: Command = (state, dispatch) => {
|
||||
let {$head, $anchor} = state.selection
|
||||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false
|
||||
if (dispatch) dispatch(state.tr.insertText("\n").scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
function defaultBlockAt(match: ContentMatch) {
|
||||
for (let i = 0; i < match.edgeCount; i++) {
|
||||
let {type} = match.edge(i)
|
||||
if (type.isTextblock && !type.hasRequiredAttrs()) return type
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/// When the selection is in a node with a truthy
|
||||
/// [`code`](#model.NodeSpec.code) property in its spec, create a
|
||||
/// default block after the code block, and move the cursor there.
|
||||
export const exitCode: Command = (state, dispatch) => {
|
||||
let {$head, $anchor} = state.selection
|
||||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false
|
||||
let above = $head.node(-1), after = $head.indexAfter(-1), type = defaultBlockAt(above.contentMatchAt(after))
|
||||
if (!type || !above.canReplaceWith(after, after, type)) return false
|
||||
if (dispatch) {
|
||||
let pos = $head.after(), tr = state.tr.replaceWith(pos, pos, type.createAndFill()!)
|
||||
tr.setSelection(Selection.near(tr.doc.resolve(pos), 1))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// If a block node is selected, create an empty paragraph before (if
|
||||
/// it is its parent's first child) or after it.
|
||||
export const createParagraphNear: Command = (state, dispatch) => {
|
||||
let sel = state.selection, {$from, $to} = sel
|
||||
if (sel instanceof AllSelection || $from.parent.inlineContent || $to.parent.inlineContent) return false
|
||||
let type = defaultBlockAt($to.parent.contentMatchAt($to.indexAfter()))
|
||||
if (!type || !type.isTextblock) return false
|
||||
if (dispatch) {
|
||||
let side = (!$from.parentOffset && $to.index() < $to.parent.childCount ? $from : $to).pos
|
||||
let tr = state.tr.insert(side, type.createAndFill()!)
|
||||
tr.setSelection(TextSelection.create(tr.doc, side + 1))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// If the cursor is in an empty textblock that can be lifted, lift the
|
||||
/// block.
|
||||
export const liftEmptyBlock: Command = (state, dispatch) => {
|
||||
let {$cursor} = state.selection as TextSelection
|
||||
if (!$cursor || $cursor.parent.content.size) return false
|
||||
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
|
||||
let before = $cursor.before()
|
||||
if (canSplit(state.doc, before)) {
|
||||
if (dispatch) dispatch(state.tr.split(before).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
}
|
||||
let range = $cursor.blockRange(), target = range && liftTarget(range)
|
||||
if (target == null) return false
|
||||
if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
/// Create a variant of [`splitBlock`](#commands.splitBlock) that uses
|
||||
/// a custom function to determine the type of the newly split off block.
|
||||
export function splitBlockAs(
|
||||
splitNode?: (node: Node, atEnd: boolean, $from: ResolvedPos) => {type: NodeType, attrs?: Attrs} | null
|
||||
): Command {
|
||||
return (state, dispatch) => {
|
||||
let {$from, $to} = state.selection
|
||||
if (state.selection instanceof NodeSelection && state.selection.node.isBlock) {
|
||||
if (!$from.parentOffset || !canSplit(state.doc, $from.pos)) return false
|
||||
if (dispatch) dispatch(state.tr.split($from.pos).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
if (!$from.depth) return false
|
||||
let types: (null | {type: NodeType, attrs?: Attrs | null})[] = []
|
||||
let splitDepth, deflt, atEnd = false, atStart = false
|
||||
for (let d = $from.depth;; d--) {
|
||||
let node = $from.node(d)
|
||||
if (node.isBlock) {
|
||||
atEnd = $from.end(d) == $from.pos + ($from.depth - d)
|
||||
atStart = $from.start(d) == $from.pos - ($from.depth - d)
|
||||
deflt = defaultBlockAt($from.node(d - 1).contentMatchAt($from.indexAfter(d - 1)))
|
||||
let splitType = splitNode && splitNode($to.parent, atEnd, $from)
|
||||
types.unshift(splitType || (atEnd && deflt ? {type: deflt} : null))
|
||||
splitDepth = d
|
||||
break
|
||||
} else {
|
||||
if (d == 1) return false
|
||||
types.unshift(null)
|
||||
}
|
||||
}
|
||||
|
||||
let tr = state.tr
|
||||
if (state.selection instanceof TextSelection || state.selection instanceof AllSelection) tr.deleteSelection()
|
||||
let splitPos = tr.mapping.map($from.pos)
|
||||
let can = canSplit(tr.doc, splitPos, types.length, types)
|
||||
if (!can) {
|
||||
types[0] = deflt ? {type: deflt} : null
|
||||
can = canSplit(tr.doc, splitPos, types.length, types)
|
||||
}
|
||||
if (!can) return false
|
||||
tr.split(splitPos, types.length, types)
|
||||
if (!atEnd && atStart && $from.node(splitDepth).type != deflt) {
|
||||
let first = tr.mapping.map($from.before(splitDepth)), $first = tr.doc.resolve(first)
|
||||
if (deflt && $from.node(splitDepth - 1).canReplaceWith($first.index(), $first.index() + 1, deflt))
|
||||
tr.setNodeMarkup(tr.mapping.map($from.before(splitDepth)), deflt)
|
||||
}
|
||||
if (dispatch) dispatch(tr.scrollIntoView())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// Split the parent block of the selection. If the selection is a text
|
||||
/// selection, also delete its content.
|
||||
export const splitBlock: Command = splitBlockAs()
|
||||
|
||||
/// Acts like [`splitBlock`](#commands.splitBlock), but without
|
||||
/// resetting the set of active marks at the cursor.
|
||||
export const splitBlockKeepMarks: Command = (state, dispatch) => {
|
||||
return splitBlock(state, dispatch && (tr => {
|
||||
let marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks())
|
||||
if (marks) tr.ensureMarks(marks)
|
||||
dispatch(tr)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Move the selection to the node wrapping the current selection, if
|
||||
/// any. (Will not select the document node.)
|
||||
export const selectParentNode: Command = (state, dispatch) => {
|
||||
let {$from, to} = state.selection, pos
|
||||
let same = $from.sharedDepth(to)
|
||||
if (same == 0) return false
|
||||
pos = $from.before(same)
|
||||
if (dispatch) dispatch(state.tr.setSelection(NodeSelection.create(state.doc, pos)))
|
||||
return true
|
||||
}
|
||||
|
||||
/// Select the whole document.
|
||||
export const selectAll: Command = (state, dispatch) => {
|
||||
if (dispatch) dispatch(state.tr.setSelection(new AllSelection(state.doc)))
|
||||
return true
|
||||
}
|
||||
|
||||
function joinMaybeClear(state: EditorState, $pos: ResolvedPos, dispatch: ((tr: Transaction) => void) | undefined) {
|
||||
let before = $pos.nodeBefore, after = $pos.nodeAfter, index = $pos.index()
|
||||
if (!before || !after || !before.type.compatibleContent(after.type)) return false
|
||||
if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
|
||||
if (dispatch) dispatch(state.tr.delete($pos.pos - before.nodeSize, $pos.pos).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || canJoin(state.doc, $pos.pos)))
|
||||
return false
|
||||
if (dispatch)
|
||||
dispatch(state.tr.join($pos.pos).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
function deleteBarrier(state: EditorState, $cut: ResolvedPos, dispatch: ((tr: Transaction) => void) | undefined, dir: number) {
|
||||
let before = $cut.nodeBefore!, after = $cut.nodeAfter!, conn, match
|
||||
let isolated = before.type.spec.isolating || after.type.spec.isolating
|
||||
if (!isolated && joinMaybeClear(state, $cut, dispatch)) return true
|
||||
|
||||
let canDelAfter = !isolated && $cut.parent.canReplace($cut.index(), $cut.index() + 1)
|
||||
if (canDelAfter &&
|
||||
(conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) &&
|
||||
match.matchType(conn[0] || after.type)!.validEnd) {
|
||||
if (dispatch) {
|
||||
let end = $cut.pos + after.nodeSize, wrap = Fragment.empty
|
||||
for (let i = conn.length - 1; i >= 0; i--)
|
||||
wrap = Fragment.from(conn[i].create(null, wrap))
|
||||
wrap = Fragment.from(before.copy(wrap))
|
||||
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap, 1, 0), conn.length, true))
|
||||
let $joinAt = tr.doc.resolve(end + 2 * conn.length)
|
||||
if ($joinAt.nodeAfter && $joinAt.nodeAfter.type == before.type &&
|
||||
canJoin(tr.doc, $joinAt.pos)) tr.join($joinAt.pos)
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
let selAfter = after.type.spec.isolating || (dir > 0 && isolated) ? null : Selection.findFrom($cut, 1)
|
||||
let range = selAfter && selAfter.$from.blockRange(selAfter.$to), target = range && liftTarget(range)
|
||||
if (target != null && target >= $cut.depth) {
|
||||
if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
if (canDelAfter && textblockAt(after, "start", true) && textblockAt(before, "end")) {
|
||||
let at = before, wrap = []
|
||||
for (;;) {
|
||||
wrap.push(at)
|
||||
if (at.isTextblock) break
|
||||
at = at.lastChild!
|
||||
}
|
||||
let afterText = after, afterDepth = 1
|
||||
for (; !afterText.isTextblock; afterText = afterText.firstChild!) afterDepth++
|
||||
if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
|
||||
if (dispatch) {
|
||||
let end = Fragment.empty
|
||||
for (let i = wrap.length - 1; i >= 0; i--) end = Fragment.from(wrap[i].copy(end))
|
||||
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - wrap.length, $cut.pos + after.nodeSize,
|
||||
$cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth,
|
||||
new Slice(end, wrap.length, 0), 0, true))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function selectTextblockSide(side: number): Command {
|
||||
return function(state, dispatch) {
|
||||
let sel = state.selection, $pos = side < 0 ? sel.$from : sel.$to
|
||||
let depth = $pos.depth
|
||||
while ($pos.node(depth).isInline) {
|
||||
if (!depth) return false
|
||||
depth--
|
||||
}
|
||||
if (!$pos.node(depth).isTextblock) return false
|
||||
if (dispatch)
|
||||
dispatch(state.tr.setSelection(TextSelection.create(
|
||||
state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the cursor to the start of current text block.
|
||||
export const selectTextblockStart = selectTextblockSide(-1)
|
||||
|
||||
/// Moves the cursor to the end of current text block.
|
||||
export const selectTextblockEnd = selectTextblockSide(1)
|
||||
|
||||
// Parameterized commands
|
||||
|
||||
/// Wrap the selection in a node of the given type with the given
|
||||
/// attributes.
|
||||
export function wrapIn(nodeType: NodeType, attrs: Attrs | null = null): Command {
|
||||
return function(state, dispatch) {
|
||||
let {$from, $to} = state.selection
|
||||
let range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs)
|
||||
if (!wrapping) return false
|
||||
if (dispatch) dispatch(state.tr.wrap(range!, wrapping).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a command that tries to set the selected textblocks to the
|
||||
/// given node type with the given attributes.
|
||||
export function setBlockType(nodeType: NodeType, attrs: Attrs | null = null): Command {
|
||||
return function(state, dispatch) {
|
||||
let applicable = false
|
||||
for (let i = 0; i < state.selection.ranges.length && !applicable; i++) {
|
||||
let {$from: {pos: from}, $to: {pos: to}} = state.selection.ranges[i]
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (applicable) return false
|
||||
if (!node.isTextblock || node.hasMarkup(nodeType, attrs)) return
|
||||
if (node.type == nodeType) {
|
||||
applicable = true
|
||||
} else {
|
||||
let $pos = state.doc.resolve(pos), index = $pos.index()
|
||||
applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!applicable) return false
|
||||
if (dispatch) {
|
||||
let tr = state.tr
|
||||
for (let i = 0; i < state.selection.ranges.length; i++) {
|
||||
let {$from: {pos: from}, $to: {pos: to}} = state.selection.ranges[i]
|
||||
tr.setBlockType(from, to, nodeType, attrs)
|
||||
}
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function markApplies(doc: Node, ranges: readonly SelectionRange[], type: MarkType, enterAtoms: boolean) {
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let {$from, $to} = ranges[i]
|
||||
let can = $from.depth == 0 ? doc.inlineContent && doc.type.allowsMarkType(type) : false
|
||||
doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
|
||||
if (can || !enterAtoms && node.isAtom && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos)
|
||||
return false
|
||||
can = node.inlineContent && node.type.allowsMarkType(type)
|
||||
})
|
||||
if (can) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function removeInlineAtoms(ranges: readonly SelectionRange[]): readonly SelectionRange[] {
|
||||
let result = []
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let {$from, $to} = ranges[i]
|
||||
$from.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
|
||||
if (node.isAtom && node.content.size && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos) {
|
||||
if (pos + 1 > $from.pos) result.push(new SelectionRange($from, $from.doc.resolve(pos + 1)))
|
||||
$from = $from.doc.resolve(pos + 1 + node.content.size)
|
||||
return false
|
||||
}
|
||||
})
|
||||
if ($from.pos < $to.pos) result.push(new SelectionRange($from, $to))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// Create a command function that toggles the given mark with the
|
||||
/// given attributes. Will return `false` when the current selection
|
||||
/// doesn't support that mark. This will remove the mark if any marks
|
||||
/// of that type exist in the selection, or add it otherwise. If the
|
||||
/// selection is empty, this applies to the [stored
|
||||
/// marks](#state.EditorState.storedMarks) instead of a range of the
|
||||
/// document.
|
||||
export function toggleMark(markType: MarkType, attrs: Attrs | null = null, options?: {
|
||||
/// Controls whether, when part of the selected range has the mark
|
||||
/// already and part doesn't, the mark is removed (`true`, the
|
||||
/// default) or added (`false`).
|
||||
removeWhenPresent?: boolean
|
||||
/// When set to false, this will prevent the command from acting on
|
||||
/// the content of inline nodes marked as
|
||||
/// [atoms](#model.NodeSpec.atom) that are completely covered by a
|
||||
/// selection range.
|
||||
enterInlineAtoms?: boolean
|
||||
/// By default, this command doesn't apply to leading and trailing
|
||||
/// whitespace in the selection. Set this to `true` to change that.
|
||||
includeWhitespace?: boolean
|
||||
}): Command {
|
||||
let removeWhenPresent = (options && options.removeWhenPresent) !== false
|
||||
let enterAtoms = (options && options.enterInlineAtoms) !== false
|
||||
let dropSpace = !(options && options.includeWhitespace)
|
||||
return function(state, dispatch) {
|
||||
let {empty, $cursor, ranges} = state.selection as TextSelection
|
||||
if ((empty && !$cursor) || !markApplies(state.doc, ranges, markType, enterAtoms)) return false
|
||||
if (dispatch) {
|
||||
if ($cursor) {
|
||||
if (markType.isInSet(state.storedMarks || $cursor.marks()))
|
||||
dispatch(state.tr.removeStoredMark(markType))
|
||||
else
|
||||
dispatch(state.tr.addStoredMark(markType.create(attrs)))
|
||||
} else {
|
||||
let add, tr = state.tr
|
||||
if (!enterAtoms) ranges = removeInlineAtoms(ranges)
|
||||
if (removeWhenPresent) {
|
||||
add = !ranges.some(r => state.doc.rangeHasMark(r.$from.pos, r.$to.pos, markType))
|
||||
} else {
|
||||
add = !ranges.every(r => {
|
||||
let missing = false
|
||||
tr.doc.nodesBetween(r.$from.pos, r.$to.pos, (node, pos, parent) => {
|
||||
if (missing) return false
|
||||
missing = !markType.isInSet(node.marks) && !!parent && parent.type.allowsMarkType(markType) &&
|
||||
!(node.isText && /^\s*$/.test(node.textBetween(Math.max(0, r.$from.pos - pos),
|
||||
Math.min(node.nodeSize, r.$to.pos - pos))))
|
||||
})
|
||||
return !missing
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let {$from, $to} = ranges[i]
|
||||
if (!add) {
|
||||
tr.removeMark($from.pos, $to.pos, markType)
|
||||
} else {
|
||||
let from = $from.pos, to = $to.pos, start = $from.nodeAfter, end = $to.nodeBefore
|
||||
let spaceStart = dropSpace && start && start.isText ? /^\s*/.exec(start.text!)![0].length : 0
|
||||
let spaceEnd = dropSpace && end && end.isText ? /\s*$/.exec(end.text!)![0].length : 0
|
||||
if (from + spaceStart < to) { from += spaceStart; to -= spaceEnd }
|
||||
tr.addMark(from, to, markType.create(attrs))
|
||||
}
|
||||
}
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function wrapDispatchForJoin(dispatch: (tr: Transaction) => void, isJoinable: (a: Node, b: Node) => boolean) {
|
||||
return (tr: Transaction) => {
|
||||
if (!tr.isGeneric) return dispatch(tr)
|
||||
|
||||
let ranges: number[] = []
|
||||
for (let i = 0; i < tr.mapping.maps.length; i++) {
|
||||
let map = tr.mapping.maps[i]
|
||||
for (let j = 0; j < ranges.length; j++)
|
||||
ranges[j] = map.map(ranges[j])
|
||||
map.forEach((_s, _e, from, to) => ranges.push(from, to))
|
||||
}
|
||||
|
||||
// Figure out which joinable points exist inside those ranges,
|
||||
// by checking all node boundaries in their parent nodes.
|
||||
let joinable = []
|
||||
for (let i = 0; i < ranges.length; i += 2) {
|
||||
let from = ranges[i], to = ranges[i + 1]
|
||||
let $from = tr.doc.resolve(from), depth = $from.sharedDepth(to), parent = $from.node(depth)
|
||||
for (let index = $from.indexAfter(depth), pos = $from.after(depth + 1); pos <= to; ++index) {
|
||||
let after = parent.maybeChild(index)
|
||||
if (!after) break
|
||||
if (index && joinable.indexOf(pos) == -1) {
|
||||
let before = parent.child(index - 1)
|
||||
if (before.type == after.type && isJoinable(before, after))
|
||||
joinable.push(pos)
|
||||
}
|
||||
pos += after.nodeSize
|
||||
}
|
||||
}
|
||||
// Join the joinable points
|
||||
joinable.sort((a, b) => a - b)
|
||||
for (let i = joinable.length - 1; i >= 0; i--) {
|
||||
if (canJoin(tr.doc, joinable[i])) tr.join(joinable[i])
|
||||
}
|
||||
dispatch(tr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a command so that, when it produces a transform that causes
|
||||
/// two joinable nodes to end up next to each other, those are joined.
|
||||
/// Nodes are considered joinable when they are of the same type and
|
||||
/// when the `isJoinable` predicate returns true for them or, if an
|
||||
/// array of strings was passed, if their node type name is in that
|
||||
/// array.
|
||||
export function autoJoin(
|
||||
command: Command,
|
||||
isJoinable: ((before: Node, after: Node) => boolean) | readonly string[]
|
||||
): Command {
|
||||
let canJoin = Array.isArray(isJoinable) ? (node: Node) => isJoinable.indexOf(node.type.name) > -1
|
||||
: isJoinable as (a: Node, b: Node) => boolean
|
||||
return (state, dispatch, view) => command(state, dispatch && wrapDispatchForJoin(dispatch, canJoin), view)
|
||||
}
|
||||
|
||||
/// Combine a number of command functions into a single function (which
|
||||
/// calls them one by one until one returns true).
|
||||
export function chainCommands(...commands: readonly Command[]): Command {
|
||||
return function(state, dispatch, view) {
|
||||
for (let i = 0; i < commands.length; i++)
|
||||
if (commands[i](state, dispatch, view)) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward)
|
||||
let del = chainCommands(deleteSelection, joinForward, selectNodeForward)
|
||||
|
||||
/// A basic keymap containing bindings not specific to any schema.
|
||||
/// Binds the following keys (when multiple commands are listed, they
|
||||
/// are chained with [`chainCommands`](#commands.chainCommands)):
|
||||
///
|
||||
/// * **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock`
|
||||
/// * **Mod-Enter** to `exitCode`
|
||||
/// * **Backspace** and **Mod-Backspace** to `deleteSelection`, `joinBackward`, `selectNodeBackward`
|
||||
/// * **Delete** and **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
/// * **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
|
||||
/// * **Mod-a** to `selectAll`
|
||||
export const pcBaseKeymap: {[key: string]: Command} = {
|
||||
"Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock),
|
||||
"Mod-Enter": exitCode,
|
||||
"Backspace": backspace,
|
||||
"Mod-Backspace": backspace,
|
||||
"Shift-Backspace": backspace,
|
||||
"Delete": del,
|
||||
"Mod-Delete": del,
|
||||
"Mod-a": selectAll
|
||||
}
|
||||
|
||||
/// A copy of `pcBaseKeymap` that also binds **Ctrl-h** like Backspace,
|
||||
/// **Ctrl-d** like Delete, **Alt-Backspace** like Ctrl-Backspace, and
|
||||
/// **Ctrl-Alt-Backspace**, **Alt-Delete**, and **Alt-d** like
|
||||
/// Ctrl-Delete.
|
||||
export const macBaseKeymap: {[key: string]: Command} = {
|
||||
"Ctrl-h": pcBaseKeymap["Backspace"],
|
||||
"Alt-Backspace": pcBaseKeymap["Mod-Backspace"],
|
||||
"Ctrl-d": pcBaseKeymap["Delete"],
|
||||
"Ctrl-Alt-Backspace": pcBaseKeymap["Mod-Delete"],
|
||||
"Alt-Delete": pcBaseKeymap["Mod-Delete"],
|
||||
"Alt-d": pcBaseKeymap["Mod-Delete"],
|
||||
"Ctrl-a": selectTextblockStart,
|
||||
"Ctrl-e": selectTextblockEnd
|
||||
}
|
||||
for (let key in pcBaseKeymap) (macBaseKeymap as any)[key] = pcBaseKeymap[key]
|
||||
|
||||
const mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform)
|
||||
// @ts-ignore
|
||||
: typeof os != "undefined" && os.platform ? os.platform() == "darwin" : false
|
||||
|
||||
/// Depending on the detected platform, this will hold
|
||||
/// [`pcBasekeymap`](#commands.pcBaseKeymap) or
|
||||
/// [`macBaseKeymap`](#commands.macBaseKeymap).
|
||||
export const baseKeymap: {[key: string]: Command} = mac ? macBaseKeymap : pcBaseKeymap
|
||||
Reference in New Issue
Block a user