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/DashDocCommentView.tsx15
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx14
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss9
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx43
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx26
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx82
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx14
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts9
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts16
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx9
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts4
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts27
12 files changed, 169 insertions, 99 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index 40dd6fbc7..4bff57842 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,5 +1,5 @@
import { TextSelection } from 'prosemirror-state';
-import * as ReactDOM from 'react-dom';
+import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
import React = require('react');
@@ -9,6 +9,7 @@ import React = require('react');
// the comment can be toggled on/off with the '<-' text anchor.
export class DashDocCommentView {
dom: HTMLDivElement; // container for label and value
+ root: any;
constructor(node: any, view: any, getPos: any) {
this.dom = document.createElement('div');
@@ -30,15 +31,17 @@ export class DashDocCommentView {
e.stopPropagation();
};
- ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this.dom);
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />);
(this as any).dom = this.dom;
}
- destroy() {
- ReactDOM.unmountComponentAtNode(this.dom);
+ deselectNode() {
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ this.dom.classList.add('ProseMirror-selectednode');
}
-
- selectNode() {}
}
interface IDashDocCommentViewInternal {
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 73a711b9d..63ee7d1f3 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,7 +1,7 @@
import { action, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { NodeSelection } from 'prosemirror-state';
-import * as ReactDOM from 'react-dom';
+import * as ReactDOM from 'react-dom/client';
import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
@@ -15,6 +15,7 @@ import React = require('react');
export class DashDocView {
dom: HTMLSpanElement; // container for label and value
+ root: any;
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this.dom = document.createElement('span');
@@ -38,14 +39,13 @@ export class DashDocView {
e.stopPropagation();
};
- ReactDOM.render(
- <DashDocViewInternal docid={node.attrs.docid} alias={node.attrs.alias} width={node.attrs.width} height={node.attrs.height} hidden={node.attrs.hidden} fieldKey={node.attrs.fieldKey} tbox={tbox} view={view} node={node} getPos={getPos} />,
- this.dom
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(
+ <DashDocViewInternal docid={node.attrs.docid} alias={node.attrs.alias} width={node.attrs.width} height={node.attrs.height} hidden={node.attrs.hidden} fieldKey={node.attrs.fieldKey} tbox={tbox} view={view} node={node} getPos={getPos} />
);
- (this as any).dom = this.dom;
}
destroy() {
- ReactDOM.unmountComponentAtNode(this.dom);
+ // ReactDOM.unmountComponentAtNode(this.dom);
}
selectNode() {}
}
@@ -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/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index c36e6804b..f17579853 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,3 +1,5 @@
+@import '../../global/globalCssVariables';
+
.dashFieldView {
position: relative;
display: inline-flex;
@@ -22,7 +24,7 @@
position: relative;
display: inline-block;
font-weight: normal;
- background: rgba(0,0,0,0.1);
+ background: rgba(0, 0, 0, 0.1);
}
.dashFieldView-fieldSpan {
min-width: 8px;
@@ -31,11 +33,12 @@
padding-left: 2px;
display: inline-block;
background-color: rgba(155, 155, 155, 0.24);
- font-weight: bold;
span {
min-width: 100%;
display: inline-block;
}
}
}
- \ No newline at end of file
+.ProseMirror-selectedNode {
+ outline: solid 1px $light-blue !important;
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 35d919f38..afae59733 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,6 +1,6 @@
import { action, computed, IReactionDisposer, observable } from 'mobx';
import { observer } from 'mobx-react';
-import * as ReactDOM from 'react-dom';
+import * as ReactDOM from 'react-dom/client';
import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
@@ -16,20 +16,18 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { Tooltip } from '@material-ui/core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { NodeSelection } from 'prosemirror-state';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
+ root: any;
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey);
-
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
- this.dom.style.fontWeight = 'bold';
this.dom.style.position = 'relative';
this.dom.style.display = 'inline-block';
- this.dom.textContent = node.attrs.fieldKey.startsWith('#') ? node.attrs.fieldKey : node.attrs.fieldKey + ' ' + strVal;
this.dom.onkeypress = function (e: any) {
e.stopPropagation();
};
@@ -43,13 +41,20 @@ export class DashFieldView {
e.stopPropagation();
};
- setTimeout(() => ReactDOM.render(<DashFieldViewInternal fieldKey={node.attrs.fieldKey} docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} tbox={tbox} />, this.dom));
- (this as any).dom = this.dom;
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(
+ <DashFieldViewInternal node={node} getPos={getPos} fieldKey={node.attrs.fieldKey} docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} editable={node.attrs.editable} tbox={tbox} />
+ );
}
destroy() {
- ReactDOM.unmountComponentAtNode(this.dom);
+ //this.root.unmount();
+ }
+ deselectNode() {
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ this.dom.classList.add('ProseMirror-selectednode');
}
- selectNode() {}
}
interface IDashFieldViewInternal {
@@ -59,6 +64,9 @@ interface IDashFieldViewInternal {
tbox: FormattedTextBox;
width: number;
height: number;
+ editable: boolean;
+ node: any;
+ getPos: any;
}
@observer
@@ -116,7 +124,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
return (
<span
className="dashFieldView-fieldSpan"
- contentEditable={true}
+ contentEditable={this.props.editable}
style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
suppressContentEditableWarning={true}
defaultValue={strVal}
@@ -125,7 +133,13 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false));
r?.addEventListener(
'pointerdown',
- action(e => e.stopPropagation())
+ action(e => {
+ // let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ // while (target && !target.dataset?.targethrefs) target = target.parentElement;
+ this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new NodeSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos()))));
+ // FormattedTextBoxComment.update(this.props.tbox, this.props.tbox.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc);
+ // e.stopPropagation();
+ })
);
}}>
{strVal}
@@ -160,7 +174,6 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
updateText = (nodeText: string, forceMatch: boolean) => {
if (nodeText) {
const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText;
-
// look for a document whose id === the fieldKey being displayed. If there's a match, then that document
// holds the different enumerated values for the field in the titles of its collected documents.
// if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
@@ -266,14 +279,12 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
document.addEventListener('pointerdown', hideMenu, true);
};
render() {
- const buttons = [
+ return this.getElement([
<Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
<button className="antimodeMenu-button" onPointerDown={this.showFields}>
<FontAwesomeIcon icon="eye" size="lg" />
</button>
</Tooltip>,
- ];
-
- return this.getElement(buttons);
+ ]);
}
}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 98d611ca6..0fd2a7808 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,7 +1,8 @@
import EquationEditor from 'equation-editor-react';
import { IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
-import * as ReactDOM from 'react-dom';
+import { TextSelection } from 'prosemirror-state';
+import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
import { StrCast } from '../../../../fields/Types';
import './DashFieldView.scss';
@@ -10,7 +11,7 @@ import React = require('react');
export class EquationView {
dom: HTMLDivElement; // container for label and value
-
+ root: any;
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
@@ -21,13 +22,16 @@ 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);
- (this as any).dom = this.dom;
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
}
_editor: EquationEditor | undefined;
setEditor = (editor?: EquationEditor) => (this._editor = editor);
destroy() {
- ReactDOM.unmountComponentAtNode(this.dom);
+ // 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 6db199149..63435eea8 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;
@@ -387,13 +393,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const newAutoLinks = new Set<Doc>();
const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
if (this._editorView?.state.doc.textContent) {
+ const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
const t = this._editorView.state.selection.to;
var tr = this._editorView.state.tr as any;
const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
- tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+ tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
@@ -508,7 +515,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 +531,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 +545,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();
@@ -675,8 +685,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 +797,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 +1005,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(
@@ -1030,8 +1043,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) {
@@ -1119,6 +1133,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
quickScroll = undefined;
this.tryUpdateScrollHeight();
+ setTimeout(this.tryUpdateScrollHeight, 250);
}
pushToGoogleDoc = async () => {
@@ -1168,7 +1183,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 +1311,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 +1363,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));
}
@@ -1383,11 +1399,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
} else if (this._editorView) {
this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
- FormattedTextBox.DontSelectInitialText = false;
}
selectOnLoad && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
if (this._editorView) {
+ const tr = this._editorView.state.tr;
+ const { from, to } = tr.selection;
+ // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selectoin after the document has ben fully instantiated.
+ if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
this._editorView.state.storedMarks = [
...(this._editorView.state.storedMarks ?? []),
...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })] : []),
@@ -1399,6 +1418,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
];
}
+ FormattedTextBox.DontSelectInitialText = false;
}
componentWillUnmount() {
@@ -1466,7 +1486,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
document.removeEventListener('pointermove', this.onSelectMove);
};
onPointerUp = (e: React.PointerEvent): void => {
- if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu();
+ if (!this._editorView?.state.selection.empty && !(this._editorView?.state.selection instanceof NodeSelection) && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu();
if (!this._downEvent) return;
this._downEvent = false;
if (this.props.isContentActive(true) && !(e.nativeEvent as any).dash) {
@@ -1475,7 +1495,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
!this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc);
+ FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview);
}
if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index bdf59863b..e7ca26d5c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,5 +1,5 @@
import { Mark, ResolvedPos } from 'prosemirror-model';
-import { EditorState } from 'prosemirror-state';
+import { EditorState, NodeSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
@@ -92,7 +92,7 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip.style.display = '';
}
- static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '') {
+ static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '', noPreview: boolean = false) {
FormattedTextBoxComment.textBox = textBox;
if (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection)) {
FormattedTextBoxComment.setupPreview(
@@ -102,12 +102,13 @@ export class FormattedTextBoxComment {
?.trim()
.split(' ')
.filter(h => h),
- linkDoc
+ linkDoc,
+ noPreview
);
}
}
- static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string) {
+ static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) {
const state = view.state;
// this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
if (state.selection.$from) {
@@ -130,8 +131,8 @@ export class FormattedTextBoxComment {
if (state.selection.$from && hrefs?.length) {
const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- nbef &&
- naft &&
+ //nbef &&
+ naft &&
LinkDocPreview.SetLinkInfo({
docProps: textBox.props,
linkSrc: textBox.rootDoc,
@@ -139,6 +140,7 @@ export class FormattedTextBoxComment {
location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
hrefs,
showHeader: true,
+ noPreview,
});
}
}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index be501329f..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));
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index e5ea7b3b0..e3c67ad2e 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -248,8 +248,7 @@ export class RichTextRules {
// [[fieldKey:Doc]] => show field of doc
new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
const fieldKey = match[1];
- const rawdocid = match[3];
- const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined;
+ const docid = match[3]?.replace(':', '');
const value = match[2]?.substring(1);
if (!fieldKey) {
if (docid) {
@@ -259,7 +258,7 @@ export class RichTextRules {
if (rstate) {
this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
}
- const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid);
+ const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500 }, docid);
DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined);
const fstate = this.TextBox.EditorView?.state;
@@ -275,7 +274,7 @@ export class RichTextRules {
const num = value.match(/^[0-9.]$/);
this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
}
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid, hideKey: false });
return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
}),
@@ -295,6 +294,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
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 01acc3de9..1fe6d822b 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,6 +1,6 @@
import { TextSelection } from 'prosemirror-state';
import { Fragment, Node, Slice } from 'prosemirror-model';
-import * as ReactDOM from 'react-dom';
+import * as ReactDOM from 'react-dom/client';
import React = require('react');
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
@@ -9,6 +9,7 @@ import React = require('react');
// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
export class SummaryView {
dom: HTMLSpanElement; // container for label and value
+ root: any;
constructor(node: any, view: any, getPos: any) {
const self = this;
@@ -35,13 +36,13 @@ export class SummaryView {
return js.apply(this, arguments);
};
- ReactDOM.render(<SummaryViewInternal />, this.dom);
- (this as any).dom = this.dom;
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<SummaryViewInternal />);
}
className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed');
destroy() {
- ReactDOM.unmountComponentAtNode(this.dom);
+ // ReactDOM.unmountComponentAtNode(this.dom);
}
selectNode() {}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 00c41e187..97549830c 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -75,6 +75,7 @@ export const marks: { [index: string]: MarkSpec } = {
allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
location: { default: null },
title: { default: null },
+ noPreview: { default: false },
docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
},
inclusive: false,
@@ -85,6 +86,7 @@ export const marks: { [index: string]: MarkSpec } = {
return {
location: dom.getAttribute('location'),
title: dom.getAttribute('title'),
+ noPreview: dom.getAttribute('noPreview'),
};
},
},
@@ -111,7 +113,7 @@ export const marks: { [index: string]: MarkSpec } = {
['br'],
]
: //node.attrs.allLinks.length === 1 ?
- ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
+ ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline` }, 0];
// ["div", { class: "prosemirror-anchor" },
// ["span", { class: "prosemirror-linkBtn" },
// ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 5142b7da6..aa2475dca 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',
@@ -251,6 +263,7 @@ export const nodes: { [index: string]: NodeSpec } = {
fieldKey: { default: '' },
docid: { default: '' },
hideKey: { default: false },
+ editable: { default: true },
},
group: 'inline',
draggable: false,
@@ -260,20 +273,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: {