aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/formattedText')
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss1
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx20
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts65
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx40
4 files changed, 61 insertions, 65 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 3dcc45c96..38dd2e847 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -350,6 +350,7 @@ footnote::before {
span {
font-family: inherit;
background-color: inherit;
+ display: contents; // fixes problem where extra space is added around <ol> lists when inside a prosemirror span
}
blockquote {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c2f3a6e4b..43010b2ed 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -8,7 +8,7 @@ import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
@@ -983,10 +983,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
animateRes = (resIndex: number, newText: string) => {
if (resIndex < newText.length) {
const marks = this._editorView?.state.storedMarks ?? [];
- this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks));
- setTimeout(() => {
- this.animateRes(resIndex + 1, newText);
- }, 20);
+ this._editorView?.dispatch(this._editorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
+ setTimeout(() => this.animateRes(resIndex + 1, newText), 20);
}
};
@@ -994,13 +992,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
try {
let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
if (!res) {
- console.error('GPT call failed');
this.animateRes(0, 'Something went wrong.');
- } else {
- this.animateRes(0, res);
+ } else if (this._editorView) {
+ const { dispatch, state } = this._editorView;
+ // for no animation, use: dispatch(state.tr.insertText(res));
+ // for animted response starting at end of text, use:
+ dispatch(state.tr.setSelection(Selection.atEnd(state.doc)));
+ this.animateRes(0, '\n\n' + res);
}
} catch (err) {
- console.error('GPT call failed');
+ console.error(err);
this.animateRes(0, 'Something went wrong.');
}
});
@@ -1484,6 +1485,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
}
}
if (selectOnLoad) {
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index ec8879487..03c902580 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -257,7 +257,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
- const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
+ const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
@@ -269,7 +269,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (
!joinBackward(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
- if (once && view.state.selection.$from.depth > 1 && view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === view.state.schema.nodes.list_item) backspace(view.state, view.dispatch, view, false);
+ 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 (
@@ -296,7 +299,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const depth = trange ? liftTarget(trange) : null;
if (
depth !== null &&
- state.selection.$from.node(state.selection.$from.depth - 1)?.type === state.schema.nodes.blockquote && //
+ state.selection.$from.node(-1)?.type === state.schema.nodes.blockquote && //
!state.selection.$from.node().content.size &&
trange
) {
@@ -306,7 +309,13 @@ 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 (!newlineInCode(state, dispatch as any)) {
- if (once && view.state.selection.$from.depth > 1 && !view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) {
+ 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) => {
@@ -314,32 +323,32 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
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);
- dispatch(tx4);
- if (
- view.state.selection.$from.parentOffset && //
- !view.state.selection.$from.node().content.size
- )
- liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
- else if (
- once &&
- view.state.selection.$from.parentOffset &&
- view.state.selection.$from.depth > 1 && //
- view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === schema.nodes.list_item
- )
- enter(view.state, view.dispatch, view, false);
- else if (once && depth && !view.state.selection.$from.parentOffset) backspace(view.state, view.dispatch, view, false);
- } else dispatch(tx3.insertText('\r\n'));
- })
- ) {
- return false;
+ 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);
+ }
+
+ 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;
+ }
}
}
}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 4bd4ca72b..cecf106a3 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -404,39 +404,23 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// remove all node type and apply the passed-in one to the selected text
changeListType = (mapStyle: string) => {
const active = this.view?.state && RichTextMenu.Instance?.getActiveListStyle();
- const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle });
- if (!this.view || nodeType?.attrs.mapStyle === '') return;
-
- const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
- let inList: any = undefined;
- let fromList = -1;
- const path: any = Array.from((this.view.state.selection.$from as any).path);
- for (let i = 0; i < path.length; i++) {
- if (path[i]?.type === schema.nodes.ordered_list) {
- inList = path[i];
- fromList = path[i - 1];
- }
- }
+ const newMapStyle = active === mapStyle ? '' : mapStyle;
+ if (!this.view || newMapStyle === '') return;
+ let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
- if (
- inList ||
+ if (inList) {
+ const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
+ marks && tx2.ensureMarks([...marks]);
+ marks && tx2.setStoredMarks([...marks]);
+ this.view.dispatch(tx2);
+ } else
!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
-
- this.view!.dispatch(tx2);
- })
- ) {
- const tx2 = this.view.state.tr;
- if (nodeType && (inList || nextIsOL)) {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to);
+ const tx3 = updateBullets(tx2, schema, newMapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
- this.view.dispatch(tx3);
- }
- }
+ this.view!.dispatch(tx3);
+ });
this.view.focus();
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};