aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-05-03 10:23:46 -0400
committerbobzel <zzzman@gmail.com>2024-05-03 10:23:46 -0400
commit723c8b33ade753764d1d02b130c189fb65e20425 (patch)
treee278304fdace45a2c38562e72a3ccd2e8e91b759 /src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
parentf410a7b314dd78244e18c9c52140b67b37ab0c87 (diff)
parent2caf7b7bb80b663b6ba585f88cdbd2d725f8505e (diff)
Merge branch 'master' into nathan-starter
Diffstat (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts')
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts96
1 files changed, 59 insertions, 37 deletions
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 47527847b..03c902580 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,7 +1,7 @@
import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
import { redo, undo } from 'prosemirror-history';
import { Schema } from 'prosemirror-model';
-import { splitListItem, wrapInList } from 'prosemirror-schema-list';
+import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
@@ -11,8 +11,8 @@ import { Docs } from '../../../documents/Documents';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { OpenWhere } from '../DocumentView';
-import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
import { Doc } from '../../../../fields/Doc';
+import { EditorView } from 'prosemirror-view';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -89,7 +89,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
- const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ const marks = state.storedMarks || state.selection.$to.parentOffset ? state.selection.$from.marks() : undefined;
if (
!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
@@ -102,11 +102,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
if (
!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
+ const tx25 = updateBullets(tx2, schema);
+ const ol_node = tx25.doc.nodeAt(range!.start)!;
+ const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
// when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
+ const tx4 = tx3.setSelection(TextSelection.near(tx3.doc.resolve(state.selection.to + 2)));
+ dispatch(tx4);
})
) {
console.log('bullet promote fail');
@@ -120,7 +123,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (
- !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+ !liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -164,12 +167,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
SelectionManager.DeselectAll();
});
- const splitMetadata = (marks: any, tx: Transaction) => {
- marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- return tx;
- };
-
bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
@@ -260,7 +257,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
- bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
@@ -272,6 +269,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (
!joinBackward(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
+ if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
+ // gets rid of an extra paragraph when joining two list items together.
+ joinBackward(view.state, (tx: Transaction) => view.dispatch(tx));
+ }
})
) {
if (
@@ -284,59 +285,80 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
}
}
return true;
- });
+ };
+ bind('Backspace', backspace);
//newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
//command to break line
- bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+
+ const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const trange = state.selection.$from.blockRange(state.selection.$to);
- const path = (state.selection.$from as any).path;
- const depth = trange ? liftTarget(trange) : undefined;
- const split = path.length > 5 && !path[path.length - 3].textContent && path[path.length - 6].type !== schema.nodes.list_item;
- if (split && trange && depth !== undefined && depth !== null) {
+ const depth = trange ? liftTarget(trange) : null;
+ if (
+ depth !== null &&
+ state.selection.$from.node(-1)?.type === state.schema.nodes.blockquote && //
+ !state.selection.$from.node().content.size &&
+ trange
+ ) {
dispatch(state.tr.lift(trange, depth) as any);
return true;
}
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- const cr = state.selection.$from.node().textContent.endsWith('\n');
- if (/*cr ||*/ !newlineInCode(state, dispatch as any)) {
- if (
+ if (!newlineInCode(state, dispatch as any)) {
+ const olNode = view.state.selection.$anchor.node(-2);
+ const liNode = view.state.selection.$anchor.node(-1);
+ // prettier-ignore
+ if (liNode?.type === schema.nodes.list_item && !liNode.textContent &&
+ olNode?.type === schema.nodes.ordered_list && once && view.state.selection.$from.depth === 3)
+ {
+ // handles case of hitting enter at then end of a top-level empty list item - the result is to create a paragraph
+ for (let i = 0; i < 10 && view.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++);
+ } else if (
!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
dispatch(tx3);
+ // removes an extra paragraph created when selecting text across two list items or splitting an empty list item
+ !once && view.dispatch(view.state.tr.deleteRange(view.state.selection.from - 5, view.state.selection.from - 2));
})
) {
- const fromattrs = state.selection.$from.node().attrs;
- if (
- !splitBlockKeepMarks(state, (tx3: Transaction) => {
- const tonode = tx3.selection.$to.node();
- if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
- const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
- splitMetadata(marks, tx4);
- if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) {
+ if (once && view.state.selection.$from.node(-2)?.type === schema.nodes.ordered_list && view.state.selection.$from.node(-1)?.type === schema.nodes.list_item && view.state.selection.$from.node(-1)?.textContent === '') {
+ // handles case of hitting enter on an empty list item which needs to create a second empty paragraph, then split it by calling enter() again
+ view.dispatch(view.state.tr.insert(view.state.selection.from, schema.nodes.paragraph.create({})));
+ enter(view.state, view.dispatch, view, false);
+ } else {
+ const fromattrs = state.selection.$from.node().attrs;
+ if (
+ !splitBlockKeepMarks(state, (tx3: Transaction) => {
+ const tonode = tx3.selection.$to.node();
+ if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
+ const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
dispatch(tx4);
}
- } else dispatch(tx3.insertText('\r\n'));
- })
- ) {
- return false;
+
+ if (view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) {
+ // if text is selected across list items, then we need to forcibly insert a new line since the splitBlock code joins the two list items.
+ enter(view.state, dispatch, view, false);
+ }
+ })
+ ) {
+ return false;
+ }
}
}
}
return true;
- });
+ };
+ bind('Enter', enter);
//Command to create a blank space
bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
- const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- dispatch(splitMetadata(marks, state.tr));
return false;
});