From 705975eb43e7904c62e7e847478f6d0dac60d443 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 23 Mar 2025 18:14:31 -0400 Subject: more _props.Document to .Document refactoring. type updates to prosemirrortransfer --- .../formattedText/ProsemirrorExampleTransfer.ts | 174 +++++++++++---------- 1 file changed, 93 insertions(+), 81 deletions(-) (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts') diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 3c84e5a10..a75efd77b 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -1,8 +1,8 @@ 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, ResolvedPos, 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'; @@ -13,17 +13,18 @@ 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) => { 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 { path } = tx2.doc.resolve(offset) as unknown as { path: (number | Node)[] }; // bcz: can't access path .. need to FIX + let depth = Array.from(path).reduce((p: number, c: number | Node) => p + ((c as Node).type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) { if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle; depth++; @@ -34,25 +35,25 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle return tx2; }; -export function buildKeymap>(schema: S, props: any): KeyMap { - const keys: { [key: string]: any } = {}; +export function buildKeymap>(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 - // eslint-disable-next-line no-restricted-globals - return props.onKey?.(event, props); + return tbox?._props.onKey?.(event, 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 prevNode = (state.selection as any).$cursor.nodeBefore; const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid; if (prevUser !== ClientUtils.CurrentUserEmail()) { return false; @@ -64,7 +65,7 @@ export function buildKeymap>(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 +85,13 @@ export function buildKeymap>(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 +102,13 @@ export function buildKeymap>(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 +116,16 @@ export function buildKeymap>(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,72 +135,83 @@ export function buildKeymap>(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; + bind('Cmd-]', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { + const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible 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); + tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); } else { const node = resolved.nodeAfter; const sm = state.storedMarks || undefined; @@ -207,14 +219,14 @@ export function buildKeymap>(schema: S, props: any): KeyMa 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; + bind('Cmd-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { + const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible 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); + tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); } else { const node = resolved.nodeAfter; const sm = state.storedMarks || undefined; @@ -222,14 +234,14 @@ export function buildKeymap>(schema: S, props: any): KeyMa 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; + bind('Cmd-[', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { + const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible 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); + tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); } else { const node = resolved.nodeAfter; const sm = state.storedMarks || undefined; @@ -237,16 +249,16 @@ export function buildKeymap>(schema: S, props: any): KeyMa 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 +271,25 @@ export function buildKeymap>(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 +297,7 @@ export function buildKeymap>(schema: S, props: any): KeyMa ) { if ( !selectNodeBackward(state, (tx: Transaction) => { - dispatch(updateBullets(tx, schema)); + dispatch?.(updateBullets(tx, schema)); }) ) { return false; @@ -299,7 +311,7 @@ export function buildKeymap>(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 +323,31 @@ export function buildKeymap>(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 +358,12 @@ export function buildKeymap>(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 +380,14 @@ export function buildKeymap>(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) { -- cgit v1.2.3-70-g09d2 From 288a0095bf7e54b0c1765ba994f5f203fd8507cf Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 23 Mar 2025 20:51:21 -0400 Subject: fixed more typing errors. fixed updateBullets to not generate error when part of selection is not on a list. --- .../views/collections/CollectionNoteTakingView.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 4 +- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 12 ++--- src/client/views/nodes/FieldView.tsx | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 58 ++++++++++++---------- src/client/views/nodes/formattedText/nodes_rts.ts | 5 +- 8 files changed, 48 insertions(+), 43 deletions(-) (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts') diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 2dabd3269..01695dbaf 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -271,7 +271,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { containerViewPath={this.childContainerViewPath} fitWidth={this._props.childLayoutFitWidth} isContentActive={emptyFunction} - onKey={this.onKeyDown} + onKey={this.onKey} // TODO: change this from a prop to a parameter passed into a function dontHideOnDrag isDocumentActive={this.isContentActive} @@ -438,7 +438,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { }; @undoBatch - onKeyDown = (e: React.KeyboardEvent, textBox: FormattedTextBox) => { + onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => { if ((e.ctrlKey || textBox.Document._createDocOnCR) && ['Enter'].includes(e.key)) { e.stopPropagation?.(); const newDoc = Doc.MakeCopy(textBox.Document, true); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 883b0bbe3..fd48a9dc1 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -310,7 +310,7 @@ export class CollectionStackingView extends CollectionSubView { + onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => { if (['Enter'].includes(e.key) && e.ctrlKey) { e.stopPropagation?.(); const layoutFieldKey = StrCast(textBox.fieldKey); @@ -359,7 +359,7 @@ export class CollectionStackingView extends CollectionSubView { + onKey = (e: KeyboardEvent /* , textBox: FormattedTextBox */) => { if (this.outlineMode && e.key === 'Enter') { e.stopPropagation(); this.makeTextCollection(this.treeChildren); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 5ac283d22..cb7da8c84 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -939,7 +939,7 @@ export class TreeView extends ObservableReactComponent { if (property.startsWith(StyleProp.Decorations)) return null; return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; - onKeyDown = (e: React.KeyboardEvent) => { + onKey = (e: KeyboardEvent) => { if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) { switch (e.key) { case 'Tab': @@ -1138,7 +1138,7 @@ export class TreeView extends ObservableReactComponent { ScreenToLocalTransform={this.docTransform} renderDepth={this._props.renderDepth + 1} onClickScript={this.onChildClick} - onKey={this.onKeyDown} + onKey={this.onKey} containerViewPath={this.treeView.childContainerViewPath} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 25cec9c0d..7fd5d71fd 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1498,7 +1498,7 @@ export class CollectionFreeFormView extends CollectionSubView { + onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => { if ((e.metaKey || e.ctrlKey || e.altKey || textBox.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { e.stopPropagation?.(); return this.createTextDocCopy(textBox, !e.altKey && e.key !== 'Tab'); @@ -1541,7 +1541,7 @@ export class CollectionFreeFormView extends CollectionSubView disposer?.()); } - updateIcon = (usePanelDimensions?: boolean) => { + updateIcon = (/*usePanelDimensions?: boolean*/) => { const contentDiv = this._mainCont; return !contentDiv ? new Promise(res => res()) : UpdateIcon( this.layoutDoc[Id] + '_icon_' + new Date().getTime(), contentDiv, - usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width), - usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height), + this._props.PanelWidth(), // usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width), + this._props.PanelHeight(), // usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height), this._props.PanelWidth(), this._props.PanelHeight(), 0, @@ -2314,7 +2314,7 @@ export class CollectionFreeFormView extends CollectionSubView ScriptField; onPointerDownScript?: () => ScriptField; onPointerUpScript?: () => ScriptField; - onKey?: (e: React.KeyboardEvent, textBox: FormattedTextBox) => boolean | undefined; + onKey?: (e: KeyboardEvent, textBox: FormattedTextBox) => boolean | undefined; fitWidth?: (doc: Doc) => boolean | undefined; dontCenter?: 'x' | 'y' | 'xy' | undefined; searchFilterDocs: () => Doc[]; diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index a75efd77b..eabc6455f 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -1,12 +1,12 @@ import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands'; import { redo, undo } from 'prosemirror-history'; -import { MarkType, Node, ResolvedPos, Schema } from 'prosemirror-model'; +import { MarkType, Node, Schema } from 'prosemirror-model'; import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list'; 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'; @@ -19,12 +19,12 @@ const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : 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: 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 unknown as { path: (number | Node)[] }; // bcz: can't access path .. need to FIX - let depth = Array.from(path).reduce((p: number, c: number | Node) => p + ((c as Node).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++; @@ -33,7 +33,7 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle } }); return tx2; -}; +} export function buildKeymap>(schema: S, tbox?: FormattedTextBox): KeyMap { const keys: { [key: string]: Command } = {}; @@ -43,8 +43,9 @@ export function buildKeymap>(schema: S, tbox?: Formatte } 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 - return tbox?._props.onKey?.(event, tbox); + // 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 event && tbox?._props.onKey?.(event as unknown as KeyboardEvent, tbox); } const canEdit = (state: EditorState) => { @@ -53,8 +54,9 @@ export function buildKeymap>(schema: S, tbox?: Formatte switch (permissions) { case AclAugment: { - const prevNode = (state.selection as any).$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; } @@ -208,12 +210,14 @@ export function buildKeymap>(schema: S, tbox?: Formatte return true; }); bind('Cmd-]', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { - const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible - const { tr } = state; - if (resolved?.parent.type.name === 'paragraph') { - tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); + 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 || [])]); @@ -223,12 +227,14 @@ export function buildKeymap>(schema: S, tbox?: Formatte return true; }); bind('Cmd-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { - const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible - const { tr } = state; - if (resolved?.parent.type.name === 'paragraph') { - tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); + 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 || [])]); @@ -238,12 +244,14 @@ export function buildKeymap>(schema: S, tbox?: Formatte return true; }); bind('Cmd-[', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => { - const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible - const { tr } = state; - if (resolved?.parent.type.name === 'paragraph') { - tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); + 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 || [])]); diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 02ded3103..fe7b77e74 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -385,10 +385,6 @@ export const nodes: { [index: string]: NodeSpec } = { }; }, }, - { - style: 'list-style-type=disc', - getAttrs: () => ({ mapStyle: 'bullet' }), - }, { tag: 'ol', getAttrs: dom => { @@ -443,6 +439,7 @@ export const nodes: { [index: string]: NodeSpec } = { mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet" visibility: { default: true }, }, + marks: '_', content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)', parseDOM: [ { -- cgit v1.2.3-70-g09d2 From b0bb28202fa77556768d1da93bf9ea879c6ba2e5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 10 Apr 2025 12:05:55 -0400 Subject: performance fixes for text boxes to not call screenToLocal unnecessarily. simplifications to template data docs --- src/client/views/DocumentDecorations.scss | 2 +- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/StyleProvider.tsx | 24 +++-- src/client/views/TagsView.scss | 3 + .../views/collections/CollectionCarouselView.scss | 2 + .../views/collections/CollectionCarouselView.tsx | 14 ++- .../views/collections/CollectionNoteTakingView.tsx | 6 +- .../views/collections/CollectionStackingView.tsx | 13 ++- src/client/views/collections/CollectionSubView.tsx | 7 +- .../views/collections/CollectionTreeView.tsx | 14 +-- .../collectionGrid/CollectionGridView.tsx | 11 +-- .../CollectionMulticolumnView.tsx | 106 ++++++++++----------- .../CollectionMultirowView.tsx | 106 ++++++++++----------- src/client/views/nodes/DocumentView.scss | 6 ++ src/client/views/nodes/DocumentView.tsx | 87 ++++++++++------- src/client/views/nodes/FieldView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 16 ++-- .../formattedText/ProsemirrorExampleTransfer.ts | 3 +- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- 19 files changed, 229 insertions(+), 197 deletions(-) (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 28cebe23c..09a13634f 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -40,10 +40,10 @@ $resizeHandler: 8px; } .documentDecorations-tagsView { position: absolute; - height: 100%; width: 100%; pointer-events: all; border-radius: 50%; + top: 30; // offset by height of documentButtonBar so that items can be clicked without overlap interference color: black; } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 452dd60ee..0756d3d9e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -863,7 +863,7 @@ export class DocumentDecorations extends ObservableReactComponent 1 || !seldocview.showTags ? 'none' : undefined, transform: `translate(${-this._resizeBorderWidth + 10}px, ${2 * this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a3067907d..e8876f96f 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -327,7 +327,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt doc?.pointerEvents !== 'none' ? null : ( + const showLock = doc?.pointerEvents === 'none' + const lock = () => !showLock ? null : (
toggleLockedPosition(doc)}>
); - const paint = () => !doc?.onPaint ? null : ( + const showPaint = doc?.onPaint; + const paint = () => !showPaint ? null : (
togglePaintView(e, doc, props)}>
); + const showFilterIcon = + StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length + ? 'green' // #18c718bd' //'hasFilter' + : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length + ? 'orange' // 'inheritsFilter' + : undefined; + const showFilter = showFilterIcon && !hideFilterStatus; const filter = () => { const dashView = untracked(() => DocumentView.getDocumentView(Doc.ActiveDashboard)); - const showFilterIcon = - StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length - ? 'green' // #18c718bd' //'hasFilter' - : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length - ? 'orange' // 'inheritsFilter' - : undefined; - return !showFilterIcon || hideFilterStatus ? null : ( + return !showFilter ? null : (
, props: Opt {paint()} {lock()} diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss index d58b55ab5..a940502d4 100644 --- a/src/client/views/TagsView.scss +++ b/src/client/views/TagsView.scss @@ -16,6 +16,9 @@ min-height: unset !important; } } + .tagsView-editing-box { + pointer-events: all; + } } } diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 1080aa703..962b590c8 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -9,6 +9,8 @@ height: 50; display: inline-block; width: 100%; + bottom: 0; + position: absolute; } .collectionCarouselView-image { height: calc(100% - 50px); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index c80ac23eb..2d3f8cb0e 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -16,7 +16,6 @@ import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { DocData } from '../../../fields/DocSymbols'; @observer export class CollectionCarouselView extends CollectionSubView() { @@ -116,12 +115,14 @@ export class CollectionCarouselView extends CollectionSubView() { ? false : undefined; // prettier-ignore onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); + renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { return ( diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 01695dbaf..173147f64 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -258,11 +258,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { const noteTakingDocTransform = () => this.getDocTransform(doc, dref); return ( { - dref = r || undefined; - }} + ref={r => (dref = r || undefined)} Document={doc} - TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)} + TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined} pointerEvents={this.blockPointerEventsWhenDragging} renderDepth={this._props.renderDepth + 1} PanelWidth={width} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index d7cf0ffc2..4f60acb18 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -675,12 +675,12 @@ export class CollectionStackingView extends CollectionSubView 35; + @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore @computed get buttonMenu() { - const menuDoc = DocCast(this.layoutDoc.layout_headerButton); - return !menuDoc ? null : ( -
+ return !this.menuBtnDoc ? null : ( +
- {buttonMenu || noviceExplainer ? ( + {this.menuBtnDoc || noviceExplainer ? (
- {buttonMenu ? this.buttonMenu : null} + {this.menuBtnDoc ? this.buttonMenu : null} {Doc.noviceMode && noviceExplainer ?
{StrCast(noviceExplainer)}
: null}
) : null} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index a88707c6f..375c0fe53 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -603,7 +603,12 @@ export function CollectionSubView() { */ @computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore - screenXPadding = () => (this.uiBtnScaling * this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) / this._props.ScreenToLocalTransform().Scale; + screenXPadding = (docView?: DocumentView) => { + if (!docView) return 0; + const diff = this._props.PanelWidth() - docView.PanelWidth(); + const xpad1 = this.uiBtnScaling * (this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) - diff / 2; // this._sideBtnWidth; + return xpad1 / (docView.NativeDimScaling?.() || 1); + }; filteredChildDocs = () => this.childLayoutPairs.map(pair => pair.layout); childDocsFunc = () => this.childDocs; @action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 418c437d5..962fbdcd7 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -8,7 +8,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; @@ -200,7 +200,7 @@ export class CollectionTreeView extends CollectionSubView { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout const layoutItems: ContextMenuProps[] = []; - const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc; + const menuDoc = ScriptCast(this.menuBtnDoc?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc; menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' }); if (!Doc.noviceMode) { layoutItems.push({ @@ -347,14 +347,14 @@ export class CollectionTreeView extends CollectionSubView 35; + @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore @computed get buttonMenu() { - const menuDoc = Cast(this.layoutDoc.layout_headerButton, Doc, null); // To create a multibutton menu add a CollectionLinearView - return !menuDoc ? null : ( -
+ return !this.menuBtnDoc ? null : ( +
(this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined); /** * - * @param layout + * @param childLayout * @param dxf the x- and y-translations of the decorations box as a transform i.e. this.lookupIndividualTransform * @param width * @param height * @returns the `ContentFittingDocumentView` of the node */ - getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => ( + getDisplayDoc = (childLayout: Doc, dxf: () => Transform, width: () => number, height: () => number) => ( { - if (this.columnUnitLength === undefined) { - return Transform.Identity(); // we're still waiting on promises to resolve - } - let offset = 0; - for (const { layout: candidate } of this.childLayoutPairs) { - if (candidate === layout) { - return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0); + if (this.columnUnitLength !== undefined) { + let offset = 0; + for (const { layout: candidate } of this.childLayoutPairs) { + if (candidate === layout) { + return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0); + } + offset += this.lookupPixels(candidate) + resizerWidth; } - offset += this.lookupPixels(candidate) + resizerWidth; } return Transform.Identity(); }; @@ -262,50 +261,51 @@ export class CollectionMulticolumnView extends CollectionSubView() { ? true : undefined; }; - getDisplayDoc = (childLayout: Doc) => { - const width = () => this.lookupPixels(childLayout); - const height = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); - const dxf = () => - this.lookupIndividualTransform(childLayout) + childHeight = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); + childWidth = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc)); + childXf = computedFn( + (childDoc: Doc) => () => + this.lookupIndividualTransform(childDoc) .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) - .scale(this._props.NativeDimScaling?.() || 1); - return ( - - ); - }; + .scale(this._props.NativeDimScaling?.() || 1) + ); + getDisplayDoc = (childLayout: Doc) => ( + + ); + /** * @returns the resolved list of rendered child documents, displayed * at their resolved pixel widths, each separated by a resizer. diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index f95928cf6..6bbe55f54 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -2,7 +2,7 @@ import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../../fields/Doc'; -import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { Transform } from '../../../util/Transform'; @@ -11,7 +11,7 @@ import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView' import './CollectionMultirowView.scss'; import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; -import { DocData } from '../../../../fields/DocSymbols'; +import { computedFn } from 'mobx-utils'; interface HeightSpecifier { magnitude: number; @@ -179,16 +179,14 @@ export class CollectionMultirowView extends CollectionSubView() { * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { - if (this.rowUnitLength === undefined) { - return Transform.Identity(); // we're still waiting on promises to resolve - } - let offset = 0; - // eslint-disable-next-line no-restricted-syntax - for (const { layout: candidate } of this.childLayoutPairs) { - if (candidate === layout) { - return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); + if (this.rowUnitLength !== undefined) { + let offset = 0; + for (const { layout: candidate } of this.childLayoutPairs) { + if (candidate === layout) { + return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); + } + offset += this.lookupPixels(candidate) + resizerHeight; } - offset += this.lookupPixels(candidate) + resizerHeight; } return Transform.Identity(); // type coersion, this case should never be hit }; @@ -244,49 +242,51 @@ export class CollectionMultirowView extends CollectionSubView() { ? true : undefined; }; - getDisplayDoc = (layout: Doc) => { - const height = () => this.lookupPixels(layout); - const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); - const dxf = () => - this.lookupIndividualTransform(layout) + childHeight = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc)); + childWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); + childXf = computedFn( + (childDoc: Doc) => () => + this.lookupIndividualTransform(childDoc) .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) - .scale(this._props.NativeDimScaling?.() || 1); - return ( - - ); - }; + .scale(this._props.NativeDimScaling?.() || 1) + ); + + getDisplayDoc = (childLayout: Doc) => ( + + ); + /** * @returns the resolved list of rendered child documents, displayed * at their resolved pixel widths, each separated by a resizer. diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index f60f33608..5ac66f2cd 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -277,6 +277,12 @@ bottom: 0; pointer-events: none; } +.documentView-widgetDecorations { + transform-origin: top right; + position: absolute; + top: 0; + right: 0; +} .documentView-editorView-history { position: absolute; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b03dd3944..bec5ea5c1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -712,6 +712,54 @@ export class DocumentViewInternal extends DocComponent (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1); aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling); + aiEditor = () => ( + <> +
this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))} + style={{ + transform: `scale(${this.uiBtnScaling})`, + height: this.aiContentsHeight() / this.uiBtnScaling, + width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling, + }}> + {this._componentView?.componentAIViewHistory?.() ?? null} +
+
this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}> +
+ {this._componentView?.componentAIView?.() ?? null} + {this._props.DocumentView?.() ? : null} +
+ + ); + @computed get tagsOverlay() { + return ( +
+ {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? : null} +
+ ); + } + tagsOverlayFunc = () => (this._props.DocumentView?.().showTags ? this.tagsOverlay : null); + @computed get widgetOverlay() { + return ( +
+ {this.widgetDecorations} +
+ ); + } + widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null); @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -742,43 +790,8 @@ export class DocumentViewInternal extends DocComponent
- {!this._props.showAIEditor ? ( -
- {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? : null} -
- ) : ( - <> -
this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))} - style={{ - transform: `scale(${this.uiBtnScaling})`, - height: this.aiContentsHeight() / this.uiBtnScaling, - width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling, - }}> - {this._componentView?.componentAIViewHistory?.() ?? null} -
-
this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}> -
- {this._componentView?.componentAIView?.() ?? null} - {this._props.DocumentView?.() ? : null} -
- - )} -
{this.widgetDecorations ?? null}
+ {this._props.showAIEditor ? this.aiEditor() : this.tagsOverlayFunc()} + {this.widgetOverlayFunc()} ); } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 74059bd12..ad6d93d43 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -50,7 +50,7 @@ export interface FieldViewSharedProps { TemplateDataDocument?: Doc; renderDepth: number; scriptContext?: unknown; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document - screenXPadding?: () => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView) + screenXPadding?: (view: DocumentView | undefined) => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView) xPadding?: number; yPadding?: number; dontRegisterView?: boolean; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 6fced76bd..a45b8a488 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -215,7 +215,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document); + const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : this.rootDoc; if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc; const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc }); this.addDocument(anchor); @@ -308,7 +308,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (node.type === this.EditorView?.state.schema.nodes.dashField) { - const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); + const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); const fieldKey = StrCast(node.attrs.fieldKey); return ( (node.attrs.hideKey ? '' : fieldKey + ':') + // @@ -316,7 +316,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); - const scrSize = (which: number, view = this._props.docViewPath().slice(-which)[0]) => - [view?._props.PanelWidth() /(view?.screenToLocalScale()??1), view?._props.PanelHeight() / (view?.screenToLocalScale()??1)]; // prettier-ignore - const scrMargin = [Math.max(0, (scrSize(2)[0] - scrSize(1)[0]) / 2), Math.max(0, (scrSize(2)[1] - scrSize(1)[1]) / 2)]; - const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), this._props.xPadding ?? 0, 0, ((this._props.screenXPadding?.() ?? 0) - scrMargin[0]) * this.ScreenToLocalBoxXf().Scale); - const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, ((this._props.yPadding ?? 0) - scrMargin[1]) * this.ScreenToLocalBoxXf().Scale); + const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xPadding ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0); + const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yPadding ?? 0); // prettier-ignore const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., return this.isLabel ? ( diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index eabc6455f..7ae1fc202 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -50,8 +50,7 @@ export function buildKeymap>(schema: S, tbox?: Formatte const canEdit = (state: EditorState) => { if (!tbox) return true; - const permissions = GetEffectiveAcl(tbox._props.TemplateDataDocument ?? tbox.Document[DocData]); - switch (permissions) { + switch (GetEffectiveAcl(tbox.dataDoc)) { case AclAugment: { // previously used hack: (state.selection as any).$cursor.nodeBefore; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 31cd1603f..7e0375275 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -67,7 +67,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { // Since this node is being rendered with a template, this method retrieves // the actual slide being rendered from the auto-generated rendering template @computed get slideDoc() { - return DocCast(this.Document.rootDocument, this.Document); + return this.rootDoc; } // this is the document in the workspaces that is targeted by the slide -- cgit v1.2.3-70-g09d2