Initial
This commit is contained in:
8
resources/app/node_modules/prosemirror-commands/.tern-project
generated
vendored
Normal file
8
resources/app/node_modules/prosemirror-commands/.tern-project
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"libs": ["browser"],
|
||||
"plugins": {
|
||||
"node": {},
|
||||
"complete_strings": {},
|
||||
"es_modules": {}
|
||||
}
|
||||
}
|
||||
104
resources/app/node_modules/prosemirror-commands/CONTRIBUTING.md
generated
vendored
Normal file
104
resources/app/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
resources/app/node_modules/prosemirror-commands/LICENSE
generated
vendored
Normal file
19
resources/app/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.
|
||||
796
resources/app/node_modules/prosemirror-commands/dist/index.cjs
generated
vendored
Normal file
796
resources/app/node_modules/prosemirror-commands/dist/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,796 @@
|
||||
'use strict';
|
||||
|
||||
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
value: true
|
||||
});
|
||||
|
||||
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 (!before.type.spec.isolating && deleteBarrier(state, $cut, dispatch)) return true;
|
||||
|
||||
if ($cursor.parent.content.size == 0 && (textblockAt(before, "end") || prosemirrorState.NodeSelection.isSelectable(before))) {
|
||||
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(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 (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)) 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.parent.isBlock) return false;
|
||||
|
||||
if (dispatch) {
|
||||
var atEnd = $to.parentOffset == $to.parent.content.size;
|
||||
var tr = state.tr;
|
||||
if (state.selection instanceof prosemirrorState.TextSelection || state.selection instanceof prosemirrorState.AllSelection) tr.deleteSelection();
|
||||
var deflt = $from.depth == 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)));
|
||||
var splitType = splitNode && splitNode($to.parent, atEnd);
|
||||
var types = splitType ? [splitType] : atEnd && deflt ? [{
|
||||
type: deflt
|
||||
}] : undefined;
|
||||
var can = prosemirrorTransform.canSplit(tr.doc, tr.mapping.map($from.pos), 1, types);
|
||||
|
||||
if (!types && !can && prosemirrorTransform.canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{
|
||||
type: deflt
|
||||
}] : undefined)) {
|
||||
if (deflt) types = [{
|
||||
type: deflt
|
||||
}];
|
||||
can = true;
|
||||
}
|
||||
|
||||
if (can) {
|
||||
tr.split(tr.mapping.map($from.pos), 1, types);
|
||||
|
||||
if (!atEnd && !$from.parentOffset && $from.parent.type != deflt) {
|
||||
var first = tr.mapping.map($from.before()),
|
||||
$first = tr.doc.resolve(first);
|
||||
if (deflt && $from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt)) tr.setNodeMarkup(tr.mapping.map($from.before()), deflt);
|
||||
}
|
||||
}
|
||||
|
||||
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.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount)).join($pos.pos).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
|
||||
function deleteBarrier(state, $cut, dispatch) {
|
||||
var before = $cut.nodeBefore,
|
||||
after = $cut.nodeAfter,
|
||||
conn,
|
||||
match;
|
||||
if (before.type.spec.isolating || after.type.spec.isolating) return false;
|
||||
if (joinMaybeClear(state, $cut, dispatch)) return true;
|
||||
var canDelAfter = $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 = end + 2 * conn.length;
|
||||
if (prosemirrorTransform.canJoin(tr.doc, joinAt)) tr.join(joinAt);
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var selAfter = 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) {
|
||||
var _loop = function _loop(i) {
|
||||
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) {
|
||||
if (can) return false;
|
||||
can = node.inlineContent && node.type.allowsMarkType(type);
|
||||
});
|
||||
if (can) return {
|
||||
v: true
|
||||
};
|
||||
};
|
||||
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var _ret = _loop(i);
|
||||
|
||||
if (_typeof(_ret) === "object") return _ret.v;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function toggleMark(markType) {
|
||||
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
||||
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)) 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 has = false,
|
||||
tr = state.tr;
|
||||
|
||||
for (var i = 0; !has && i < ranges.length; i++) {
|
||||
var _ranges$i2 = ranges[i],
|
||||
$from = _ranges$i2.$from,
|
||||
$to = _ranges$i2.$to;
|
||||
has = state.doc.rangeHasMark($from.pos, $to.pos, markType);
|
||||
}
|
||||
|
||||
for (var _i3 = 0; _i3 < ranges.length; _i3++) {
|
||||
var _ranges$_i = ranges[_i3],
|
||||
_$from = _ranges$_i.$from,
|
||||
_$to = _ranges$_i.$to;
|
||||
|
||||
if (has) {
|
||||
tr.removeMark(_$from.pos, _$to.pos, markType);
|
||||
} else {
|
||||
var from = _$from.pos,
|
||||
to = _$to.pos,
|
||||
start = _$from.nodeAfter,
|
||||
end = _$to.nodeBefore;
|
||||
var spaceStart = start && start.isText ? /^\s*/.exec(start.text)[0].length : 0;
|
||||
var spaceEnd = 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 _i4 = 0; _i4 < ranges.length; _i4 += 2) {
|
||||
var from = ranges[_i4],
|
||||
to = ranges[_i4 + 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 _i5 = joinable.length - 1; _i5 >= 0; _i5--) {
|
||||
if (prosemirrorTransform.canJoin(tr.doc, joinable[_i5])) tr.join(joinable[_i5]);
|
||||
}
|
||||
|
||||
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;
|
||||
196
resources/app/node_modules/prosemirror-commands/dist/index.d.cts
generated
vendored
Normal file
196
resources/app/node_modules/prosemirror-commands/dist/index.d.cts
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
import { Node, 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`]($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`]($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) => {
|
||||
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): 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 };
|
||||
801
resources/app/node_modules/prosemirror-commands/dist/index.js
generated
vendored
Normal file
801
resources/app/node_modules/prosemirror-commands/dist/index.js
generated
vendored
Normal file
@@ -0,0 +1,801 @@
|
||||
import { liftTarget, replaceStep, ReplaceStep, canJoin, joinPoint, canSplit, ReplaceAroundStep, findWrapping } from 'prosemirror-transform';
|
||||
import { Slice, Fragment } from 'prosemirror-model';
|
||||
import { NodeSelection, Selection, TextSelection, AllSelection } 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 (!before.type.spec.isolating && deleteBarrier(state, $cut, dispatch))
|
||||
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))) {
|
||||
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(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 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.
|
||||
*/
|
||||
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`]($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))
|
||||
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.parent.isBlock)
|
||||
return false;
|
||||
if (dispatch) {
|
||||
let atEnd = $to.parentOffset == $to.parent.content.size;
|
||||
let tr = state.tr;
|
||||
if (state.selection instanceof TextSelection || state.selection instanceof AllSelection)
|
||||
tr.deleteSelection();
|
||||
let deflt = $from.depth == 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)));
|
||||
let splitType = splitNode && splitNode($to.parent, atEnd);
|
||||
let types = splitType ? [splitType] : atEnd && deflt ? [{ type: deflt }] : undefined;
|
||||
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types);
|
||||
if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)) {
|
||||
if (deflt)
|
||||
types = [{ type: deflt }];
|
||||
can = true;
|
||||
}
|
||||
if (can) {
|
||||
tr.split(tr.mapping.map($from.pos), 1, types);
|
||||
if (!atEnd && !$from.parentOffset && $from.parent.type != deflt) {
|
||||
let first = tr.mapping.map($from.before()), $first = tr.doc.resolve(first);
|
||||
if (deflt && $from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt))
|
||||
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt);
|
||||
}
|
||||
}
|
||||
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
|
||||
.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount))
|
||||
.join($pos.pos)
|
||||
.scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
function deleteBarrier(state, $cut, dispatch) {
|
||||
let before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match;
|
||||
if (before.type.spec.isolating || after.type.spec.isolating)
|
||||
return false;
|
||||
if (joinMaybeClear(state, $cut, dispatch))
|
||||
return true;
|
||||
let canDelAfter = $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 = end + 2 * conn.length;
|
||||
if (canJoin(tr.doc, joinAt))
|
||||
tr.join(joinAt);
|
||||
dispatch(tr.scrollIntoView());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
let selAfter = 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) {
|
||||
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 => {
|
||||
if (can)
|
||||
return false;
|
||||
can = node.inlineContent && node.type.allowsMarkType(type);
|
||||
});
|
||||
if (can)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
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) {
|
||||
return function (state, dispatch) {
|
||||
let { empty, $cursor, ranges } = state.selection;
|
||||
if ((empty && !$cursor) || !markApplies(state.doc, ranges, markType))
|
||||
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 has = false, tr = state.tr;
|
||||
for (let i = 0; !has && i < ranges.length; i++) {
|
||||
let { $from, $to } = ranges[i];
|
||||
has = state.doc.rangeHasMark($from.pos, $to.pos, markType);
|
||||
}
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let { $from, $to } = ranges[i];
|
||||
if (has) {
|
||||
tr.removeMark($from.pos, $to.pos, markType);
|
||||
}
|
||||
else {
|
||||
let from = $from.pos, to = $to.pos, start = $from.nodeAfter, end = $to.nodeBefore;
|
||||
let spaceStart = start && start.isText ? /^\s*/.exec(start.text)[0].length : 0;
|
||||
let spaceEnd = 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 };
|
||||
35
resources/app/node_modules/prosemirror-commands/package.json
generated
vendored
Normal file
35
resources/app/node_modules/prosemirror-commands/package.json
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "prosemirror-commands",
|
||||
"version": "1.5.2",
|
||||
"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.0.0",
|
||||
"prosemirror-state": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@prosemirror/buildhelper": "^0.1.5",
|
||||
"prosemirror-test-builder": "^1.0.0"
|
||||
}
|
||||
}
|
||||
40
resources/app/node_modules/prosemirror-commands/src/README.md
generated
vendored
Normal file
40
resources/app/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
|
||||
725
resources/app/node_modules/prosemirror-commands/src/commands.ts
generated
vendored
Normal file
725
resources/app/node_modules/prosemirror-commands/src/commands.ts
generated
vendored
Normal file
@@ -0,0 +1,725 @@
|
||||
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 (!before.type.spec.isolating && deleteBarrier(state, $cut, dispatch))
|
||||
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))) {
|
||||
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(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 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)) 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) => {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.parent.isBlock) return false
|
||||
|
||||
if (dispatch) {
|
||||
let atEnd = $to.parentOffset == $to.parent.content.size
|
||||
let tr = state.tr
|
||||
if (state.selection instanceof TextSelection || state.selection instanceof AllSelection) tr.deleteSelection()
|
||||
let deflt = $from.depth == 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
|
||||
let splitType = splitNode && splitNode($to.parent, atEnd)
|
||||
let types = splitType ? [splitType] : atEnd && deflt ? [{type: deflt}] : undefined
|
||||
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
|
||||
if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{type: deflt}] : undefined)) {
|
||||
if (deflt) types = [{type: deflt}]
|
||||
can = true
|
||||
}
|
||||
if (can) {
|
||||
tr.split(tr.mapping.map($from.pos), 1, types)
|
||||
if (!atEnd && !$from.parentOffset && $from.parent.type != deflt) {
|
||||
let first = tr.mapping.map($from.before()), $first = tr.doc.resolve(first)
|
||||
if (deflt && $from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt))
|
||||
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt)
|
||||
}
|
||||
}
|
||||
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
|
||||
.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount))
|
||||
.join($pos.pos)
|
||||
.scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
function deleteBarrier(state: EditorState, $cut: ResolvedPos, dispatch: ((tr: Transaction) => void) | undefined) {
|
||||
let before = $cut.nodeBefore!, after = $cut.nodeAfter!, conn, match
|
||||
if (before.type.spec.isolating || after.type.spec.isolating) return false
|
||||
if (joinMaybeClear(state, $cut, dispatch)) return true
|
||||
|
||||
let canDelAfter = $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 = end + 2 * conn.length
|
||||
if (canJoin(tr.doc, joinAt)) tr.join(joinAt)
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
let selAfter = 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) {
|
||||
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 => {
|
||||
if (can) return false
|
||||
can = node.inlineContent && node.type.allowsMarkType(type)
|
||||
})
|
||||
if (can) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// 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): Command {
|
||||
return function(state, dispatch) {
|
||||
let {empty, $cursor, ranges} = state.selection as TextSelection
|
||||
if ((empty && !$cursor) || !markApplies(state.doc, ranges, markType)) 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 has = false, tr = state.tr
|
||||
for (let i = 0; !has && i < ranges.length; i++) {
|
||||
let {$from, $to} = ranges[i]
|
||||
has = state.doc.rangeHasMark($from.pos, $to.pos, markType)
|
||||
}
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let {$from, $to} = ranges[i]
|
||||
if (has) {
|
||||
tr.removeMark($from.pos, $to.pos, markType)
|
||||
} else {
|
||||
let from = $from.pos, to = $to.pos, start = $from.nodeAfter, end = $to.nodeBefore
|
||||
let spaceStart = start && start.isText ? /^\s*/.exec(start.text!)![0].length : 0
|
||||
let spaceEnd = 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