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.tsx30
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx34
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss10
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx94
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx39
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx72
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx442
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx14
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts21
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx144
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts57
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx10
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts20
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts35
15 files changed, 585 insertions, 443 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index 40dd6fbc7..aa269d8d6 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,19 +31,24 @@ 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);
+ this.root.unmount();
+ }
+ deselectNode() {
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ this.dom.classList.add('ProseMirror-selectednode');
}
-
- selectNode() {}
}
interface IDashDocCommentViewInternal {
- docid: string;
+ docId: string;
view: any;
getPos: any;
}
@@ -57,13 +63,13 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
}
onPointerLeaveCollapsed(e: any) {
- DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+ DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
}
onPointerEnterCollapsed(e: any) {
- DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+ DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
}
@@ -76,7 +82,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
- expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ expand && DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
} catch (e) {}
@@ -94,12 +100,12 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
const state = this.props.view.state;
for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
const m = state.doc.nodeAt(i);
- if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docid === this.props.docid) {
+ if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) {
return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any; pos: number; hidden: boolean };
}
}
- const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: this.props.docid, float: 'right' });
+ const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: this.props.docId, float: 'right' });
this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc));
setTimeout(() => {
try {
@@ -113,7 +119,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
return (
<span
className="formattedTextBox-inlineComment"
- id={'DashDocCommentView-' + this.props.docid}
+ id={'DashDocCommentView-' + this.props.docId}
onPointerLeave={this.onPointerLeaveCollapsed}
onPointerEnter={this.onPointerEnterCollapsed}
onPointerUp={this.onPointerUpCollapsed}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 73a711b9d..61345f891 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';
@@ -12,9 +12,11 @@ import { Transform } from '../../../util/Transform';
import { DocumentView } from '../DocumentView';
import { FormattedTextBox } from './FormattedTextBox';
import React = require('react');
+import { SelectionManager } from '../../../util/SelectionManager';
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,20 +40,20 @@ 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);
+ this.root.unmount();
+ // ReactDOM.unmountComponentAtNode(this.dom);
}
selectNode() {}
}
interface IDashDocViewInternal {
- docid: string;
+ docId: string;
alias: string;
tbox: FormattedTextBox;
width: string;
@@ -75,7 +77,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
updateDoc = action((dashDoc: Doc) => {
this._dashDoc = dashDoc;
- this._finalLayout = this.props.docid ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc, this.props.fieldKey);
+ this._finalLayout = this.props.docId ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc, this.props.fieldKey);
if (this._finalLayout) {
if (!Doc.AreProtosEqual(this._finalLayout, dashDoc)) {
@@ -105,12 +107,12 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
super(props);
this._textBox = this.props.tbox;
- DocServer.GetRefField(this.props.docid + this.props.alias).then(async dashDoc => {
+ DocServer.GetRefField(this.props.docId + this.props.alias).then(async dashDoc => {
if (!(dashDoc instanceof Doc)) {
this.props.alias &&
- DocServer.GetRefField(this.props.docid).then(async dashDocBase => {
+ DocServer.GetRefField(this.props.docId).then(async dashDocBase => {
if (dashDocBase instanceof Doc) {
- const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias);
+ const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docId + this.props.alias);
aliasedDoc.layoutKey = 'layout';
this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined);
this.updateDoc(aliasedDoc);
@@ -149,7 +151,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();
@@ -159,12 +161,12 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
};
onPointerLeave = () => {
- const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docId) as HTMLDivElement;
ele && (ele.style.backgroundColor = '');
};
onPointerEnter = () => {
- const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docId) as HTMLDivElement;
ele && (ele.style.backgroundColor = 'orange');
};
@@ -191,10 +193,10 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
Document={this._finalLayout}
DataDoc={this._resolvedDataDoc}
addDocument={returnFalse}
- rootSelected={this._textBox.props.isSelected}
+ rootSelected={returnFalse} //{this._textBox.props.isSelected}
removeDocument={this.removeDoc}
isDocumentActive={returnFalse}
- isContentActive={this._textBox.props.isContentActive}
+ isContentActive={emptyFunction}
styleProvider={this._textBox.props.styleProvider}
docViewPath={this._textBox.props.docViewPath}
ScreenToLocalTransform={this.getDocTransform}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index c36e6804b..ad315acc8 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,13 @@
padding-left: 2px;
display: inline-block;
background-color: rgba(155, 155, 155, 0.24);
- font-weight: bold;
span {
+ user-select: all;
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 d2f7b5677..21ccf3bc7 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,35 +1,39 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable } from 'mobx';
import { observer } from 'mobx-react';
-import * as ReactDOM from 'react-dom';
+import { NodeSelection } from 'prosemirror-state';
+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';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { ComputedField } from '../../../../fields/ScriptField';
import { Cast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
import React = require('react');
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
-import { Tooltip } from '@material-ui/core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
+ root: any;
+ node: any;
+ tbox: FormattedTextBox;
+ unclickable = () => !this.tbox.props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey);
-
+ this.node = node;
+ this.tbox = tbox;
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,22 +47,44 @@ 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}
+ unclickable={this.unclickable}
+ 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 {
fieldKey: string;
- docid: string;
+ docId: string;
hideKey: boolean;
tbox: FormattedTextBox;
width: number;
height: number;
+ editable: boolean;
+ node: any;
+ getPos: any;
+ unclickable: () => boolean;
}
@observer
@@ -74,8 +100,12 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._fieldKey = this.props.fieldKey;
this._textBoxDoc = this.props.tbox.props.Document;
- if (this.props.docid) {
- DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ if (this.props.docId) {
+ DocServer.GetRefField(this.props.docId).then(
+ action(async dashDoc => {
+ dashDoc instanceof Doc && (this._dashDoc = dashDoc);
+ })
+ );
} else {
this._dashDoc = this.props.tbox.rootDoc;
}
@@ -116,7 +146,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
return (
<span
className="dashFieldView-fieldSpan"
- contentEditable={true}
+ contentEditable={!this.props.unclickable()}
style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
suppressContentEditableWarning={true}
defaultValue={strVal}
@@ -125,7 +155,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}
@@ -138,6 +174,10 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// we need to handle all key events on the input span or else they will propagate to prosemirror.
@action
fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
+ if (e.key === 'c' && (e.ctrlKey || e.metaKey)) {
+ navigator.clipboard.writeText(window.getSelection()?.toString() || '');
+ return;
+ }
if (e.key === 'Enter') {
// handle the enter key by "submitting" the current text to Dash's database.
this.updateText(span.textContent!, true);
@@ -153,6 +193,9 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
}
+ if (!this.props.editable) {
+ e.preventDefault();
+ }
e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
};
@@ -160,7 +203,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.
@@ -178,7 +220,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
} else {
if (Number(newText).toString() === newText) {
if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
- Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true);
+ Doc.SetInPlace(this._dashDoc!, this._fieldKey, Number(newText), true);
} else {
const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
@@ -207,7 +249,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey;
- this.props.tbox.props.addDocTab(alias, 'add:right');
+ this.props.tbox.props.addDocTab(alias, OpenWhere.addRight);
}
};
@@ -266,14 +308,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);
+ </Tooltip>
+ );
}
}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 98d611ca6..714ae458c 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 { IReactionDisposer, trace } 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,8 +11,12 @@ import React = require('react');
export class EquationView {
dom: HTMLDivElement; // container for label and value
-
+ root: any;
+ tbox: FormattedTextBox;
+ view: any;
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ this.tbox = tbox;
+ this.view = view;
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
@@ -21,17 +26,25 @@ 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);
+ this.root.unmount();
+ // ReactDOM.unmountComponentAtNode(this.dom);
}
- selectNode() {
+ setSelection() {
this._editor?.mathField.focus();
}
+ selectNode() {
+ this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
+ setTimeout(() => {
+ this._editor?.mathField.focus();
+ setTimeout(() => (this.tbox._applyingChange = ''));
+ });
+ }
deselectNode() {}
}
@@ -40,6 +53,7 @@ interface IEquationViewInternal {
tbox: FormattedTextBox;
width: number;
height: number;
+ getPos: () => number;
setEditor: (editor: EquationEditor | undefined) => void;
}
@@ -67,11 +81,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/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index 1683cc972..531a60297 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -1,10 +1,10 @@
-import { EditorView } from "prosemirror-view";
-import { EditorState } from "prosemirror-state";
-import { keymap } from "prosemirror-keymap";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { schema } from "./schema_rts";
-import { redo, undo } from "prosemirror-history";
-import { StepMap } from "prosemirror-transform";
+import { EditorView } from 'prosemirror-view';
+import { EditorState } from 'prosemirror-state';
+import { keymap } from 'prosemirror-keymap';
+import { baseKeymap, toggleMark } from 'prosemirror-commands';
+import { schema } from './schema_rts';
+import { redo, undo } from 'prosemirror-history';
+import { StepMap } from 'prosemirror-transform';
export class FootnoteView {
innerView: any;
@@ -20,38 +20,39 @@ export class FootnoteView {
this.getPos = getPos;
// The node's representation in the editor (empty, for now)
- this.dom = document.createElement("footnote");
+ this.dom = document.createElement('footnote');
- this.dom.addEventListener("pointerup", this.toggle, true);
+ this.dom.addEventListener('pointerup', this.toggle, true);
// These are used when the footnote is selected
this.innerView = null;
}
selectNode() {
- this.dom.classList.add("ProseMirror-selectednode");
+ this.dom.classList.add('ProseMirror-selectednode');
if (!this.innerView) this.open();
}
deselectNode() {
- this.dom.classList.remove("ProseMirror-selectednode");
+ this.dom.classList.remove('ProseMirror-selectednode');
if (this.innerView) this.close();
}
open() {
// Append a tooltip to the outer node
- const tooltip = this.dom.appendChild(document.createElement("div"));
- tooltip.className = "footnote-tooltip";
+ const tooltip = this.dom.appendChild(document.createElement('div'));
+ tooltip.className = 'footnote-tooltip';
// And put a sub-ProseMirror into that
this.innerView = new EditorView(tooltip, {
// You can use any node as an editor document
state: EditorState.create({
doc: this.node,
- plugins: [keymap(baseKeymap),
- keymap({
- "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
- "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
- "Mod-b": toggleMark(schema.marks.strong)
- }),
+ plugins: [
+ keymap(baseKeymap),
+ keymap({
+ 'Mod-z': () => undo(this.outerView.state, this.outerView.dispatch),
+ 'Mod-y': () => redo(this.outerView.state, this.outerView.dispatch),
+ 'Mod-b': toggleMark(schema.marks.strong),
+ }),
// new Plugin({
// view(newView) {
// // TODO -- make this work with RichTextMenu
@@ -59,7 +60,6 @@ export class FootnoteView {
// }
// })
],
-
}),
// This is the magic part
dispatchTransaction: this.dispatchInner.bind(this),
@@ -69,36 +69,39 @@ export class FootnoteView {
// footnote is node-selected (and thus DOM-selected) when
// the parent editor is focused.
e.stopPropagation();
- document.addEventListener("pointerup", this.ignore, true);
+ document.addEventListener('pointerup', this.ignore, true);
if (this.outerView.hasFocus()) this.innerView.focus();
- }) as any
- }
+ }) as any,
+ },
});
setTimeout(() => this.innerView?.docView.setSelection(0, 0, this.innerView.root, true), 0);
}
ignore = (e: PointerEvent) => {
e.stopPropagation();
- document.removeEventListener("pointerup", this.ignore, true);
- }
+ document.removeEventListener('pointerup', this.ignore, true);
+ };
toggle = () => {
+ console.log('TOGGLE');
if (this.innerView) this.close();
else this.open();
- }
+ };
close() {
+ console.log('CLOSE');
this.innerView?.destroy();
this.innerView = null;
- this.dom.textContent = "";
+ this.dom.textContent = '';
}
dispatchInner(tr: any) {
const { state, transactions } = this.innerView.state.applyTransaction(tr);
this.innerView.updateState(state);
- if (!tr.getMeta("fromOutside")) {
- const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
+ if (!tr.getMeta('fromOutside')) {
+ const outerTr = this.outerView.state.tr,
+ offsetMap = StepMap.offset(this.getPos() + 1);
for (const transaction of transactions) {
for (const step of transaction.steps) {
outerTr.step(step.map(offsetMap));
@@ -117,11 +120,11 @@ export class FootnoteView {
if (start !== null) {
let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
const overlap = start - Math.min(endA, endB);
- if (overlap > 0) { endA += overlap; endB += overlap; }
- this.innerView.dispatch(
- state.tr
- .replace(start, endB, node.slice(start, endA))
- .setMeta("fromOutside", true));
+ if (overlap > 0) {
+ endA += overlap;
+ endB += overlap;
+ }
+ this.innerView.dispatch(state.tr.replace(start, endB, node.slice(start, endA)).setMeta('fromOutside', true));
}
}
return true;
@@ -139,4 +142,3 @@ export class FootnoteView {
return true;
}
}
-
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index d3d8c47c0..cbe0a465d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -15,7 +15,7 @@ audiotag {
position: absolute;
cursor: pointer;
border-radius: 10px;
- width: 10px;
+ width: 12px;
margin-top: -2px;
font-size: 4px;
background: lightblue;
@@ -225,6 +225,8 @@ footnote::after {
.prosemirror-attribution {
font-size: 8px;
+ float: right;
+ display: inline;
}
.footnote-tooltip::before {
@@ -740,6 +742,8 @@ footnote::after {
.prosemirror-attribution {
font-size: 8px;
+ float: right;
+ display: inline;
}
.footnote-tooltip::before {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f61533619..361e000f9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,16 +11,16 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
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 { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
@@ -35,6 +35,7 @@ import { SnappingManager } from '../../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
+import { CollectionTreeView } from '../../collections/CollectionTreeView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -44,8 +45,10 @@ import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
+import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
+import { PinProps, PresBox } from '../trails';
import { DashDocCommentView } from './DashDocCommentView';
import { DashDocView } from './DashDocView';
import { DashFieldView } from './DashFieldView';
@@ -61,25 +64,14 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
-import { text } from 'body-parser';
-import { CollectionTreeView } from '../../collections/CollectionTreeView';
-import { DocumentViewInternal } from '../DocumentView';
const translateGoogleApi = require('translate-google-api');
-export interface FormattedTextBoxProps {
- makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
- xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
- yPadding?: number;
- noSidebar?: boolean;
- dontScale?: boolean;
- dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
-}
export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
@@ -90,13 +82,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
- static _canAnnotate = true;
static _hadSelection: boolean = false;
private _sidebarRef = React.createRef<SidebarAnnos>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
- private _applyingChange: string = '';
+ public _applyingChange: string = '';
private _searchIndex = 0;
private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
@@ -111,7 +102,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _rules: RichTextRules | undefined;
private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
private _forceDownNode: Node | undefined;
- private _downEvent: any;
private _downX = 0;
private _downY = 0;
private _break = true;
@@ -127,13 +117,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@computed get noSidebar() {
- return this.props.docViewPath?.()[this.props.docViewPath().length - 2]?.rootDoc.type === DocumentType.RTF || this.props.noSidebar || this.Document._noSidebar;
+ return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._noSidebar;
}
@computed get sidebarWidthPercent() {
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;
@@ -196,7 +186,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return '';
}
public static GetDocFromUrl(url: string) {
- return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid
+ return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
}
constructor(props: any) {
@@ -240,7 +230,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
- getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection');
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+ if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc;
+ const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true });
+ this.addDocument(anchor);
+ this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.rootDoc);
+ return anchor;
+ };
@action
setupAnchorMenu = () => {
@@ -248,23 +245,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
!this.layoutDoc.showSidebar && this.toggleSidebar();
- this._sidebarRef.current?.anchorMenuClick(this.getAnchor());
+ setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
};
AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
!this.layoutDoc.showSidebar && this.toggleSidebar();
- const anchor = this.getAnchor();
- const target = this._sidebarRef.current?.anchorMenuClick(anchor);
- if (target) {
- anchor.followLinkAudio = true;
- DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target));
- target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`);
- }
+ const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
+ setTimeout(() => {
+ const target = this._sidebarRef.current?.anchorMenuClick(anchor);
+ if (target) {
+ anchor.followLinkAudio = true;
+ DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target));
+ target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`);
+ }
+ });
};
AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
- this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch);
+ this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
return undefined;
});
- AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
+ AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
* This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
@@ -279,7 +278,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), this.getAnchor, targetCreator), e.pageX, e.pageY);
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
@@ -290,12 +289,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"'));
@@ -306,29 +306,35 @@ 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())));
+ const textChange = curText !== Cast(dataDoc[this.fieldKey], RichTextField)?.Text;
+ textChange && (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
- ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
+ 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
+ textChange && 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;
}
@@ -339,7 +345,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;
@@ -357,7 +363,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let linkTime;
let linkAnchor;
let link;
- DocListCast(this.dataDoc.links).forEach((l, i) => {
+ LinkManager.Links(this.dataDoc).forEach((l, i) => {
const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
@@ -394,15 +400,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.Links(this.props.Document).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);
@@ -446,8 +453,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
alink =
alink ??
- (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
- DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
+ (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
+ DocUtils.MakeLink(this.props.Document, target, { linkRelationship: LinkManager.AutoKeywords })!);
newAutoLinks.add(alink);
const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
@@ -501,12 +508,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const end = this._editorView.state.doc.nodeSize - 2;
this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
}
- if (FormattedTextBox.PasteOnLoad) {
- const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin');
- const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion');
- FormattedTextBox.PasteOnLoad = undefined;
- setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10);
- }
};
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
@@ -517,7 +518,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();
@@ -526,14 +527,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = this.getAnchor;
+ if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
const dragData = de.complete.docDragData;
if (dragData) {
const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0];
// 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
@@ -544,9 +545,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
width: target[WidthSym](),
height: target[HeightSym](),
title: 'dashDoc',
- docid: target[Id],
+ 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();
@@ -612,20 +616,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' });
@@ -683,20 +687,29 @@ 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));
+ LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
+ // 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);
};
@undoBatch
- pinToPres = (anchor: Doc) => this.props.pinToPres(anchor);
+ pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {});
@undoBatch
- makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin);
+ makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
+
+ @undoBatch
+ showTargetTrail = (anchor: Doc) => {
+ const trail = DocCast(anchor.presTrail);
+ if (trail) {
+ Doc.ActivePresentation = trail;
+ this.props.addDocTab(trail, OpenWhere.replaceRight);
+ }
+ };
- isPushpin = (anchor: Doc) => BoolCast(anchor.isPushpin);
+ isTargetToggler = (anchor: Doc) => BoolCast(anchor.followLinkToggle);
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
@@ -719,8 +732,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc);
AnchorMenu.Instance.Pinned = false;
AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc);
- AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc);
- AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc);
+ AnchorMenu.Instance.MakeTargetToggle = () => this.makeTargetToggle(anchor as Doc);
+ AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(anchor as Doc);
+ AnchorMenu.Instance.IsTargetToggler = () => this.isTargetToggler(anchor as Doc);
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
})
);
@@ -776,13 +790,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join('')));
this.updateHighlights();
},
- icon: 'expand-arrows-alt',
+ icon: FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'highlighter' : 'remove-format',
})
);
const uicontrols: ContextMenuProps[] = [];
- !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
- uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: !this.Document._noSidebar ? 'eye-slash' : 'eye' });
uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
uicontrols.push({
@@ -796,6 +809,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',
@@ -830,8 +844,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' });
- optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+ optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: !this.Document._singleLine ? 'grip-lines' : 'bars' });
+ optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
};
@@ -895,27 +909,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
// TODO: nda -- Look at how link anchors are added
- makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
+ makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean, addAsAnnotation?: boolean) {
const state = this._editorView?.state;
if (state) {
+ let selectedText = '';
const sel = state.selection;
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
const href = targetHref ?? Doc.localServerPath(anchor);
- if (anchor !== anchorDoc) this.addDocument(anchor);
+ if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
const allAnchors = [{ href, title, anchorId: anchor[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
- const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location });
+ const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location, noPreview });
tr = tr.addMark(pos, pos + node.nodeSize, link);
+ selectedText += (node as Node).textContent;
}
});
this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+ anchor.text = selectedText;
return anchor;
}
return anchorDoc ?? this.rootDoc;
@@ -923,12 +940,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return anchorDoc ?? this.rootDoc;
}
- scrollFocus = (textAnchor: Doc, smooth: boolean) => {
- let didToggle = false;
- if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {
- this.toggleSidebar(!smooth);
- didToggle = true;
+ getView = async (doc: Doc) => {
+ if (DocListCast(this.rootDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+ !this.SidebarShown && this.toggleSidebar(false);
+ setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
}
+ return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ };
+ focus = (textAnchor: Doc, options: DocFocusOptions) => {
+ const focusSpeed = options.zoomTime ?? 500;
const textAnchorId = textAnchor[Id];
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
@@ -967,7 +987,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const content = (ret.frag as any)?.content;
if ((ret.frag.size > 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
- smooth && (this._focusSpeed = 500);
+ !options.instant && (this._focusSpeed = focusSpeed);
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
if (ret.frag.firstChild) {
selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
@@ -979,11 +999,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
}
}
-
- return this._didScroll ? this._focusSpeed : didToggle ? 1 : undefined; // if we actually scrolled, then return some focusSpeed
};
- getScrollHeight = () => this.scrollHeight;
// if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc.
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
@@ -997,17 +1014,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
componentDidMount() {
!this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
- this._cachedLinks = DocListCast(this.Document.links);
+ this._cachedLinks = LinkManager.Links(this.Document);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
this._disposers.autoHeight = reaction(
() => 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(
@@ -1015,14 +1034,14 @@ 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);
}
},
{ fireImmediately: true }
);
this._disposers.links = reaction(
- () => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ () => LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
this._cachedLinks = newLinks;
@@ -1039,8 +1058,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) {
@@ -1118,7 +1138,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
if (duration) {
- smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
+ this._scrollStopper = smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0), 'ease', this._scrollStopper);
} else {
this._scrollRef.current.scrollTo({ top: pos });
}
@@ -1128,6 +1148,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
);
quickScroll = undefined;
this.tryUpdateScrollHeight();
+ setTimeout(this.tryUpdateScrollHeight, 250);
}
pushToGoogleDoc = async () => {
@@ -1177,7 +1198,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;
@@ -1229,61 +1250,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
- const cbe = event as ClipboardEvent;
- const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin');
- const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion');
- return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false;
+ const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
+ return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false;
};
- addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => {
+ addPdfReference = (pdfAnchorId: string) => {
const view = this._editorView!;
- if (pdfDocId && pdfRegionId) {
- DocServer.GetRefField(pdfDocId).then(pdfDoc => {
- DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
- if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) {
- setTimeout(async () => {
- const targetField = Doc.LayoutFieldKey(pdfDoc);
- const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations
- if (targetAnnotations) targetAnnotations.push(pdfRegion);
- else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion);
- });
-
- const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted');
- if (link) {
- const linkId = link[Id];
- const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) });
- const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0);
- if (slice) {
- view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
- } else {
- selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView()));
- }
- }
+ if (pdfAnchorId) {
+ DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
+ if (pdfAnchor instanceof Doc) {
+ const dashField = view.state.schema.nodes.paragraph.create({}, [
+ view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
+ view.state.schema.marks.linkAnchor.create({
+ allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }],
+ location: 'add:right',
+ title: `from: ${DocCast(pdfAnchor.context).title}`,
+ noPreview: true,
+ docref: false,
+ }),
+ view.state.schema.marks.pFontSize.create({ fontSize: '8px' }),
+ view.state.schema.marks.em.create({}),
+ ]),
+ ]);
+
+ const link = DocUtils.MakeLink(pdfAnchor, this.rootDoc, { linkRelationship: 'PDF pasted' });
+ if (link) {
+ view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
}
- });
+ }
});
return true;
}
return false;
-
- function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
- const nodes: Node[] = [];
- frag.forEach(node => nodes.push(marker(node)));
- return Fragment.fromArray(nodes);
- }
-
- function addLinkMark(node: Node, title: string, linkId: string) {
- if (!node.isText) {
- const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
- return node.copy(content);
- }
- const marks = [...node.marks];
- const linkIndex = marks.findIndex(mark => mark.type.name === 'link');
- const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
- const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true });
- marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
- return node.mark(marks);
- }
};
isActiveTab(el: Element | null | undefined) {
@@ -1304,9 +1302,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
});
}
_didScroll = false;
+ _scrollStopper: undefined | (() => void);
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();
@@ -1322,7 +1321,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
if (this._focusSpeed !== undefined) {
- scrollPos && smoothScroll(this._focusSpeed, scrollRef, scrollPos);
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
} else {
scrollRef.scrollTo({ top: scrollPos });
}
@@ -1357,7 +1356,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));
}
@@ -1392,14 +1392,17 @@ 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 && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
+ 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 ?? []),
- schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
+ ...(!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) })] : []),
...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
@@ -1407,7 +1410,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
];
+ if (FormattedTextBox.PasteOnLoad) {
+ const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+ FormattedTextBox.PasteOnLoad = undefined;
+ pdfAnchorId && this.addPdfReference(pdfAnchorId);
+ }
}
+ FormattedTextBox.DontSelectInitialText = false;
}
componentWillUnmount() {
@@ -1426,7 +1435,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
} else (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.Document.forceActive) e.stopPropagation();
- this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
+ this.tryUpdateScrollHeight(); // if a doc a fitWidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
if ((e.target as any).tagName === 'AUDIOTAG') {
e.preventDefault();
e.stopPropagation();
@@ -1438,7 +1447,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const func = () => {
const docView = DocumentManager.Instance.getDocumentView(audiodoc);
if (!docView) {
- this.props.addDocTab(audiodoc, 'add:bottom');
+ this.props.addDocTab(audiodoc, OpenWhere.addBottom);
setTimeout(func);
} else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
};
@@ -1453,7 +1462,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
this._downX = e.clientX;
this._downY = e.clientY;
- this._downEvent = true;
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
@@ -1462,33 +1470,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// but that's changed, so this shouldn't be needed.
//e.stopPropagation(); // if the text box is selected, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
- document.addEventListener('pointermove', this.onSelectMove);
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
};
- onSelectMove = (e: PointerEvent) => e.stopPropagation();
onSelectEnd = (e: PointerEvent) => {
document.removeEventListener('pointerup', this.onSelectEnd);
- 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._downEvent) return;
- this._downEvent = false;
- if (this.props.isContentActive(true) && !(e.nativeEvent as any).dash) {
- const editor = this._editorView!;
+ const editor = this._editorView!;
+ const state = editor?.state;
+ if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
+ if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
+ else if (this.props.isContentActive(true)) {
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
- !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
+ !this.props.isSelected(true) && editor.dispatch(state.tr.setSelection(new TextSelection(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);
- }
-
- if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
- e.stopPropagation();
+ FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+ if (pcords && pcords.inside > 0 && state.doc.nodeAt(pcords.inside)?.type === state.schema.nodes.dashDoc) {
+ return;
+ }
}
};
@action
@@ -1519,10 +1523,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
//applyDevTools.applyDevTools(this._editorView);
FormattedTextBox.Focused = this;
this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+ this.startUndoTypingBatch();
};
@observable public static Focused: FormattedTextBox | undefined;
onClick = (e: React.MouseEvent): void => {
+ if (!this.props.isContentActive()) return;
if ((e.nativeEvent as any).handledByInnerReactInstance) {
e.stopPropagation();
return;
@@ -1554,8 +1560,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this.props.isSelected(true)) {
// if text box is selected, then it consumes all click events
(e.nativeEvent as any).handledByInnerReactInstance = true;
- if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.
- // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)
this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
}
this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
@@ -1607,7 +1611,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
onBlur = (e: any) => {
if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
- this.autoLink();
+ if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
+ const stordMarks = this._editorView?.state.storedMarks?.slice();
+ this.autoLink();
+ if (this._editorView?.state.tr) {
+ const tr = stordMarks?.reduce((tr, m) => {
+ tr.addStoredMark(m);
+ return tr;
+ }, this._editorView.state.tr);
+ tr && this._editorView.dispatch(tr);
+ }
+ }
FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
@@ -1647,8 +1661,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
onKeyDown = (e: React.KeyboardEvent) => {
- if (e.altKey) {
+ if ((e.altKey || e.ctrlKey) && e.key === 't') {
e.preventDefault();
+ e.stopPropagation();
+ this.props.setTitleFocus?.();
return;
}
const state = this._editorView!.state;
@@ -1699,6 +1715,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._ignoreScroll = true;
this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
this._ignoreScroll = false;
+ e.stopPropagation();
+ e.preventDefault();
}
}
};
@@ -1706,9 +1724,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) {
@@ -1719,12 +1739,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
};
- fitContentsToBox = () => this.props.Document._fitContentsToBox;
+ fitContentsToBox = () => BoolCast(this.props.Document._fitContentsToBox);
sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
- // console.log("printting allSideBarDocs");
- // console.log(this.allSidebarDocs);
return this.addDocument(doc, sidebarKey);
};
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
@@ -1765,8 +1783,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-sidebar-handle"
onPointerDown={this.sidebarDown}
style={{
- backgroundColor: backgroundColor,
- color: color,
+ backgroundColor,
+ color,
opacity: annotated ? 1 : undefined,
}}>
<FontAwesomeIcon icon={'comment-alt'} />
@@ -1780,33 +1798,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
<SidebarAnnos
ref={this._sidebarRef}
{...this.props}
- fieldKey={this.fieldKey}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- ScreenToLocalTransform={this.sidebarScreenToLocal}
+ usePanelWidth={true}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
showSidebar={this.SidebarShown}
- PanelWidth={this.sidebarWidth}
- setHeight={this.setSidebarHeight}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
+ fieldKey={this.fieldKey}
+ PanelWidth={this.sidebarWidth}
+ setHeight={this.setSidebarHeight}
/>
) : (
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
<ComponentTag
- {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ {...this.props}
+ setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
- scaleField={this.SidebarKey + '-scale'}
+ viewField={this.SidebarKey}
isAnnotationOverlay={false}
select={emptyFunction}
+ isAnyChildContentActive={returnFalse}
NativeDimScaling={this.sidebarContentScaling}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.sidebarRemDocument}
@@ -1843,30 +1864,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : '';
- const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
- return styleFromString?.height === '0px' ? null : (
+ const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ return styleFromLayoutString?.height === '0px' ? null : (
<div
className="formattedTextBox-cont"
- onWheel={e => this.props.isContentActive() && e.stopPropagation()}
+ ref={r =>
+ r?.addEventListener(
+ 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
+ (e: WheelEvent) => {
+ if (this.props.isContentActive()) {
+ if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault();
+ e.stopPropagation();
+ }
+ },
+ { passive: false }
+ )
+ }
style={{
- transform: this.props.dontScale ? undefined : `scale(${scale})`,
- transformOrigin: this.props.dontScale ? undefined : 'top left',
- width: this.props.dontScale ? undefined : `${100 / scale}%`,
- height: this.props.dontScale ? undefined : `${100 / scale}%`,
+ ...(this.props.dontScale
+ ? {}
+ : {
+ transform: `scale(${scale})`,
+ transformOrigin: 'top left',
+ width: `${100 / scale}%`,
+ height: `${100 / scale}%`,
+ }),
+ transition: 'inherit',
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
- ...styleFromString,
+ color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
+ fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
+ fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily),
+ fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight),
+ ...styleFromLayoutString,
}}>
<div
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),
- background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
- color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
- fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
- fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any,
- fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'),
pointerEvents: interactive ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
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 31552cf1b..68b0488a2 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -2,13 +2,14 @@ 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';
import { Utils } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { SelectionManager } from '../../../util/SelectionManager';
+import { OpenWhere } from '../DocumentView';
import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -135,7 +136,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Command to create a new Tab with a PDF of all the command shortcuts
bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
- props.addDocTab(newDoc, 'add:right');
+ props.addDocTab(newDoc, OpenWhere.addRight);
});
//Commands to modify BlockType
@@ -143,7 +144,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 +174,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 2a77210ae..f0caa1f4f 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -16,10 +16,11 @@ import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { FieldViewProps } from '../FieldView';
-import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
+import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
+import { EquationBox } from '../EquationBox';
const { toggleMark } = require('prosemirror-commands');
@observer
@@ -29,7 +30,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
private _linkToRef = React.createRef<HTMLInputElement>();
@observable public view?: EditorView;
- public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined;
+ public editorProps: FieldViewProps | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
@@ -53,7 +54,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _activeFontColor: string = 'black';
@observable private showColorDropdown: boolean = false;
- @observable private activeHighlightColor: string = 'transparent';
+ @observable private _activeHighlightColor: string = 'transparent';
@observable private showHighlightDropdown: boolean = false;
@observable private currentLink: string | undefined = '';
@@ -64,6 +65,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
super(props);
runInAction(() => {
RichTextMenu.Instance = this;
+ this.updateMenu(undefined, undefined, props);
this._canFade = false;
this.Pinned = true;
});
@@ -87,6 +89,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@computed get fontColor() {
return this._activeFontColor;
}
+ @computed get fontHighlight() {
+ return this._activeHighlightColor;
+ }
@computed get fontFamily() {
return this._activeFontFamily;
}
@@ -96,6 +101,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@computed get textAlign() {
return this._activeAlignment;
}
+ _disposer: IReactionDisposer | undefined;
+ componentDidMount() {
+ this._disposer = reaction(
+ () => SelectionManager.Views(),
+ views => this.updateMenu(undefined, undefined, undefined)
+ );
+ }
+ componentWillUnmount() {
+ this._disposer?.();
+ }
@action
public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) {
@@ -103,13 +118,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return;
}
this.view = view;
- if (!view || !view.hasFocus()) {
- return;
- }
props && (this.editorProps = props);
// Don't do anything if the document/selection didn't change
- if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
+ if (view && view.hasFocus()) {
+ if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
+ }
// update active marks
const activeMarks = this.getActiveMarksOnSelection();
@@ -124,10 +138,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...';
- this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
@@ -144,13 +158,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
} else if (dontToggle) {
- toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
- const { from, $from, to, empty } = tx.selection;
- if (!tx.doc.rangeHasMark(from, to, mark.type)) {
- // hack -- should have just set the mark in the first place
- toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
- } else dispatch(tx);
- });
+ const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
+ dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
} else {
toggleMark(mark.type, mark.attrs)(state, dispatch);
}
@@ -159,7 +168,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) {
@@ -172,7 +181,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) {
@@ -188,17 +197,16 @@ 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 ?? [])];
- if (state.selection.empty) {
+ if (state.storedMarks !== null) {
+ } else if (state.selection.empty) {
const ref_node = this.reference_node(pos);
marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
} else {
@@ -207,13 +215,15 @@ 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));
});
+ } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
+ SelectionManager.Views().forEach(dv => StrCast(dv.rootDoc._fontSize) && activeSizes.add(StrCast(dv.rootDoc._fontSize)));
}
- 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) {
@@ -226,7 +236,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);
@@ -279,28 +289,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this._superscriptActive = false;
activeMarks.forEach(mark => {
+ // prettier-ignore
switch (mark.name) {
- case 'noAutoLinkAnchor':
- this._noLinkActive = true;
- break;
- case 'strong':
- this._boldActive = true;
- break;
- case 'em':
- this._italicsActive = true;
- break;
- case 'underline':
- this._underlineActive = true;
- break;
- case 'strikethrough':
- this._strikethroughActive = true;
- break;
- case 'subscript':
- this._subscriptActive = true;
- break;
- case 'superscript':
- this._superscriptActive = true;
- break;
+ case 'noAutoLinkAnchor': this._noLinkActive = true; break;
+ case 'strong': this._boldActive = true; break;
+ case 'em': this._italicsActive = true; break;
+ case 'underline': this._underlineActive = true; break;
+ case 'strikethrough': this._strikethroughActive = true; break;
+ case 'subscript': this._subscriptActive = true; break;
+ case 'superscript': this._superscriptActive = true; break;
}
});
}
@@ -342,14 +339,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
this.TextView.dataDoc.fontSize = fontSize;
this.view.focus();
- this.updateMenu(this.view, undefined, this.props);
} else {
const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
- this.updateMenu(this.view, undefined, this.props);
}
- }
+ } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
+ SelectionManager.Views().forEach(dv => (dv.rootDoc._fontSize = fontSize));
+ } else Doc.UserDoc()._fontSize = fontSize;
+ this.updateMenu(this.view, undefined, this.props);
};
setFontFamily = (family: string) => {
@@ -357,24 +355,26 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family });
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
- this.updateMenu(this.view, undefined, this.props);
- }
+ } else Doc.UserDoc()._fontFamily = family;
+ this.updateMenu(this.view, undefined, this.props);
};
- setHighlight(color: String, view: EditorView, dispatch: any) {
- const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color });
- if (view.state.selection.empty) return false;
- view.focus();
- this.setMark(highlightMark, view.state, dispatch, false);
+ setHighlight(color: string) {
+ if (this.view) {
+ const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color });
+ this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
+ this.view.focus();
+ } else Doc.UserDoc()._fontHighlight = color;
+ this.updateMenu(this.view, undefined, this.props);
}
- setColor(color: String, view: EditorView, dispatch: any) {
+ setColor(color: string) {
if (this.view) {
- const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color });
+ const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
- view.focus();
- this.updateMenu(this.view, undefined, this.props);
- }
+ this.view.focus();
+ } else Doc.UserDoc().fontColor = color;
+ this.updateMenu(this.view, undefined, this.props);
}
// TODO: remove doesn't work
@@ -430,7 +430,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)) {
@@ -568,7 +568,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action setActiveHighlight(color: string) {
- this.activeHighlightColor = color;
+ this._activeHighlightColor = color;
}
@action setCurrentLink(link: string) {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 1916b94bf..e691869cc 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -80,7 +80,7 @@ export class RichTextRules {
textDoc.inlineTextCount = numInlines + 1;
const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to
const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
- const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' });
+ const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _fitWidth: true, _autoHeight: true, _fontSize: '9px', title: 'inline comment' });
textDocInline.title = inlineFieldKey; // give the annotation its own title
textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
@@ -89,8 +89,8 @@ export class RichTextRules {
textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
textDoc[inlineFieldKey] = ''; // set a default value for the annotation
const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: textDocInline[Id], float: 'right' });
+ const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id] });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' });
const sm = state.storedMarks || undefined;
const replaced = node
? state.tr
@@ -248,19 +248,18 @@ 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) {
- DocServer.GetRefField(docid).then(docx => {
+ if (docId) {
+ DocServer.GetRefField(docId).then(docx => {
const rstate = this.TextBox.EditorView?.state;
const selection = rstate?.selection.$from.pos;
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);
- DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined);
+ const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500 }, docId);
+ DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { linkRelationship: 'portal to:portal from' });
const fstate = this.TextBox.EditorView?.state;
if (fstate && selection) {
@@ -275,8 +274,8 @@ 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 });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
+ 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);
}),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
@@ -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
@@ -303,16 +311,16 @@ export class RichTextRules {
const fieldKey = match[1] || '';
const fieldParam = match[2]?.replace('…', '...') || '';
const rawdocid = match[3]?.substring(1);
- const docid = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined;
- if (!fieldKey && !docid) return state.tr;
- docid &&
- DocServer.GetRefField(docid).then(docx => {
+ const docId = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined;
+ if (!fieldKey && !docId) return state.tr;
+ docId &&
+ DocServer.GetRefField(docId).then(docx => {
if (!(docx instanceof Doc && docx)) {
- Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docid);
+ Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docId);
}
});
const node = (state.doc.resolve(start) as any).nodeAfter;
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docid, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docId, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() });
const sm = state.storedMarks || undefined;
return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}),
@@ -327,7 +335,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 +354,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/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 01acc3de9..4e75d374c 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,14 @@ 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);
+ this.root.unmount();
+ // 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..3898490d3 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'),
};
},
},
@@ -95,12 +97,9 @@ export const marks: { [index: string]: MarkSpec } = {
return node.attrs.docref && node.attrs.title
? [
'div',
- ['span', `"`],
['span', 0],
- ['span', `"`],
- ['br'],
[
- 'a',
+ 'span',
{
...node.attrs,
class: 'prosemirror-attribution',
@@ -108,19 +107,8 @@ export const marks: { [index: string]: MarkSpec } = {
},
node.attrs.title,
],
- ['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];
- // ["div", { class: "prosemirror-anchor" },
- // ["span", { class: "prosemirror-linkBtn" },
- // ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
- // ["input", { class: "prosemirror-hrefoptions" }],
- // ],
- // ["div", { class: "prosemirror-links" }, ...node.attrs.allLinks.map((item: { href: string, title: string }) =>
- // ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
- // )]
- // ];
+ : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline; cursor: default` }, 0];
},
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 5142b7da6..6c9d5d31a 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',
@@ -164,7 +176,7 @@ export const nodes: { [index: string]: NodeSpec } = {
dashComment: {
attrs: {
- docid: { default: '' },
+ docId: { default: '' },
},
inline: true,
group: 'inline',
@@ -201,7 +213,7 @@ export const nodes: { [index: string]: NodeSpec } = {
title: { default: null },
float: { default: 'left' },
location: { default: 'add:right' },
- docid: { default: '' },
+ docId: { default: '' },
},
group: 'inline',
draggable: true,
@@ -234,7 +246,7 @@ export const nodes: { [index: string]: NodeSpec } = {
float: { default: 'right' },
hidden: { default: false }, // whether dashComment node has toggle the dashDoc's display off
fieldKey: { default: '' },
- docid: { default: '' },
+ docId: { default: '' },
alias: { default: '' },
},
group: 'inline',
@@ -249,8 +261,9 @@ export const nodes: { [index: string]: NodeSpec } = {
inline: true,
attrs: {
fieldKey: { default: '' },
- docid: { 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: {