aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/formattedText')
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx18
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx86
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts18
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx30
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts26
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts26
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: {