diff options
| author | brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> | 2022-10-28 10:10:04 -0400 |
|---|---|---|
| committer | brynnchernosky <56202540+brynnchernosky@users.noreply.github.com> | 2022-10-28 10:10:04 -0400 |
| commit | ceb338752aacc383c97a0e3a9b608365a1cf39b6 (patch) | |
| tree | d2f355b726a9b21950f332c0f65931d7d6eef515 /src/client/views/nodes/formattedText | |
| parent | 5d6a0458b9d4f35e0c568a4d76d4fcab4e22f698 (diff) | |
| parent | 2fc88a931cb2fc3408297b000208990633445585 (diff) | |
merge
Diffstat (limited to 'src/client/views/nodes/formattedText')
7 files changed, 133 insertions, 73 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 73a711b9d..5f576be41 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -149,7 +149,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current); return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target + outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document, {}); // ideally, this would scroll to show the focus target onKeyDown = (e: any) => { e.stopPropagation(); diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index 98d611ca6..4895dcdc5 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -1,6 +1,7 @@ import EquationEditor from 'equation-editor-react'; import { IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; +import { TextSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom'; import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; @@ -21,7 +22,7 @@ export class EquationView { e.stopPropagation(); }; - ReactDOM.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} setEditor={this.setEditor} tbox={tbox} />, this.dom); + ReactDOM.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />, this.dom); (this as any).dom = this.dom; } _editor: EquationEditor | undefined; @@ -29,6 +30,9 @@ export class EquationView { destroy() { ReactDOM.unmountComponentAtNode(this.dom); } + setSelection() { + this._editor?.mathField.focus(); + } selectNode() { this._editor?.mathField.focus(); } @@ -40,6 +44,7 @@ interface IEquationViewInternal { tbox: FormattedTextBox; width: number; height: number; + getPos: () => number; setEditor: (editor: EquationEditor | undefined) => void; } @@ -67,11 +72,22 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal> return ( <div className="equationView" + onKeyDown={e => { + if (e.key === 'Enter') { + this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new TextSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos() + 1)))); + this.props.tbox.EditorView!.focus(); + e.preventDefault(); + } + e.stopPropagation(); + }} + onKeyPress={e => e.stopPropagation()} style={{ position: 'relative', display: 'inline-block', width: this.props.width, height: this.props.height, + background: 'white', + borderRadius: '10%', bottom: 3, }}> <EquationEditor diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 314696251..096f9a92c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -18,7 +18,7 @@ import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from '../../../../fields/RichTextField'; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { ComputedField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; @@ -124,7 +124,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); } @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4')); + return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.fieldKey + '-backgroundColor'], '#e4e4e4')); } @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; @@ -281,12 +281,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const state = this._editorView.state.apply(tx); this._editorView.updateState(state); + const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc; const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); - const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box - const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype + const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField) : undefined; // the actual text in the text box + const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - const effectiveAcl = GetEffectiveAcl(this.dataDoc); + const effectiveAcl = GetEffectiveAcl(dataDoc); const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); @@ -297,29 +298,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps accumTags.push(node.attrs.fieldKey); } }); - const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith('#')); + const curTags = Object.keys(dataDoc).filter(key => key.startsWith('#')); const added = accumTags.filter(tag => !curTags.includes(tag)); const removed = curTags.filter(tag => !accumTags.includes(tag)); - removed.forEach(r => (this.dataDoc[r] = undefined)); - added.forEach(a => (this.dataDoc[a] = a)); + removed.forEach(r => (dataDoc[r] = undefined)); + added.forEach(a => (dataDoc[a] = a)); let unchanged = true; if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { this._applyingChange = this.fieldKey; - curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text && (this.dataDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()))); + curText !== Cast(dataDoc[this.fieldKey], RichTextField)?.Text && (dataDoc[this.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || json.includes('dash')) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (removeSelection(json) !== removeSelection(curLayout?.Data)) { - this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); - this.dataDoc[this.props.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + const numstring = NumCast(dataDoc[this.fieldKey], null); + if (numstring !== undefined) { + dataDoc[this.fieldKey] = Number(curText); + } else { + dataDoc[this.fieldKey] = new RichTextField(json, curText); + } + dataDoc[this.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; } } else { // if we've deleted all the text in a note driven by a template, then restore the template data - this.dataDoc[this.props.fieldKey] = undefined; + dataDoc[this.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); - this.dataDoc[this.props.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have + dataDoc[this.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; } @@ -330,7 +336,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } } } else { - const jsonstring = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!; + const jsonstring = Cast(dataDoc[this.fieldKey], RichTextField)?.Data!; if (jsonstring) { const json = JSON.parse(jsonstring); json.selection = state.toJSON().selection; @@ -508,7 +514,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._dropDisposer?.(); this.ProseRef = ele; if (ele) { - this.setupEditor(this.config, this.props.fieldKey); + this.setupEditor(this.config, this.fieldKey); this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc); } // if (this.autoHeight) this.tryUpdateScrollHeight(); @@ -524,7 +530,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps // replace text contents whend dragging with Alt if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) { if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); + Doc.GetProto(this.dataDoc)[this.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); e.stopPropagation(); } // embed document when dragg marked as embed @@ -538,6 +544,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps docid: target[Id], float: 'unset', }); + if (!['alias', 'copy'].includes((dragData.dropAction ?? '') as any)) { + dragData.removeDocument?.(dragData.draggedDocuments[0]); + } const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); e.stopPropagation(); @@ -603,20 +612,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' }); } if (highlights.indexOf('Todo Items') !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'todo', { outline: 'black solid 1px' }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' }); } if (highlights.indexOf('Important Items') !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'important', { 'font-size': 'larger' }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-important', { 'font-size': 'larger' }); } if (highlights.indexOf('Bold Text') !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, ''); addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, ''); } if (highlights.indexOf('Disagree Items') !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'disagree', { 'text-decoration': 'line-through' }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-disagree', { 'text-decoration': 'line-through' }); } if (highlights.indexOf('Ignore Items') !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'ignore', { 'font-size': '1' }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' }); } if (highlights.indexOf('By Recent Minute') !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); @@ -675,8 +684,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps @undoBatch deleteAnnotation = (anchor: Doc) => { LinkManager.Instance.deleteLink(DocListCast(anchor.links)[0]); - // const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]); - // this.props.dataDoc[this.props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion)); + // const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]); + // this.props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion)); // AnchorMenu.Instance.fadeOut(true); this.props.select(false); }; @@ -787,6 +796,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' }); // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); + !Doc.noviceMode && appearanceItems.push({ description: 'Make Default Layout', @@ -994,11 +1004,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps () => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight() ); + this._disposers.width = reaction( + () => this.props.PanelWidth(), + width => this.tryUpdateScrollHeight() + ); this._disposers.scrollHeight = reaction( () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), - ({ width, scrollHeight, autoHeight }) => { - width && autoHeight && this.resetNativeHeight(scrollHeight); - }, + ({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight), { fireImmediately: true } ); this._disposers.componentHeights = reaction( @@ -1006,7 +1018,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); - if (autoHeight && newHeight && newHeight !== this.rootDoc.height) { + if (autoHeight && newHeight && newHeight !== this.rootDoc.height && !this.props.dontRegisterView) { this.props.setHeight?.(newHeight); } }, @@ -1030,8 +1042,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ); this._disposers.editorState = reaction( () => { - const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : this.dataDoc?.[this.props.fieldKey + '-noTemplate'] || !this.layoutDoc[this.props.fieldKey] ? this.dataDoc : this.layoutDoc; - return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) }; + const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc; + const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '-noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc; + return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(whichDoc[this.fieldKey]) }; }, incomingValue => { if (this._editorView && this._applyingChange !== this.fieldKey) { @@ -1168,7 +1181,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps let pullSuccess = false; if (exportState !== undefined) { pullSuccess = true; - dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(exportState.state.toJSON())); + dataDoc[this.fieldKey] = new RichTextField(JSON.stringify(exportState.state.toJSON())); setTimeout(() => { if (this._editorView) { const state = this._editorView.state; @@ -1296,8 +1309,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } _didScroll = false; setupEditor(config: any, fieldKey: string) { - const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.props.fieldKey]); - const rtfField = Cast((!curText && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField); + const curText = Cast(this.dataDoc[this.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.fieldKey]); + const rtfField = Cast((!curText && this.layoutDoc[this.fieldKey]) || this.dataDoc[fieldKey], RichTextField); if (this.ProseRef) { const self = this; this._editorView?.destroy(); @@ -1348,7 +1361,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }); const { state, dispatch } = this._editorView; if (!rtfField) { - const startupText = Field.toString(this.dataDoc[fieldKey] as Field); + const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc; + const startupText = Field.toString(dataDoc[fieldKey] as Field); if (startupText) { dispatch(state.tr.insertText(startupText)); } @@ -1697,9 +1711,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; if (children) { - const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins); + const proseHeight = !this.ProseRef + ? 0 + : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')) + Number(getComputedStyle(child).marginTop.replace('px', '')) + Number(getComputedStyle(child).marginBottom.replace('px', '')), margins); const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); - if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { + if (this.props.setHeight && scrollHeight && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight); if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) { @@ -1860,7 +1876,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps className={`formattedTextBox-cont`} ref={this._ref} style={{ - overflow: this.autoHeight ? 'hidden' : undefined, + overflow: this.autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an autoHeight doc in its own tab, or in the lightbox height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined), pointerEvents: interactive ? undefined : 'none', }} diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 31552cf1b..3d9bd6add 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -2,7 +2,7 @@ import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinU import { redo, undo } from 'prosemirror-history'; import { Schema } from 'prosemirror-model'; import { splitListItem, wrapInList } from 'prosemirror-schema-list'; -import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; +import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state'; import { liftTarget } from 'prosemirror-transform'; import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc'; import { GetEffectiveAcl } from '../../../../fields/util'; @@ -143,7 +143,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey 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-m', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() })))); + bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => { + 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)))); + } + }); 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)); @@ -168,6 +173,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true)); bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true)); + bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => { + dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1)))); + 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)))); + return true; + }); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => { diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index c548e211b..6c6d26af5 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -154,7 +154,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveAlignment() { - if (this.view && this.TextView.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected(true)) { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { @@ -167,7 +167,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveListStyle() { - if (this.view && this.TextView.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected(true)) { const path = (this.view.state.selection.$from as any).path; for (let i = 0; i < path.length; i += 3) { if (path[i].type === this.view.state.schema.nodes.ordered_list) { @@ -183,13 +183,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveFontStylesOnSelection() { - if (!this.view) return { activeFamilies: [], activeSizes: [], activeColors: [], activeHighlights: [] }; - - const activeFamilies: string[] = []; - const activeSizes: string[] = []; - const activeColors: string[] = []; - const activeHighlights: string[] = []; - if (this.TextView.props.isSelected(true)) { + const activeFamilies = new Set<string>(); + const activeSizes = new Set<string>(); + const activeColors = new Set<string>(); + const activeHighlights = new Set<string>(); + if (this.view && this.TextView?.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; const marks: Mark[] = [...(state.storedMarks ?? [])]; @@ -203,13 +201,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { }); } marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); - m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize); - m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight)); + m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family); + m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color); + m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize); + m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight)); }); } - return { activeFamilies, activeSizes, activeColors, activeHighlights }; + return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) }; } getMarksInSelection(state: EditorState) { @@ -222,7 +220,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { //finds all active marks on selection in given group getActiveMarksOnSelection() { let activeMarks: MarkType[] = []; - if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks; + if (!this.view || !this.TextView?.props.isSelected(true)) return activeMarks; const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); @@ -412,7 +410,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { - if (this.TextView.props.isSelected(true)) { + if (this.TextView?.props.isSelected(true)) { var tr = view.state.tr; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 2097b321f..47833dd43 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -276,7 +276,7 @@ export class RichTextRules { this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; } const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); - return state.tr.deleteRange(start, end).insert(start, fieldView); + return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); }), // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document @@ -295,6 +295,15 @@ export class RichTextRules { return state.tr; }), + // create an inline equation node + // eq:<equation>> + new InputRule(new RegExp(/:eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => { + const fieldKey = 'math' + Utils.GenerateGuid(); + this.TextBox.dataDoc[fieldKey] = match[1]; + const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey })); + return tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1))); + }), + // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document // {{<layout>}} => show layout for this doc @@ -327,7 +336,10 @@ export class RichTextRules { this.Document[DataSym].tags = `${tags + '#' + tag + ':'}`; } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); - return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(' '); + return state.tr + .setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))) + .replaceSelectionWith(fieldView, true) + .insertText(' '); }), // # heading @@ -343,8 +355,14 @@ export class RichTextRules { const node = (state.doc.resolve(start) as any).nodeAfter; if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) { + } + return node + ? state.tr + .removeMark(start, end, schema.marks.user_mark) + .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })) + .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) + : state.tr; }), new InputRule(new RegExp(/%\(/), (state, match, start, end) => { diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 5142b7da6..66d747bf7 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -157,6 +157,18 @@ export const nodes: { [index: string]: NodeSpec } = { }, }, + equation: { + inline: true, + attrs: { + fieldKey: { default: '' }, + }, + group: 'inline', + toDOM(node) { + const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; + return ['div', { ...node.attrs, ...attrs }]; + }, + }, + // :: NodeSpec The text node. text: { group: 'inline', @@ -260,20 +272,6 @@ export const nodes: { [index: string]: NodeSpec } = { }, }, - equation: { - inline: true, - attrs: { - fieldKey: { default: '' }, - }, - atom: true, - group: 'inline', - draggable: false, - toDOM(node) { - const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; - return ['div', { ...node.attrs, ...attrs }]; - }, - }, - video: { inline: true, attrs: { |
