diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts')
| -rw-r--r-- | src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | 208 | 
1 files changed, 114 insertions, 94 deletions
| diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 3c84e5a10..eabc6455f 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -1,29 +1,30 @@  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 { MarkType, Node, Schema } from 'prosemirror-model';  import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list'; -import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state'; +import { Command, EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';  import { liftTarget } from 'prosemirror-transform';  import { EditorView } from 'prosemirror-view';  import { ClientUtils } from '../../../../ClientUtils'; -import { Utils } from '../../../../Utils'; +import { numberRange, Utils } from '../../../../Utils';  import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';  import { GetEffectiveAcl } from '../../../../fields/util';  import { Docs } from '../../../documents/Documents';  import { RTFMarkup } from '../../../util/RTFMarkup';  import { DocumentView } from '../DocumentView';  import { OpenWhere } from '../OpenWhere'; +import { FormattedTextBox } from './FormattedTextBox';  const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; -export type KeyMap = { [key: string]: any }; +export type KeyMap = { [key: string]: Command }; -export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => { +export function updateBullets(tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) {      let mapStyle = assignedMapStyle; -    tx2.doc.descendants((node: any, offset: any /* , index: any */) => { +    tx2.doc.descendants((node: Node, offset: number /* , index: any */) => {          if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) { -            const { path } = tx2.doc.resolve(offset) as any; -            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0); +            const resolved = tx2.doc.resolve(offset); +            let depth = [0, ...numberRange(resolved.depth)].reduce((p, c, idx) => p + (resolved.node(idx).type === schema.nodes.ordered_list ? 1 : 0), 0);              if (node.type === schema.nodes.ordered_list) {                  if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;                  depth++; @@ -32,28 +33,30 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle          }      });      return tx2; -}; +} -export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap { -    const keys: { [key: string]: any } = {}; +export function buildKeymap<S extends Schema<string>>(schema: S, tbox?: FormattedTextBox): KeyMap { +    const keys: { [key: string]: Command } = {}; -    function bind(key: string, cmd: any) { +    function bind(key: string, cmd: Command) {          keys[key] = cmd;      }      function onKey(): boolean | undefined { -        // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway +        // bcz: hack -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway          // eslint-disable-next-line no-restricted-globals -        return props.onKey?.(event, props); +        return event && tbox?._props.onKey?.(event as unknown as KeyboardEvent, tbox);      } -    const canEdit = (state: any) => { -        const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]); +    const canEdit = (state: EditorState) => { +        if (!tbox) return true; +        const permissions = GetEffectiveAcl(tbox._props.TemplateDataDocument ?? tbox.Document[DocData]);          switch (permissions) {              case AclAugment:                  { -                    const prevNode = state.selection.$cursor.nodeBefore; -                    const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid; +                    // previously used hack: (state.selection as any).$cursor.nodeBefore; +                    const prevNode = state.selection?.$anchor.nodeBefore; +                    const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : Array.from(prevNode.marks).lastElement()?.attrs.userid;                      if (prevUser !== ClientUtils.CurrentUserEmail()) {                          return false;                      } @@ -64,7 +67,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa          return true;      }; -    const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch); +    const toggleEditableMark = (mark: MarkType) => (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && toggleMark(mark)(state, dispatch);      // History commands      bind('Mod-z', undo); @@ -84,13 +87,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa      bind('Mod-U', toggleEditableMark(schema.marks.underline));      // Commands for lists -    bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); +    bind('Ctrl-i', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch));      bind('Ctrl-Tab', () => onKey() || true);      bind('Alt-Tab', () => onKey() || true);      bind('Meta-Tab', () => onKey() || true);      bind('Meta-Enter', () => onKey() || true); -    bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { +    bind('Tab', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {          if (onKey()) return true;          if (!canEdit(state)) return true;          const ref = state.selection; @@ -101,13 +104,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa                  const tx3 = updateBullets(tx2, schema);                  marks && tx3.ensureMarks([...marks]);                  marks && tx3.setStoredMarks([...marks]); -                dispatch(tx3); +                dispatch?.(tx3);              })          ) {              // couldn't sink into an existing list, so wrap in a new one              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) => { +                !wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {                      const tx25 = updateBullets(tx2, schema);                      const olNode = tx25.doc.nodeAt(range!.start)!;                      const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks); @@ -115,16 +118,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa                      marks && tx3.ensureMarks([...marks]);                      marks && tx3.setStoredMarks([...marks]);                      const tx4 = tx3.setSelection(TextSelection.near(tx3.doc.resolve(state.selection.to + 2))); -                    dispatch(tx4); +                    dispatch?.(tx4);                  })              ) {                  console.log('bullet promote fail');              }          } -        return undefined; +        return false;      }); -    bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { +    bind('Shift-Tab', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {          if (onKey()) return true;          if (!canEdit(state)) return true;          const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); @@ -134,119 +137,136 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa                  const tx3 = updateBullets(tx2, schema);                  marks && tx3.ensureMarks([...marks]);                  marks && tx3.setStoredMarks([...marks]); -                dispatch(tx3); +                dispatch?.(tx3);              })          ) {              console.log('bullet demote fail');          } -        return undefined; +        return false;      });      // Command to create a new Tab with a PDF of all the command shortcuts -    bind('Mod-/', () => { +    // eslint-disable-next-line @typescript-eslint/no-unused-vars +    bind('Mod-/', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {          const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); -        props.addDocTab(newDoc, OpenWhere.addRight); +        tbox?._props.addDocTab(newDoc, OpenWhere.addRight); +        return false;      });      // Commands to modify BlockType -    bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any))); -    bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any)); -    bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any)); +    bind('Ctrl->', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && wrapIn(schema.nodes.blockquote)(state, dispatch)); +    bind('Alt-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch)); +    bind('Shift-Ctrl-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch)); -    bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => { +    bind('Ctrl-m', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {          if (canEdit(state)) {              const tr = state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() })); -            dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1)))); +            dispatch?.(tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1)))); +            return true;          } +        return false;      });      for (let i = 1; i <= 6; i++) { -        bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any)); +        bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch));      }      // Command to create a horizontal break line      const hr = schema.nodes.horizontal_rule; -    bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())); +    bind('Mod-_', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { +        if (canEdit(state)) { +            dispatch?.(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); +            return true; +        } +        return false; +    });      // Command to unselect all -    bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => { -        dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); -        (document.activeElement as any).blur?.(); +    bind('Escape', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { +        dispatch?.(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); +        (document.activeElement as HTMLElement)?.blur?.();          DocumentView.DeselectAll(); +        return true;      });      bind('Alt-Enter', () => onKey() || true);      bind('Ctrl-Enter', () => onKey() || true); -    bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => { -        dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1)))); +    bind('Cmd-a', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { +        dispatch?.(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));          return true;      });      bind('Cmd-?', () => {          RTFMarkup.Instance.setOpen(true);          return true;      }); -    bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => { +    bind('Cmd-e', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {          if (!state.selection.empty) {              const mark = state.schema.marks.summarizeInclusive.create();              const tr = state.tr.addMark(state.selection.$from.pos, state.selection.$to.pos, mark);              const content = tr.selection.content(); -            tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() })); -            dispatch(tr); +            tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }, undefined, state.selection.$anchor.marks() ?? [])); +            dispatch?.(tr);          }          return true;      }); -    bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => { -        const resolved = state.doc.resolve(state.selection.from) as any; -        const { tr } = state; -        if (resolved?.parent.type.name === 'paragraph') { -            tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); +    bind('Cmd-]', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { +        const { +            tr, +            selection: { $from }, +        } = state; +        if ($from?.parent.type.name === 'paragraph') { +            tr.setNodeMarkup(state.selection.from - state.selection.$from.parentOffset - 1, schema.nodes.paragraph, { ...$from.parent.attrs, align: 'right' }, $from.parent.marks);          } else { -            const node = resolved.nodeAfter; +            const node = $from.nodeAfter;              const sm = state.storedMarks || undefined;              if (node) {                  tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]);              }          } -        dispatch(tr); +        dispatch?.(tr);          return true;      }); -    bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => { -        const resolved = state.doc.resolve(state.selection.from) as any; -        const { tr } = state; -        if (resolved?.parent.type.name === 'paragraph') { -            tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); +    bind('Cmd-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { +        const { +            tr, +            selection: { $from }, +        } = state; +        if ($from?.parent.type.name === 'paragraph') { +            tr.setNodeMarkup(state.selection.from - state.selection.$from.parentOffset - 1, schema.nodes.paragraph, { ...$from.parent.attrs, align: 'center' }, $from.parent.marks);          } else { -            const node = resolved.nodeAfter; +            const node = $from.nodeAfter;              const sm = state.storedMarks || undefined;              if (node) {                  tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]);              }          } -        dispatch(tr); +        dispatch?.(tr);          return true;      }); -    bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => { -        const resolved = state.doc.resolve(state.selection.from) as any; -        const { tr } = state; -        if (resolved?.parent.type.name === 'paragraph') { -            tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); +    bind('Cmd-[', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { +        const { +            tr, +            selection: { $from }, +        } = state; +        if ($from?.parent.type.name === 'paragraph') { +            tr.setNodeMarkup(state.selection.from - state.selection.$from.parentOffset - 1, schema.nodes.paragraph, { ...$from.parent.attrs, align: 'left' }, $from.parent.marks);          } else { -            const node = resolved.nodeAfter; +            const node = $from.nodeAfter;              const sm = state.storedMarks || undefined;              if (node) {                  tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]);              }          } -        dispatch(tr); +        dispatch?.(tr);          return true;      }); -    bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => { +    bind('Cmd-f', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {          const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);          const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);          const { tr } = state;          tr.replaceSelectionWith(newNode); // replace insertion with a footnote. -        dispatch( +        dispatch?.(              tr.setSelection(                  new NodeSelection( // select the footnote node to open its display                      tr.doc.resolve( @@ -259,25 +279,25 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa          return true;      }); -    bind('Ctrl-a', (state: EditorState, dispatch: (tx: Transaction) => void) => { -        dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1)))); +    bind('Ctrl-a', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { +        dispatch?.(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));          return true;      });      // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); -    const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => { +    const backspace = (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined, view?: EditorView) => {          if (onKey()) return true;          if (!canEdit(state)) return true;          if (              !deleteSelection(state, (tx: Transaction) => { -                dispatch(updateBullets(tx, schema)); +                dispatch?.(updateBullets(tx, schema));              })          ) {              if (                  !joinBackward(state, (tx: Transaction) => { -                    dispatch(updateBullets(tx, schema)); -                    if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) { +                    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, (tx2: Transaction) => view.dispatch(tx2));                      } @@ -285,7 +305,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa              ) {                  if (                      !selectNodeBackward(state, (tx: Transaction) => { -                        dispatch(updateBullets(tx, schema)); +                        dispatch?.(updateBullets(tx, schema));                      })                  ) {                      return false; @@ -299,7 +319,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa      // newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock      // command to break line -    const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => { +    const enter = (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined, view?: EditorView, once = true) => {          if (onKey()) return true;          if (!canEdit(state)) return true; @@ -311,31 +331,31 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa              !state.selection.$from.node().content.size &&              trange          ) { -            dispatch(state.tr.lift(trange, depth) as any); +            dispatch?.(state.tr.lift(trange, depth));              return true;          }          const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); -        if (!newlineInCode(state, dispatch as any)) { -            const olNode = view.state.selection.$anchor.node(-2); -            const liNode = view.state.selection.$anchor.node(-1); +        if (!newlineInCode(state, dispatch)) { +            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)  +                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++); +                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) => { +                !splitListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {                      const tx3 = updateBullets(tx2, schema);                      marks && tx3.ensureMarks([...marks]);                      marks && tx3.setStoredMarks([...marks]); -                    dispatch(tx3); +                    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)); +                    !once && view?.dispatch(view?.state.tr.deleteRange(view.state.selection.from - 5, view.state.selection.from - 2));                  })              ) { -                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 === '') { +                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); @@ -346,12 +366,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa                              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).setStoredMarks(marks || []); -                                dispatch(tx4); +                                dispatch?.(tx4);                              } -                            if (view.state.selection.$anchor.depth > 0 && -                                view.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item && -                                view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) { +                            if ((view?.state.selection.$anchor.depth ??0) > 0 && +                                view?.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item && +                                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);                              } @@ -368,14 +388,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa      // Command to create a blank space      bind('Space', () => { -        const editDoc = props.TemplateDataDocument ?? props.Document[DocData]; +        const editDoc = tbox?._props.TemplateDataDocument ?? tbox?.Document[DocData];          if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;          return false;      }); -    bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any)); -    bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any)); -    bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any)); +    bind('Alt-ArrowUp', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && joinUp(state, dispatch)); +    bind('Alt-ArrowDown', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && joinDown(state, dispatch)); +    bind('Mod-BracketLeft', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && lift(state, dispatch));      const cmd = chainCommands(exitCode, (state, dispatch) => {          if (dispatch) { | 
