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.tsx159
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx127
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx319
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.tsx3
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx93
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss5
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx626
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx33
-rw-r--r--src/client/views/nodes/formattedText/OrderedListView.tsx9
-rw-r--r--src/client/views/nodes/formattedText/ParagraphNodeSpec.ts100
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts129
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx281
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts144
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx29
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts43
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts37
17 files changed, 1064 insertions, 1081 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index a72ed1813..3ec49fa27 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,60 +1,11 @@
import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
-import { DocServer } from '../../../DocServer';
import * as React from 'react';
import { IReactionDisposer, computed, reaction } from 'mobx';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
import { NumCast } from '../../../../fields/Types';
-// creates an inline comment in a note when '>>' is typed.
-// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
-// the comment can be toggled on/off with the '<-' text anchor.
-export class DashDocCommentView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
-
- constructor(node: any, view: any, getPos: any) {
- this.node = node;
- 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.onkeypress = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
- (this as any).dom = this.dom;
- }
-
- setHeight = (hgt: number) => {
- !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
- };
-
- destroy() {
- this.root.unmount();
- }
- deselectNode() {
- this.dom.classList.remove('ProseMirror-selectednode');
- }
- selectNode() {
- this.dom.classList.add('ProseMirror-selectednode');
- }
-}
-
interface IDashDocCommentViewInternal {
docId: string;
view: any;
@@ -65,9 +16,6 @@ interface IDashDocCommentViewInternal {
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
_reactionDisposer: IReactionDisposer | undefined;
- @computed get _dashDoc() {
- return DocServer.GetRefField(this.props.docId);
- }
constructor(props: any) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -77,58 +25,62 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
}
componentDidMount(): void {
this._reactionDisposer?.();
- this._dashDoc.then(
- doc =>
- doc instanceof Doc &&
- (this._reactionDisposer = reaction(
+ this._dashDoc.then(doc => {
+ if (doc instanceof Doc) {
+ this._reactionDisposer = reaction(
() => NumCast((doc as Doc)._height),
hgt => this.props.setHeight(hgt),
- {
- fireImmediately: true,
- }
- ))
- );
+ { fireImmediately: true }
+ );
+ }
+ });
}
componentWillUnmount(): void {
this._reactionDisposer?.();
}
- onPointerLeaveCollapsed(e: any) {
+ @computed get _dashDoc() {
+ return DocServer.GetRefField(this.props.docId);
+ }
+
+ onPointerLeaveCollapsed = (e: any) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
- }
+ };
- onPointerEnterCollapsed(e: any) {
+ onPointerEnterCollapsed = (e: any) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
- }
+ };
- onPointerUpCollapsed(e: any) {
+ onPointerUpCollapsed = (e: any) => {
const target = this.targetNode();
if (target) {
const expand = target.hidden;
- const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+ const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
expand && this._dashDoc.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) {}
+ } catch (err) {
+ /* empty */
+ }
}, 0);
}
e.stopPropagation();
- }
+ };
- onPointerDownCollapsed(e: any) {
+ onPointerDownCollapsed = (e: any) => {
e.stopPropagation();
- }
+ };
targetNode = () => {
// search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
- const state = this.props.view.state;
+ const { state } = this.props.view;
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) {
@@ -141,7 +93,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
setTimeout(() => {
try {
this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
- } catch (e) {}
+ } catch (err) {
+ /* empty */
+ }
}, 0);
return undefined;
};
@@ -154,7 +108,60 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
onPointerLeave={this.onPointerLeaveCollapsed}
onPointerEnter={this.onPointerEnterCollapsed}
onPointerUp={this.onPointerUpCollapsed}
- onPointerDown={this.onPointerDownCollapsed}></span>
+ onPointerDown={this.onPointerDownCollapsed}
+ />
);
}
}
+
+// creates an inline comment in a note when '>>' is typed.
+// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
+// the comment can be toggled on/off with the '<-' text anchor.
+export class DashDocCommentView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ node: any;
+
+ constructor(node: any, view: any, getPos: any) {
+ this.node = node;
+ 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.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
+ (this as any).dom = this.dom;
+ }
+
+ setHeight = (hgt: number) => {
+ !this.node.attrs.reflow &&
+ DocServer.GetRefField(this.node.attrs.docId).then(doc => {
+ doc instanceof Doc && (this.dom.style.height = hgt + '');
+ });
+ };
+
+ destroy() {
+ this.root.unmount();
+ }
+ deselectNode() {
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ this.dom.classList.add('ProseMirror-selectednode');
+ }
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 7335c9286..93371685d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,77 +1,23 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { NodeSelection } from 'prosemirror-state';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
+import { ClientUtils, returnFalse } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
+import { DocUtils } from '../../../documents/DocUtils';
import { Transform } from '../../../util/Transform';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocumentView } from '../DocumentView';
-import { FocusViewOptions } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
import { FormattedTextBox } from './FormattedTextBox';
-var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
-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');
- this.dom.style.position = 'relative';
- this.dom.style.textIndent = '0';
- this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
- this.dom.style.height = node.attrs.height;
- this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
- (this.dom.style as any).float = node.attrs.float;
- this.dom.onkeypress = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- this.root = ReactDOM.createRoot(this.dom);
- this.root.render(
- <DashDocViewInternal
- docId={node.attrs.docId}
- embedding={node.attrs.embedding}
- width={node.attrs.width}
- height={node.attrs.height}
- hidden={node.attrs.hidden}
- fieldKey={node.attrs.fieldKey}
- tbox={tbox}
- view={view}
- node={node}
- getPos={getPos}
- />
- );
- }
- destroy() {
- setTimeout(() => {
- try {
- this.root.unmount();
- } catch {}
- });
- }
- deselectNode() {
- this.dom.style.backgroundColor = '';
- }
- selectNode() {
- this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
- }
-}
-
+const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
interface IDashDocViewInternal {
docId: string;
embedding: string;
@@ -84,6 +30,7 @@ interface IDashDocViewInternal {
node: any;
getPos: any;
}
+
@observer
export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewInternal> {
_spanRef = React.createRef<HTMLDivElement>();
@@ -157,7 +104,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
getDocTransform = () => {
if (!this._spanRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
+ const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._spanRef.current);
return new Transform(-translateX, -translateY, 1).scale(1 / scale);
};
outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
@@ -226,3 +173,61 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
);
}
}
+
+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');
+ this.dom.style.position = 'relative';
+ this.dom.style.textIndent = '0';
+ this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
+ (this.dom.style as any).float = node.attrs.float;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ this.root = ReactDOM.createRoot(this.dom);
+ this.root.render(
+ <DashDocViewInternal
+ docId={node.attrs.docId}
+ embedding={node.attrs.embedding}
+ width={node.attrs.width}
+ height={node.attrs.height}
+ hidden={node.attrs.hidden}
+ fieldKey={node.attrs.fieldKey}
+ tbox={tbox}
+ view={view}
+ node={node}
+ getPos={getPos}
+ />
+ );
+ }
+ destroy() {
+ setTimeout(() => {
+ try {
+ this.root.unmount();
+ } catch {
+ /* empty */
+ }
+ });
+ }
+ deselectNode() {
+ this.dom.style.backgroundColor = '';
+ }
+ selectNode() {
+ this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
+ }
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 17b8b53e7..9903d0e8a 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,15 +1,20 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import { NodeSelection } from 'prosemirror-state';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { Cast, DocCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
@@ -18,96 +23,73 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
import { FilterPanel } from '../../FilterPanel';
import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { OpenWhere } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import { DocData } from '../../../../fields/DocSymbols';
-import { NodeSelection } from 'prosemirror-state';
-export class DashFieldView {
- dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
- tbox: FormattedTextBox;
- getpos: any;
- @observable _nodeSelected = false;
- NodeSelected = () => this._nodeSelected;
+@observer
+export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
+ static Instance: DashFieldViewMenu;
+ static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+ static toggleFieldHide: () => void = emptyFunction;
+ static toggleValueHide: () => void = emptyFunction;
+ constructor(props: any) {
+ super(props);
+ DashFieldViewMenu.Instance = this;
+ }
- unclickable = () => !this.tbox._props.rootSelected?.() && 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) {
- makeObservable(this);
- const self = this;
- this.node = node;
- this.tbox = tbox;
- this.getpos = getPos;
- this.dom = document.createElement('div');
- this.dom.style.width = node.attrs.width;
- this.dom.style.height = node.attrs.height;
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- const tBox = this.tbox;
- this.dom.onkeypress = function (e: KeyboardEvent) {
- e.stopPropagation();
- };
- this.dom.onkeydown = function (e: KeyboardEvent) {
- e.stopPropagation();
- if (e.key === 'Tab') {
- e.preventDefault();
- const editor = tbox.EditorView;
- if (editor) {
- const state = editor.state;
- for (var i = self.getpos() + 1; i < state.doc.content.size; i++) {
- if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
- editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
- return;
- }
- }
- // tBox.setFocus(state.selection.to);
- }
- }
- };
- this.dom.onkeyup = function (e: any) {
- e.stopPropagation();
- };
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
+ showFields = (e: React.MouseEvent) => {
+ DashFieldViewMenu.createFieldView(e);
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
+ toggleFieldHide = () => {
+ DashFieldViewMenu.toggleFieldHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
+ toggleValueHide = () => {
+ DashFieldViewMenu.toggleValueHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
- 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}
- hideValue={node.attrs.hideValue}
- editable={node.attrs.editable}
- nodeSelected={this.NodeSelected}
- tbox={tbox}
- />
+ @observable _fieldKey = '';
+
+ @action
+ public show = (x: number, y: number, fieldKey: string) => {
+ this._fieldKey = fieldKey;
+ this.jumpTo(x, y, true);
+ const hideMenu = () => {
+ this.fadeOut(true);
+ document.removeEventListener('pointerdown', hideMenu, true);
+ };
+ document.addEventListener('pointerdown', hideMenu, true);
+ };
+ render() {
+ return this.getElement(
+ <>
+ <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.showFields}>
+ <FontAwesomeIcon icon="eye" size="sm" />
+ </button>
+ </Tooltip>
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+ <FontAwesomeIcon icon="bullseye" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ {this._fieldKey.startsWith('#') ? null : (
+ <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
+ <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
+ <FontAwesomeIcon icon="hashtag" size="sm" />
+ </button>
+ </Tooltip>
+ )}
+ </>
);
}
- destroy() {
- setTimeout(() => {
- try {
- this.root.unmount();
- } catch {}
- });
- }
- deselectNode() {
- runInAction(() => (this._nodeSelected = false));
- this.dom.classList.remove('ProseMirror-selectednode');
- }
- selectNode() {
- setTimeout(() => runInAction(() => (this._nodeSelected = true)), 100);
- this.dom.classList.add('ProseMirror-selectednode');
- }
}
-
interface IDashFieldViewInternal {
fieldKey: string;
docId: string;
@@ -137,7 +119,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
makeObservable(this);
this._fieldKey = this._props.fieldKey;
this._textBoxDoc = this._props.tbox.Document;
- const setDoc = action((doc: Doc) => (this._dashDoc = doc));
+ const setDoc = action((doc: Doc) => {
+ this._dashDoc = doc;
+ });
if (this._props.docId) {
DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -171,7 +155,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
return !this._dashDoc ? null : (
- <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
+ <div
+ onClick={action(() => {
+ this._expanded = !this._props.editable ? !this._expanded : true;
+ })}
+ style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
<SchemaTableCell
Document={this._dashDoc}
col={0}
@@ -188,20 +176,20 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
getFinfo={emptyFunction}
setColumnValues={returnFalse}
setSelectedColumnValues={returnFalse}
- allowCRs={true}
+ allowCRs
oneLine={!this._expanded && !this._props.nodeSelected()}
finishEdit={this.finishEdit}
transform={Transform.Identity}
menuTarget={null}
- autoFocus={true}
+ autoFocus
rootSelected={this._props.tbox._props.rootSelected}
/>
</div>
);
}
- createPivotForField = (e: React.MouseEvent) => {
- let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
+ createPivotForField = () => {
+ const container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
if (container) {
const embedding = Doc.MakeEmbedding(container.Document);
embedding._type_collection = CollectionViewType.Time;
@@ -220,7 +208,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
toggleFieldHide = undoable(
action(() => {
const editor = this._props.tbox.EditorView!;
- editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey ? true : false }));
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey }));
}),
'hideKey'
);
@@ -228,7 +216,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
toggleValueHide = undoable(
action(() => {
const editor = this._props.tbox.EditorView!;
- editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue ? true : false }));
+ editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue }));
}),
'hideValue'
);
@@ -244,11 +232,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
// clicking on the label creates a pivot view collection of all documents
// in the same collection. The pivot field is the fieldKey of this label
onPointerDownLabelSpan = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, moveEv => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
- DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
+ DashFieldViewMenu.Instance.show(moveEv.clientX, moveEv.clientY + 16, this._fieldKey);
const editor = this._props.tbox.EditorView!;
setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
});
@@ -278,7 +266,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}}>
{this._hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
+ {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : (this._dashDoc?.title ?? '') + ':') + this._fieldKey}
</span>
)}
{this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
@@ -294,65 +282,92 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
);
}
}
-@observer
-export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
- static Instance: DashFieldViewMenu;
- static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
- static toggleFieldHide: () => void = emptyFunction;
- static toggleValueHide: () => void = emptyFunction;
- constructor(props: any) {
- super(props);
- DashFieldViewMenu.Instance = this;
- }
-
- showFields = (e: React.MouseEvent) => {
- DashFieldViewMenu.createFieldView(e);
- DashFieldViewMenu.Instance.fadeOut(true);
- };
- toggleFieldHide = (e: React.MouseEvent) => {
- DashFieldViewMenu.toggleFieldHide();
- DashFieldViewMenu.Instance.fadeOut(true);
- };
- toggleValueHide = (e: React.MouseEvent) => {
- DashFieldViewMenu.toggleValueHide();
- DashFieldViewMenu.Instance.fadeOut(true);
- };
-
- @observable _fieldKey = '';
+export class DashFieldView {
+ dom: HTMLDivElement; // container for label and value
+ root: any;
+ node: any;
+ tbox: FormattedTextBox;
+ getpos: any;
+ @observable _nodeSelected = false;
+ NodeSelected = () => this._nodeSelected;
- @action
- public show = (x: number, y: number, fieldKey: string) => {
- this._fieldKey = fieldKey;
- this.jumpTo(x, y, true);
- const hideMenu = () => {
- this.fadeOut(true);
- document.removeEventListener('pointerdown', hideMenu, true);
+ unclickable = () => !this.tbox._props.rootSelected?.() && 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) {
+ makeObservable(this);
+ this.node = node;
+ this.tbox = tbox;
+ this.getpos = getPos;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onkeypress = function (e: KeyboardEvent) {
+ e.stopPropagation();
};
- document.addEventListener('pointerdown', hideMenu, true);
- };
- render() {
- 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="sm" />
- </button>
- </Tooltip>
- {this._fieldKey.startsWith('#') ? null : (
- <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
- <FontAwesomeIcon icon="bullseye" size="sm" />
- </button>
- </Tooltip>
- )}
- {this._fieldKey.startsWith('#') ? null : (
- <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
- <FontAwesomeIcon icon="hashtag" size="sm" />
- </button>
- </Tooltip>
- )}
- </>
+ this.dom.onkeydown = (e: KeyboardEvent) => {
+ e.stopPropagation();
+ if (e.key === 'Tab') {
+ e.preventDefault();
+ const editor = tbox.EditorView;
+ if (editor) {
+ const { state } = editor;
+ for (let i = this.getpos() + 1; i < state.doc.content.size; i++) {
+ if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
+ editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
+ return;
+ }
+ }
+ }
+ }
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ 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}
+ hideValue={node.attrs.hideValue}
+ editable={node.attrs.editable}
+ nodeSelected={this.NodeSelected}
+ tbox={tbox}
+ />
);
}
+ destroy() {
+ setTimeout(() => {
+ try {
+ this.root.unmount();
+ } catch {
+ /* empty */
+ }
+ });
+ }
+ deselectNode() {
+ runInAction(() => {
+ this._nodeSelected = false;
+ });
+ this.dom.classList.remove('ProseMirror-selectednode');
+ }
+ selectNode() {
+ setTimeout(
+ action(() => {
+ this._nodeSelected = true;
+ }),
+ 100
+ );
+ this.dom.classList.add('ProseMirror-selectednode');
+ }
}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index b4102e08e..d9b1a2cf8 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
import React, { Component, createRef } from 'react';
// Import JQuery, required for the functioning of the equation editor
@@ -5,11 +6,9 @@ import $ from 'jquery';
import './EquationEditor.scss';
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
window.jQuery = $;
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
require('mathquill/build/mathquill');
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index b90653acc..5167c8f2a 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
import { TextSelection } from 'prosemirror-state';
@@ -10,44 +11,6 @@ import EquationEditor from './EquationEditor';
import { FormattedTextBox } from './FormattedTextBox';
import { DocData } from '../../../../fields/DocSymbols';
-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;
- this.dom.style.position = 'relative';
- this.dom.style.display = 'inline-block';
- this.dom.onmousedown = function (e: any) {
- e.stopPropagation();
- };
-
- 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() {
- this.root.unmount();
- }
- 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() {}
-}
-
interface IEquationViewInternal {
fieldKey: string;
tbox: FormattedTextBox;
@@ -70,12 +33,12 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
this._textBoxDoc = props.tbox.Document;
}
- componentWillUnmount() {
- this._reactionDisposer?.();
- }
componentDidMount() {
this.props.setEditor(this._ref.current ?? undefined);
}
+ componentWillUnmount() {
+ this._reactionDisposer?.();
+ }
render() {
return (
@@ -100,12 +63,56 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
<EquationEditor
ref={this._ref}
value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
- onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)}
+ onChange={(str: any) => {
+ this._textBoxDoc[DocData][this._fieldKey] = str;
+ }}
autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
autoOperatorNames="sin cos tan"
- spaceBehavesLikeTab={true}
+ spaceBehavesLikeTab
/>
</div>
);
}
}
+
+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;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ 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() {
+ this.root.unmount();
+ }
+ 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() {}
+}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index b327e5137..4641da2e9 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -2,9 +2,9 @@ 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 { schema } from './schema_rts';
export class FootnoteView {
innerView: any;
@@ -100,8 +100,8 @@ export class FootnoteView {
this.innerView.updateState(state);
if (!tr.getMeta('fromOutside')) {
- const outerTr = this.outerView.state.tr,
- offsetMap = StepMap.offset(this.getPos() + 1);
+ const outerTr = this.outerView.state.tr;
+ const offsetMap = StepMap.offset(this.getPos() + 1);
for (const transaction of transactions) {
for (const step of transaction.steps) {
outerTr.step(step.map(offsetMap));
@@ -115,7 +115,7 @@ export class FootnoteView {
if (!node.sameMarkup(this.node)) return false;
this.node = node;
if (this.innerView) {
- const state = this.innerView.state;
+ const { state } = this.innerView;
const start = node.content.findDiffStart(state.doc.content);
if (start !== null) {
let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 38dd2e847..99b4a84fc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -349,8 +349,9 @@ footnote::before {
touch-action: none;
span {
font-family: inherit;
- background-color: inherit;
- display: contents; // fixes problem where extra space is added around <ol> lists when inside a prosemirror span
+ // background-color: inherit; // intended to allow texts to inherit background from list container, but this prevents css highlights e.,g highlight text from others
+ display: inline; // needs to be inline for search highlighting to appear
+ // display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span
}
blockquote {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 43010b2ed..321fdbb91 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
@@ -12,8 +13,9 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -23,37 +25,39 @@ import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { Docs } from '../../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { DocUtils } from '../../../documents/DocUtils';
import { DictationManager } from '../../../util/DictationManager';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
-import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { undoable, 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, ViewBoxInterface } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
+import { PinDocView, PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
-import { StyleProp } from '../../StyleProvider';
-import { media_state } from '../AudioBox';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
+import { StyleProp } from '../../StyleProp';
+import { styleFromLayoutString } from '../../StyleProvider';
+import { mediaState } from '../AudioBox';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
import { LinkInfo } from '../LinkDocPreview';
-import { PinProps, PresBox } from '../trails';
+import { OpenWhere } from '../OpenWhere';
import { DashDocCommentView } from './DashDocCommentView';
import { DashDocView } from './DashDocView';
import { DashFieldView } from './DashFieldView';
@@ -69,16 +73,17 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
// import * as applyDevTools from 'prosemirror-dev-tools';
-interface FormattedTextBoxProps extends FieldViewProps {
+export interface FormattedTextBoxProps extends FieldViewProps {
onBlur?: () => void; // callback when text loses focus
autoFocus?: boolean; // whether text should get input focus when created
}
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() implements ViewBoxInterface {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
+ // eslint-disable-next-line no-use-before-define
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
static _globalHighlightsCache: string = '';
@@ -97,7 +102,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _inDrop = false;
private _finishingLink = false;
private _searchIndex = 0;
- private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
private _undoTyping?: UndoManager.Batch;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -108,10 +112,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _keymap: any = undefined;
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 _downX = 0;
- private _downY = 0;
- private _downTime = 0;
private _break = true;
public ProseRef?: HTMLDivElement;
public get EditorView() {
@@ -152,10 +152,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
}
@computed get _recordingDictation() {
- return this.dataDoc?.mediaState === media_state.Recording;
+ return this.dataDoc?.mediaState === mediaState.Recording;
}
set _recordingDictation(value) {
- !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
+ !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this._props);
@@ -170,8 +170,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
keymap(baseKeymap),
new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
new Plugin({
- view(editorView) {
- return new FormattedTextBoxComment(editorView);
+ view(/* editorView */) {
+ return new FormattedTextBoxComment();
},
}),
],
@@ -183,26 +183,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private gptRes: string = '';
public static PasteOnLoad: ClipboardEvent | undefined;
- private static SelectOnLoad: Doc | undefined;
- public static SetSelectOnLoad(doc: Doc) {
- FormattedTextBox.SelectOnLoad = doc;
- }
public static DontSelectInitialText = false; // whether initial text should be selected or not
public static SelectOnLoadChar = '';
- public static IsFragment(html: string) {
- return html.indexOf('data-pm-slice') !== -1;
- }
- public static GetHref(html: string): string {
- const parser = new DOMParser();
- const parsedHtml = parser.parseFromString(html, 'text/html');
- if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
- return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
- }
- return '';
- }
- public static GetDocFromUrl(url: string) {
- return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
- }
constructor(props: FormattedTextBoxProps) {
super(props);
@@ -217,13 +199,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public RemoveLinkFromDoc(linkDoc?: Doc) {
this.unhighlightSearchTerms();
const state = this._editorView?.state;
- const a1 = linkDoc?.link_anchor_1 as Doc;
- const a2 = linkDoc?.link_anchor_2 as Doc;
+ const a1 = DocCast(linkDoc?.link_anchor_1);
+ const a2 = DocCast(linkDoc?.link_anchor_2);
if (state && a1 && a2 && this._editorView) {
this.removeDocument(a1);
this.removeDocument(a2);
- var allFoundLinkAnchors: any[] = [];
- state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
+ let allFoundLinkAnchors: any[] = [];
+ state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any /* , pos: number, parent: any */) => {
const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
return true;
@@ -246,14 +228,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
+ const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
if (!pinProps && this._editorView?.state.selection.empty) return rootDoc;
const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
this.addDocument(anchor);
this._finishingLink = true;
this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
this._finishingLink = false;
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.Document);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.Document);
return anchor;
};
@@ -261,11 +243,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setupAnchorMenu = () => {
AnchorMenu.Instance.Status = 'marquee';
- AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
+ AnchorMenu.Instance.OnClick = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
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) => {
+ AnchorMenu.Instance.OnAudio = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
@@ -275,8 +257,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
anchor.followLinkAudio = true;
let stopFunc: any;
const targetData = target[DocData];
- targetData.mediaState = media_state.Recording;
- DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+ targetData.mediaState = mediaState.Recording;
+ DictationManager.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+
const reactionDisposer = reaction(
() => target.mediaState,
dictation => {
@@ -286,12 +269,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
);
- target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
+ target.title = ComputedField.MakeFunction(`this.text_audioAnnotations_text.lastElement()`);
}
});
};
AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
+ this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight');
return undefined;
}, 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -305,7 +288,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
- FormattedTextBox.SetSelectOnLoad(target);
+ Doc.SetSelectOnLoad(target);
return target;
};
@@ -313,7 +296,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
- let ele: Opt<HTMLDivElement> = undefined;
+ let ele: Opt<HTMLDivElement>;
try {
const contents = window.getSelection()?.getRangeAt(0).cloneContents();
if (contents) {
@@ -321,7 +304,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
ele.append(contents);
}
this._selectionHTML = ele?.innerHTML;
- } catch (e) {}
+ } catch (e) {
+ /* empty */
+ }
};
leafText = (node: Node) => {
@@ -330,13 +315,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const fieldKey = StrCast(node.attrs.fieldKey);
return (
(node.attrs.hideKey ? '' : fieldKey + ':') + //
- (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
+ (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as FieldType))
);
}
return '';
};
dispatchTransaction = (tx: Transaction) => {
- if (this._editorView && (this._editorView as any).docView) {
+ if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this.tryUpdateDoc(false);
@@ -344,13 +329,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
tryUpdateDoc = (force: boolean) => {
- if (this._editorView && (this._editorView as any).docView) {
- const state = this._editorView.state;
- const dataDoc = this.dataDoc;
+ if (this._editorView) {
+ const { state } = this._editorView;
+ const { dataDoc } = this;
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
- const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype
const effectiveAcl = GetEffectiveAcl(dataDoc);
@@ -359,7 +343,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
const accumTags = [] as string[];
- state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
+ state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any /* , pos: number, parent: any */) => {
if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
accumTags.push(node.attrs.fieldKey);
}
@@ -386,7 +370,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
} else {
// if we've deleted all the text in a note driven by a template, then restore the template data
dataDoc[this.fieldKey] = undefined;
- this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data)));
+ this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData)?.Data)));
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
unchanged = false;
}
@@ -414,37 +398,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
insertTime = () => {
let linkTime;
let linkAnchor;
- let link;
- LinkManager.Links(this.dataDoc).forEach((l, i) => {
- const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) {
+ Doc.Links(this.dataDoc).forEach(l => {
+ const anchor = DocCast(l.link_anchor_1)?.annotationOn ? DocCast(l.link_anchor_1) : DocCast(l.link_anchor_2)?.annotationOn ? DocCast(l.link_anchor_2) : undefined;
+ if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
- link = l;
}
});
if (this._editorView && linkTime) {
- const state = this._editorView.state;
- const now = Date.now();
- let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
- if (!this._break && state.selection.to !== state.selection.from) {
- for (let i = state.selection.from; i <= state.selection.to; i++) {
- const pos = state.doc.resolve(i);
- const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
- if (um) {
- mark = um;
- break;
- }
- }
- }
-
- const path = (this._editorView.state.selection.$from as any).path;
- if (linkAnchor && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) {
+ const { state } = this._editorView;
+ const { path } = state.selection.$from as any;
+ if (linkAnchor && path[path.length - 3].type !== state.schema.nodes.code_block) {
const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000;
this._break = false;
- const from = state.selection.from;
- const value = this._editorView.state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
- const replaced = this._editorView.state.tr.insert(from - 1, value);
+ const { from } = state.selection;
+ const value = state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
+ const replaced = state.tr.insert(from - 1, value);
this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
}
}
@@ -452,7 +421,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = LinkManager.Links(this.Document).filter(
+ const oldAutoLinks = Doc.Links(this.Document).filter(
link =>
((!Doc.isTemplateForField(this.Document) &&
(!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
@@ -461,17 +430,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
link.link_relationship === LinkManager.AutoKeywords
); // prettier-ignore
if (this._editorView?.state.doc.textContent) {
- 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);
- Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
- tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+ let { tr } = this._editorView.state;
+ const { from, to } = this._editorView.state.selection;
+ const { autoLinkAnchor } = this._editorView.state.schema.marks;
+ tr = tr.removeMark(0, tr.doc.content.size, autoLinkAnchor);
+ Doc.MyPublishedDocs.filter(term => term.title).forEach(term => {
+ tr = this.hyperlinkTerm(tr, term, newAutoLinks);
+ });
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)));
this._editorView?.dispatch(tr);
}
- oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
+ oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc));
};
updateTitle = () => {
@@ -504,11 +473,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
* function of a freeform view that is driven by the text box's text. The include directive will copy the code of the published
* document into the code being evaluated.
*/
- hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ hyperlinkTerm = (trIn: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ let tr = trIn;
const editorView = this._editorView;
- if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
- const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
- var alink: Doc | undefined;
+ if (editorView && !Doc.AreProtosEqual(target, this.Document)) {
+ const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, '');
+ let alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
if (
!sel.$anchor.pos ||
@@ -520,11 +490,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
) {
const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
tr = tr.addMark(sel.from, sel.to, splitter);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ 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.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
alink =
alink ??
- (LinkManager.Links(this.Document).find(
+ (Doc.Links(this.Document).find(
link =>
Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.Document) && //
Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)
@@ -551,12 +521,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
};
highlightSearchTerms = (terms: string[], backward: boolean) => {
- if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
- const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
- const length = res[0].length;
- let tr = this._editorView.state.tr;
+ const { _editorView } = this;
+ if (_editorView && terms.some(t => t)) {
+ const { state } = _editorView;
+ let { tr } = state;
+ const mark = state.schema.mark(state.schema.marks.search_highlight);
+ const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+ const res = terms.filter(t => t).map(term => this.findInNode(_editorView, state.doc, term));
+ const { length } = res[0];
const flattened: TextSelection[] = [];
res.map(r => r.map(h => flattened.push(h)));
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
@@ -571,23 +543,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
- flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)));
- flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+ flattened.forEach((h: TextSelection, ind: number) => {
+ tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark);
+ });
+ flattened[lastSel] && _editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
}
};
unhighlightSearchTerms = () => {
- if (window.screen.width < 600) null;
- else if (this._editorView && (this._editorView as any).docView) {
- const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- const end = this._editorView.state.doc.nodeSize - 2;
- this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+ if (this._editorView) {
+ const { state } = this._editorView;
+ if (state) {
+ const mark = state.schema.mark(state.schema.marks.search_highlight);
+ const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+ const end = state.doc.nodeSize - 2;
+ this._editorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+ }
}
};
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
- const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
+ const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail() });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
};
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -631,7 +607,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
float: 'unset',
});
if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
- added = dragData.removeDocument?.(draggedDoc) ? true : false;
+ added = !!dragData.removeDocument?.(draggedDoc);
} else {
added = true;
}
@@ -643,9 +619,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._inDrop = true;
const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
pos && view.dispatch(view.state.tr.insert(pos, node));
- added = pos ? true : false; // pos will be null if you don't drop onto an actual text location
- } catch (e) {
- console.log('Drop failed', e);
+ added = !!pos; // pos will be null if you don't drop onto an actual text location
+ } catch (err) {
+ console.log('Drop failed', err);
added = false;
} finally {
this._inDrop = false;
@@ -677,29 +653,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
offset += (context.content as any).content[i].nodeSize;
}
- return null;
- } else {
- return null;
}
+ return null;
}
- //Recursively finds matches within a given node
+ // Recursively finds matches within a given node
findInNode(pm: EditorView, node: Node, find: string) {
let ret: TextSelection[] = [];
if (node.isTextblock) {
- let index = 0,
- foundAt;
+ let index = 0;
+ let foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
const regexp = new RegExp(find, 'i');
if (regexp) {
- var blockOffset = 0;
- for (var i = 0; i < node.childCount; i++) {
- var textContent = '';
+ let blockOffset = 0;
+ for (let i = 0; i < node.childCount; i++) {
+ let textContent = '';
while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) {
textContent += node.child(i).textContent;
i++;
}
+ // eslint-disable-next-line no-cond-assign
while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
ret.push(sel);
@@ -710,14 +685,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
} else {
- node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));
+ node.content.forEach(child => {
+ ret = ret.concat(this.findInNode(pm, child, find));
+ });
}
return ret;
}
updateHighlights = (highlights: string[]) => {
if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
- setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('')));
+ setTimeout(() => {
+ FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('');
+ });
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
if (!highlights.includes('Audio Tags')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
@@ -726,7 +705,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
if (highlights.includes('My Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
}
if (highlights.includes('Todo Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
@@ -745,21 +724,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
}
if (highlights.includes('By Recent Minute')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
if (highlights.includes('By Recent Hour')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
+ // eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
};
@observable _showSidebar = false;
@computed get SidebarShown() {
- return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+ return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
}
@action
@@ -781,7 +761,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this,
e,
this.sidebarMove,
- (e, movement, isClick) => !isClick && batch.end(),
+ (moveEv, movement, isClick) => !isClick && batch.end(),
() => {
this.toggleSidebar();
batch.end();
@@ -805,7 +785,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
deleteAnnotation = (anchor: Doc) => {
const batch = UndoManager.StartBatch('delete link');
- LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
+ Doc.DeleteLink?.(Doc.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);
@@ -817,7 +797,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
pinToPres = (anchor: Doc) => this._props.pinToPres(anchor, {});
@undoBatch
- makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
+ makeTargetToggle = (anchor: Doc) => {
+ anchor.followLinkToggle = !anchor.followLinkToggle;
+ };
@undoBatch
showTargetTrail = (anchor: Doc) => {
@@ -833,11 +815,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- const editor = this._editorView!;
- const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
- if (target && !(e.nativeEvent as any).dash) {
+ const editor = this._editorView;
+ if (editor && target && !(e.nativeEvent as any).dash) {
const hrefs = (target.dataset?.targethrefs as string)
?.trim()
.split(' ')
@@ -847,14 +828,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
.replace(Doc.localServerPath(), '')
.split('?')[0];
const deleteMarkups = undoBatch(() => {
- const sel = editor.state.selection;
- editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor));
+ const { selection } = editor.state;
+ editor.dispatch(editor.state.tr.removeMark(selection.from, selection.to, editor.state.schema.marks.linkAnchor));
});
e.persist();
anchorDoc &&
DocServer.GetRefField(anchorDoc).then(
action(anchor => {
- anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc);
+ anchor && DocumentView.SelectSchemaDoc(anchor as Doc);
AnchorMenu.Instance.Status = 'annotation';
AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc);
AnchorMenu.Instance.Pinned = false;
@@ -884,7 +865,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
this.Document.layout_fieldKey = 'layout_meta';
- setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+ setTimeout(() => {
+ this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
+ }, 50);
}),
icon: 'eye',
});
@@ -923,19 +906,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
appearanceItems.push({
description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle',
- event: () => (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar),
+ event: () => {
+ this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar;
+ },
icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
});
appearanceItems.push({
description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
- event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
+ event: () => {
+ this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI;
+ },
icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
});
!Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
appearanceItems.push({
description: 'Broadcast Message',
- event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text)),
+ event: () =>
+ DocServer.GetRefField('rtfProto').then(proto => {
+ proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text);
+ }),
icon: 'expand-arrows-alt',
});
@@ -957,27 +947,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: `Toggle auto update from template`, event: () => (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
+ optionItems.push({
+ description: `Toggle auto update from template`,
+ event: () => {
+ this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate'];
+ },
+ icon: 'star',
+ });
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
this._props.renderDepth &&
optionItems.push({
description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
- event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
+ event: () => {
+ this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR;
+ },
icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
});
!Doc.noviceMode &&
optionItems.push({
description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
- event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
+ event: () => {
+ this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
+ },
icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
});
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
const help = cm.findByDescription('Help...');
const helpItems = help && 'subitems' in help ? help.subitems : [];
- helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
+ helpItems.push({ description: `show markdown options`, event: () => RTFMarkup.Instance.setOpen(true), icon: <BsMarkdownFill /> });
!help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
- this._downX = this._downY = Number.NaN;
};
animateRes = (resIndex: number, newText: string) => {
@@ -990,7 +989,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
askGPT = action(async () => {
try {
- let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+ const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
if (!res) {
this.animateRes(0, 'Something went wrong.');
} else if (this._editorView) {
@@ -1016,10 +1015,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
breakupDictation = () => {
if (this._editorView && this._recordingDictation) {
- this.stopDictation(true);
+ this.stopDictation(/* true */);
this._break = true;
- const state = this._editorView.state;
- const to = state.selection.to;
+ const { state } = this._editorView;
+ const { to } = state.selection;
const updated = TextSelection.create(state.doc, to, to);
this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
if (this._recordingDictation) {
@@ -1037,7 +1036,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
};
- stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
+ stopDictation = (/* abort: boolean */) => DictationManager.Controls.stop(/* !abort */);
setDictationContent = (value: string) => {
if (this._editorView && this._recordingStart) {
@@ -1046,7 +1045,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' });
return this.addDocument(tanch) ? tanch : undefined;
};
- const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
+ const link = CreateLinkToActiveAudio(textanchorFunc, false).lastElement();
if (link) {
link[DocData].isDictation = true;
const audioanchor = Cast(link.link_anchor_2, Doc, null);
@@ -1065,7 +1064,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
}
}
- const from = this._editorView.state.selection.from;
+ const { from } = this._editorView.state.selection;
this._break = false;
const tr = this._editorView.state.tr.insertText(value);
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
@@ -1074,23 +1073,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// TODO: nda -- Look at how link anchors are added
makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean, addAsAnnotation?: boolean) {
- const state = this._editorView?.state;
- if (state) {
+ const { _editorView } = this;
+ if (_editorView) {
+ const { state } = _editorView;
let selectedText = '';
- const sel = state.selection;
+ const { selection } = state;
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) {
+ let tr = state.tr.addMark(selection.from, selection.to, splitter);
+ if (selection.from !== selection.to) {
const anchor =
anchorDoc ??
Docs.Create.ConfigDocument({
//
- title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')',
+ title: 'text(' + state.doc.textBetween(selection.from, selection.to) + ')',
annotationOn: this.dataDoc,
});
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ tr.doc.nodesBetween(selection.from, selection.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 ?? []));
@@ -1100,7 +1100,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
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._editorView!.dispatch(tr.removeMark(selection.from, selection.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
anchor.text = selectedText;
anchor.text_html = this._selectionHTML ?? selectedText;
@@ -1121,15 +1121,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
}
- return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ return new Promise<Opt<DocumentView>>(res => {
+ DocumentView.addViewRenderedCb(doc, dv => res(dv));
+ });
};
focus = (textAnchor: Doc, options: FocusViewOptions) => {
const focusSpeed = options.zoomTime ?? 500;
const textAnchorId = textAnchor[Id];
+ let start = 0;
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
let hadStart = start !== 0;
frag.forEach((node, index) => {
+ // eslint-disable-next-line no-use-before-define
const examinedNode = findAnchorNode(node, editor);
if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
nodes.push(examinedNode.node);
@@ -1161,7 +1165,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;
};
- let start = 0;
this._didScroll = false; // assume we don't need to scroll. if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
if (this._editorView && textAnchorId) {
const editor = this._editorView;
@@ -1177,13 +1180,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId;
addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
- setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
+ setTimeout(() => {
+ this._focusSpeed = undefined;
+ }, this._focusSpeed);
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
return focusSpeed;
- } else {
- return this._props.focus(this.Document, options);
}
+ return this._props.focus(this.Document, options);
}
+ return undefined;
};
// if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc.
@@ -1199,11 +1204,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
componentDidMount() {
!this._props.dontSelectOnLoad && this._props.setContentViewBox?.(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 = LinkManager.Links(this.Document);
+ this._cachedLinks = Doc.Links(this.Document);
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
() => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }),
- (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
+ autoHeight => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
);
this._disposers.highlights = reaction(
() => Array.from(FormattedTextBox._globalHighlights).slice(),
@@ -1212,21 +1217,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
this._disposers.width = reaction(
() => this._props.PanelWidth(),
- width => this.tryUpdateScrollHeight()
+ () => this.tryUpdateScrollHeight()
);
this._disposers.scrollHeight = reaction(
- () => ({ scrollHeight: this.scrollHeight, layout_autoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
- ({ width, scrollHeight, layout_autoHeight }) => width && layout_autoHeight && this.resetNativeHeight(scrollHeight),
+ () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
+ ({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight),
{ fireImmediately: true }
);
this._disposers.componentHeights = reaction(
// set the document height when one of the component heights changes and layout_autoHeight is on
- () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
- ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
+ () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
+ ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
if (
(!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
- layout_autoHeight &&
+ layoutAutoHeight &&
newHeight &&
newHeight !== this.layoutDoc.height &&
!this._props.dontRegisterView
@@ -1237,7 +1242,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
{ fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
);
this._disposers.links = reaction(
- () => LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ () => Doc.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;
@@ -1274,14 +1279,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._disposers.search = reaction(
() => Doc.IsSearchMatch(this.Document),
search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
- { fireImmediately: Doc.IsSearchMatchUnmemoized(this.Document) ? true : false }
+ { fireImmediately: !!Doc.IsSearchMatchUnmemoized(this.Document) }
);
this._disposers.selected = reaction(
() => this._props.rootSelected?.(),
action(selected => {
- //selected && setTimeout(() => this.prepareForTyping());
+ this.prepareForTyping();
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+ // eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
}
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
@@ -1299,7 +1305,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._disposers.record = reaction(
() => this._recordingDictation,
() => {
- this.stopDictation(true);
+ this.stopDictation(/* true */);
this._recordingDictation && this.recordDictation();
},
{ fireImmediately: true }
@@ -1322,10 +1328,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
clipboardTextSerializer = (slice: Slice): string => {
- let text = '',
- separated = true;
- const from = 0,
- to = slice.content.size;
+ let text = '';
+ let separated = true;
+ const from = 0;
+ const to = slice.content.size;
slice.content.nodesBetween(
from,
to,
@@ -1345,9 +1351,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return text;
};
- handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
+ handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => {
const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
- return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false;
+ return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId));
};
addPdfReference = (pdfAnchorId: string) => {
@@ -1378,7 +1384,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return false;
};
- isActiveTab(el: Element | null | undefined) {
+ isActiveTab(elIn: Element | null | undefined) {
+ let el = elIn;
while (el && el !== document.body) {
if (getComputedStyle(el).display === 'none') return false;
el = el.parentNode as any;
@@ -1390,7 +1397,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const self = this;
return new Plugin({
view(newView) {
- runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+ runInAction(() => {
+ self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+ });
return new RichTextMenuPlugin({ editorProps: this._props });
},
});
@@ -1415,7 +1424,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
if (this._focusSpeed !== undefined) {
- setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
+ setTimeout(() => {
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
+ });
} else {
scrollRef.scrollTo({ top: scrollPos });
}
@@ -1425,25 +1436,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- dashComment(node: any, view: any, getPos: any) {
- return new DashDocCommentView(node, view, getPos);
- },
- dashDoc(node: any, view: any, getPos: any) {
- return new DashDocView(node, view, getPos, self);
- },
- dashField(node: any, view: any, getPos: any) {
- return new DashFieldView(node, view, getPos, self);
- },
- equation(node: any, view: any, getPos: any) {
- return new EquationView(node, view, getPos, self);
- },
- summary(node: any, view: any, getPos: any) {
- return new SummaryView(node, view, getPos);
- },
- //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
- footnote(node: any, view: any, getPos: any) {
- return new FootnoteView(node, view, getPos);
- },
+ dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, // prettier-ignore
+ dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, // prettier-ignore
+ dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, // prettier-ignore
+ equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, // prettier-ignore
+ summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, // prettier-ignore
+ footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }, // prettier-ignore
},
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
@@ -1451,7 +1449,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const { state, dispatch } = this._editorView;
if (!rtfField) {
const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
- const startupText = Field.toString(dataDoc[fieldKey] as Field);
+ const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
if (startupText) {
dispatch(state.tr.insertText(startupText));
}
@@ -1465,17 +1463,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
(this._editorView as any).TextView = this;
}
- const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, Doc.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
if (selectOnLoad) {
- FormattedTextBox.SelectOnLoad = undefined;
+ Doc.SetSelectOnLoad(undefined);
FormattedTextBox.SelectOnLoadChar = '';
}
if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
this._props.select(false);
if (selLoadChar) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
- const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
@@ -1483,8 +1481,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const tr = tr2.setStoredMarks(storedMarks);
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
- } else if (curText && !FormattedTextBox.DontSelectInitialText) {
- selectAll(this._editorView.state, this._editorView?.dispatch);
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
+ } else if (!FormattedTextBox.DontSelectInitialText) {
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+ selectAll(this._editorView.state, (tx: Transaction) => {
+ this._editorView?.dispatch(tx.deleteSelection().addStoredMark(mark));
+ });
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
+ } else {
+ const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
+ const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+ const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
+ const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
+ const { tr } = this._editorView.state;
+ this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks));
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
}
}
@@ -1503,17 +1513,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// 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.
prepareForTyping = () => {
- if (!this._editorView) return;
- const docDefaultMarks = [
- ...(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)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []),
- ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
- ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
- ];
- this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
+ if (this._editorView) {
+ const { text, paragraph } = schema.nodes;
+ const selNode = this._editorView.state.selection.$anchor.node();
+ if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })];
+ this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor));
+ }
+ }
};
componentWillUnmount() {
@@ -1532,8 +1539,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onPointerDown = (e: React.PointerEvent): void => {
if ((e.nativeEvent as any).handledByInnerReactInstance) {
- return; //e.stopPropagation();
- } else (e.nativeEvent as any).handledByInnerReactInstance = true;
+ return; // e.stopPropagation();
+ }
+ (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 embedContainer (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.
@@ -1546,7 +1554,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// const timecode = NumCast(anchor.timecodeToShow, 0);
const audiodoc = anchor.annotationOn as Doc;
const func = () => {
- const docView = DocumentManager.Instance.getDocumentView(audiodoc);
+ const docView = DocumentView.getDocumentView(audiodoc);
if (!docView) {
this._props.addDocTab(audiodoc, OpenWhere.addBottom);
setTimeout(func);
@@ -1559,9 +1567,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
}
- this._downX = e.clientX;
- this._downY = e.clientY;
- this._downTime = Date.now();
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
@@ -1575,17 +1580,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.preventDefault();
}
};
- onSelectEnd = (e: PointerEvent) => {
- document.removeEventListener('pointerup', this.onSelectEnd);
- };
+ onSelectEnd = (): void => document.removeEventListener('pointerup', this.onSelectEnd);
onPointerUp = (e: React.PointerEvent): void => {
const state = this.EditorView?.state;
if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
- for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
- while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+ let clickTarget = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let { target } = e as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+ while (clickTarget && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, clickTarget?.dataset?.targethrefs, clickTarget?.dataset.linkdoc, clickTarget?.dataset.nopreview === 'true');
}
};
@action
@@ -1613,7 +1616,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
@action
onFocused = (e: React.FocusEvent): void => {
- //applyDevTools.applyDevTools(this._editorView);
+ // applyDevTools.applyDevTools(this._editorView);
this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
e.stopPropagation();
};
@@ -1624,10 +1627,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
return;
}
- if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
- this._forceDownNode = undefined;
- return;
- }
if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) {
// this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -1651,13 +1650,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._props.rootSelected?.()) {
// if text box is selected, then it consumes all click events
(e.nativeEvent as any).handledByInnerReactInstance = true;
- this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
+ this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, e.shiftKey);
}
this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
- this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node;
};
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
- hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
+ hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, selectOrderedList: boolean = false) {
this._forceUncollapse = false;
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
@@ -1710,9 +1708,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
this.autoLink();
if (this._editorView?.state.tr) {
- const tr = stordMarks?.reduce((tr, m) => {
- tr.addStoredMark(m);
- return tr;
+ const tr = stordMarks?.reduce((tr2, m) => {
+ tr2.addStoredMark(m);
+ return tr2;
}, this._editorView.state.tr);
tr && this._editorView.dispatch(tr);
}
@@ -1727,6 +1725,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
if (match) {
this.dataDoc.title_custom = true;
+ // eslint-disable-next-line prefer-destructuring
this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
}
@@ -1736,33 +1735,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
- const state = this._editorView!.state;
// if the text box blurs and none of its contents are focused(), then pass the blur along
setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
};
onKeyDown = (e: React.KeyboardEvent) => {
+ const { _editorView } = this;
+ if (!_editorView) return;
if ((e.altKey || e.ctrlKey) && e.key === 't') {
- e.preventDefault();
- e.stopPropagation();
this._props.setTitleFocus?.();
+ StopEvent(e);
return;
}
- const state = this._editorView!.state;
+ const { state } = _editorView;
if (!state.selection.empty && e.key === '%') {
this._rules!.EnteringStyle = true;
- e.preventDefault();
- e.stopPropagation();
+ StopEvent(e);
return;
}
if (state.selection.empty || !this._rules!.EnteringStyle) {
this._rules!.EnteringStyle = false;
}
- let stopPropagation = true;
- for (var i = state.selection.from; i <= state.selection.to; i++) {
+ for (let i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
- if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail()) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
e.preventDefault();
}
}
@@ -1770,27 +1767,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
case 'Escape':
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
return;
case 'Enter':
this.insertTime();
+ // eslint-disable-next-line no-fallthrough
case 'Tab':
e.preventDefault();
break;
- case 'c':
- this._editorView?.state.selection.empty && (stopPropagation = false);
+ case 'Space':
+ case 'Backspace':
break;
default:
- if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
- case ' ':
- if (e.code !== 'Space' && e.code !== 'Backspace') {
- [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
- this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+ if ([AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document))) {
+ const modified = Math.floor(Date.now() / 1000);
+ const mark = state.selection.$to.marks().find(m => m.type === schema.marks.user_mark && m.attrs.modified === modified);
+ _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified })));
}
break;
}
- if (stopPropagation) e.stopPropagation();
+ e.stopPropagation();
this.startUndoTypingBatch();
};
ondrop = (e: React.DragEvent) => {
@@ -1810,6 +1807,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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 && !SnappingManager.IsDragging) {
+ // eslint-disable-next-line no-use-before-define
const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
const toNum = (val: string) => Number(val.replace('px', ''));
const toHgt = (node: Element): number => {
@@ -1821,7 +1819,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const scrollHeight = this.ProseRef && proseHeight;
if (this._props.setHeight && !this._props.suppressSetHeight && 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.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
+ const setScrollHeight = () => {
+ this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight;
+ };
if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
@@ -1839,7 +1839,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- setSidebarHeight = (height: number) => (this.dataDoc[this.SidebarKey + '_height'] = height);
+ setSidebarHeight = (height: number) => {
+ this.dataDoc[this.SidebarKey + '_height'] = height;
+ };
sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
this._props
@@ -1857,10 +1859,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e,
returnFalse,
emptyFunction,
- action(e => (this._recordingDictation = !this._recordingDictation))
+ action(() => {
+ this._recordingDictation = !this._recordingDictation;
+ })
)
}>
- <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
+ <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon="microphone" size="sm" />
</div>
);
}
@@ -1885,15 +1889,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
- const ComponentTag: any = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+ const ComponentTag: any = tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- usePanelWidth={true}
+ usePanelWidth
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
showSidebar={this.SidebarShown}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -1906,8 +1911,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setHeight={this.setSidebarHeight}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}>
<ComponentTag
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
ref={this._sidebarTagRef as any}
setContentView={emptyFunction}
@@ -1930,8 +1936,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
renderDepth={this._props.renderDepth + 1}
setHeight={this.setSidebarHeight}
fitContentsToBox={this.fitContentsToBox}
- noSidebar={true}
- treeViewHideTitle={true}
+ noSidebar
+ treeViewHideTitle
fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
/>
</div>
@@ -1969,7 +1975,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}>
<div
className="formattedTextBox-alternateButton"
- onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
+ onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => this.cycleAlternateText())}
style={{
display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -1990,23 +1996,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@observable _isHovering = false;
onPassiveWheel = (e: WheelEvent) => {
if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
- if (this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) {
- // if the scrolled freeform is a child of the sidebar component, we need to let the event go through
- // so react can let the freeform view handle it. We prevent default to stop any containing views from scrolling
- e.preventDefault();
- }
return;
}
// 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)
if (this._props.isContentActive()) {
const scale = this._props.NativeDimScaling?.() || 1;
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
- const height = Number(styleFromLayoutString.height?.replace('px', ''));
+ const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
+ const height = Number(styleFromLayout.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
if (
- !Number.isNaN(height) &&
- (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+ !isNaN(height) &&
+ (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height || this._props.PanelHeight()) / scale) && //
(this._props.rootSelected?.() || this.isAnyChildContentActive())
) {
e.preventDefault();
@@ -2016,7 +2017,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
_oldWheel: any;
@computed get fontColor() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor);
}
@computed get fontSize() {
return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
@@ -2029,20 +2030,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
render() {
TraceMobx();
- const scale = this._props.NativeDimScaling?.() || 1; // * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = this._props.NativeDimScaling?.() || 1;
const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
- return styleFromLayoutString?.height === '0px' ? null : (
+ const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
+ return styleFromLayout?.height === '0px' ? null : (
<div
className="formattedTextBox"
onPointerEnter={action(() => {
this._isHovering = true;
this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true);
})}
- onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))}
+ onPointerLeave={action(() => { this.Document.isHovering = this._isHovering = false; })} // prettier-ignore
ref={r => {
this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
this._oldWheel = r;
@@ -2062,7 +2063,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
fontSize: this.fontSize,
fontFamily: this.fontFamily,
fontWeight: this.fontWeight,
- ...styleFromLayoutString,
+ ...styleFromLayout,
}}>
<div
className="formattedTextBox-cont"
@@ -2071,7 +2072,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
cursor: this._props.isContentActive() ? 'text' : undefined,
height: this._props.height ? 'max-content' : undefined,
overflow: this.layout_autoHeight ? 'hidden' : undefined,
- pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClickScript?.() ? undefined : 'none',
+ pointerEvents: Doc.ActiveTool === InkTool.None && !SnappingManager.ExploreMode ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -2084,7 +2085,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onDoubleClick={this.onDoubleClick}>
<div
className="formattedTextBox-outer"
- ref={r => (this._scrollRef = r)}
+ ref={r => {
+ this._scrollRef = r;
+ }}
style={{
width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
@@ -2112,3 +2115,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.RTF, {
+ layout: { view: FormattedTextBox, dataField: 'text' },
+ options: {
+ acl: '',
+ _height: 35,
+ _xMargin: 10,
+ _yMargin: 10,
+ _layout_nativeDimEditable: true,
+ _layout_reflowVertical: true,
+ _layout_reflowHorizontal: true,
+ defaultDoubleClick: 'ignore',
+ systemIcon: 'BsFileEarmarkTextFill',
+ },
+});
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index ce17af6ca..01c46edeb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,15 +1,16 @@
import { Mark, ResolvedPos } from 'prosemirror-model';
-import { EditorState, NodeSelection } from 'prosemirror-state';
+import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
-import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
+import { LinkInfo } from '../LinkDocPreview';
import { FormattedTextBox } from './FormattedTextBox';
import './FormattedTextBoxComment.scss';
import { schema } from './schema_rts';
export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined {
- return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== ClientUtils.CurrentUserEmail());
}
export function findUserMark(marks: readonly Mark[]): Mark | undefined {
return marks.find(m => m.attrs.userid);
@@ -18,20 +19,22 @@ export function findLinkMark(marks: readonly Mark[]): Mark | undefined {
return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor);
}
export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
- let before = 0,
- nbef = rpos.nodeBefore;
+ let before = 0;
+ let nbef = rpos.nodeBefore;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
+ // eslint-disable-next-line no-param-reassign
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
rpos && (nbef = rpos.nodeBefore);
}
return before;
}
export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
- let after = 0,
- naft = rpos.nodeAfter;
+ let after = 0;
+ let naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
+ // eslint-disable-next-line no-param-reassign
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
rpos && (naft = rpos.nodeAfter);
}
@@ -49,7 +52,7 @@ export class FormattedTextBoxComment {
static userMark: Mark;
static textBox: FormattedTextBox | undefined;
- constructor(view: any) {
+ constructor() {
if (!FormattedTextBoxComment.tooltip) {
const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div'));
const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div'));
@@ -79,10 +82,10 @@ export class FormattedTextBoxComment {
}
static showCommentbox(view: EditorView, nbef: number) {
- const state = view.state;
+ const { state } = view;
// These are in screen coordinates
- const start = view.coordsAtPos(state.selection.from - nbef),
- end = view.coordsAtPos(state.selection.from - nbef);
+ const start = view.coordsAtPos(state.selection.from - nbef);
+ const end = view.coordsAtPos(state.selection.from - nbef);
// The box in which the tooltip is positioned, to use as base
const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
// Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
@@ -109,14 +112,16 @@ export class FormattedTextBoxComment {
}
static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) {
- const state = view.state;
+ const { state } = view;
// 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) {
const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
const noselection = state.selection.$from === state.selection.$to;
let child: any = null;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any /* , pos: number, parent: any */) => {
+ !child && node.marks.length && (child = node);
+ });
const mark = child && findOtherUserMark(child.marks);
if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
@@ -131,7 +136,7 @@ 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 &&
+ // nbef &&
naft &&
LinkInfo.SetLinkInfo({
DocumentView: textBox.DocumentView,
diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx
index c3595e59b..dbc60f7bf 100644
--- a/src/client/views/nodes/formattedText/OrderedListView.tsx
+++ b/src/client/views/nodes/formattedText/OrderedListView.tsx
@@ -1,8 +1,7 @@
export class OrderedListView {
-
- update(node: any) {
- // if attr's of an ordered_list (e.g., bulletStyle) change,
+ update() {
+ // if attr's of an ordered_list (e.g., bulletStyle) change,
// return false forces the dom node to be recreated which is necessary for the bullet labels to update
- return false;
+ return false;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index 30da91710..8799964b3 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -1,18 +1,18 @@
+import { Node, DOMOutputSpec } from 'prosemirror-model';
import clamp from '../../../util/clamp';
import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
-import { Node, DOMOutputSpec } from 'prosemirror-model';
-//import type { NodeSpec } from './Types';
+// import type { NodeSpec } from './Types';
type NodeSpec = {
- attrs?: { [key: string]: any },
- content?: string,
- draggable?: boolean,
- group?: string,
- inline?: boolean,
- name?: string,
- parseDOM?: Array<any>,
- toDOM?: (node: any) => DOMOutputSpec,
+ attrs?: { [key: string]: any };
+ content?: string;
+ draggable?: boolean;
+ group?: string;
+ inline?: boolean;
+ name?: string;
+ parseDOM?: Array<any>;
+ toDOM?: (node: any) => DOMOutputSpec;
};
// This assumes that every 36pt maps to one indent level.
@@ -25,41 +25,18 @@ export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
const ALIGN_PATTERN = /(left|right|center|justify)/;
-// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
-// :: NodeSpec A plain paragraph textblock. Represented in the DOM
-// as a `<p>` element.
-export const ParagraphNodeSpec: NodeSpec = {
- attrs: {
- align: { default: null },
- color: { default: null },
- id: { default: null },
- indent: { default: null },
- inset: { default: null },
- lineSpacing: { default: null },
- // TODO: Add UI to let user edit / clear padding.
- paddingBottom: { default: null },
- // TODO: Add UI to let user edit / clear padding.
- paddingTop: { default: null },
- },
- content: 'inline*',
- group: 'block',
- parseDOM: [{ tag: 'p', getAttrs }],
- toDOM,
-};
+function convertMarginLeftToIndentValue(marginLeft: string): number {
+ const ptValue = convertToCSSPTValue(marginLeft);
+ return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL);
+}
function getAttrs(dom: HTMLElement): Object {
- const {
- lineHeight,
- textAlign,
- marginLeft,
- paddingTop,
- paddingBottom,
- } = dom.style;
+ const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;
let align = dom.getAttribute('align') || textAlign || '';
- align = ALIGN_PATTERN.test(align) ? align : "";
+ align = ALIGN_PATTERN.test(align) ? align : '';
- let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+ let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10);
if (!indent && marginLeft) {
indent = convertMarginLeftToIndentValue(marginLeft);
@@ -74,15 +51,7 @@ function getAttrs(dom: HTMLElement): Object {
}
function toDOM(node: Node): DOMOutputSpec {
- const {
- align,
- indent,
- inset,
- lineSpacing,
- paddingTop,
- paddingBottom,
- id,
- } = node.attrs;
+ const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs;
const attrs: { [key: string]: any } | null = {};
let style = '';
@@ -128,16 +97,29 @@ function toDOM(node: Node): DOMOutputSpec {
return ['p', attrs, 0];
}
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+export const ParagraphNodeSpec: NodeSpec = {
+ attrs: {
+ align: { default: null },
+ color: { default: null },
+ id: { default: null },
+ indent: { default: null },
+ inset: { default: null },
+ lineSpacing: { default: null },
+ // TODO: Add UI to let user edit / clear padding.
+ paddingBottom: { default: null },
+ // TODO: Add UI to let user edit / clear padding.
+ paddingTop: { default: null },
+ },
+ content: 'inline*',
+ group: 'block',
+ parseDOM: [{ tag: 'p', getAttrs }],
+ toDOM,
+};
+
export const toParagraphDOM = toDOM;
export const getParagraphNodeAttrs = getAttrs;
-export function convertMarginLeftToIndentValue(marginLeft: string): number {
- const ptValue = convertToCSSPTValue(marginLeft);
- return clamp(
- MIN_INDENT_LEVEL,
- Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
- MAX_INDENT_LEVEL
- );
-}
-
-export default ParagraphNodeSpec; \ No newline at end of file
+export default ParagraphNodeSpec;
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 03c902580..7a8b72be0 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,29 +1,29 @@
import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
import { redo, undo } from 'prosemirror-history';
import { Schema } from 'prosemirror-model';
-import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list';
+import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
-import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
-import { GetEffectiveAcl } from '../../../../fields/util';
+import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
import { Utils } from '../../../../Utils';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
+import { GetEffectiveAcl } from '../../../../fields/util';
import { Docs } from '../../../documents/Documents';
import { RTFMarkup } from '../../../util/RTFMarkup';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { OpenWhere } from '../DocumentView';
-import { Doc } from '../../../../fields/Doc';
-import { EditorView } from 'prosemirror-view';
+import { DocumentView } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
export type KeyMap = { [key: string]: any };
-export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
+export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
let mapStyle = assignedMapStyle;
- tx2.doc.descendants((node: any, offset: any, index: any) => {
+ tx2.doc.descendants((node: any, offset: any /* , index: any */) => {
if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
- const path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ const { path } = tx2.doc.resolve(offset) as any;
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) {
if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
depth++;
@@ -34,38 +34,44 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
return tx2;
};
-export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap {
const keys: { [key: string]: any } = {};
function bind(key: string, cmd: any) {
- if (mapKeys) {
- const mapped = mapKeys[key];
- if (mapped === false) return;
- if (mapped) key = mapped;
- }
keys[key] = cmd;
}
+ function onKey(): boolean | undefined {
+ // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway
+ // eslint-disable-next-line no-restricted-globals
+ return props.onKey?.(event, props);
+ }
+
const canEdit = (state: any) => {
- switch (GetEffectiveAcl(props.TemplateDataDocument)) {
+ const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]);
+ switch (permissions) {
case AclAugment:
- const prevNode = state.selection.$cursor.nodeBefore;
- const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
- if (prevUser != Doc.CurrentUserEmail) {
- return false;
+ {
+ const prevNode = state.selection.$cursor.nodeBefore;
+ const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid;
+ if (prevUser !== ClientUtils.CurrentUserEmail()) {
+ return false;
+ }
}
+ break;
+ default:
}
return true;
};
const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
- //History commands
+ // History commands
bind('Mod-z', undo);
bind('Shift-Mod-z', redo);
!mac && bind('Mod-y', redo);
- //Commands to modify Mark
+ // Commands to modify Mark
bind('Mod-b', toggleEditableMark(schema.marks.strong));
bind('Mod-B', toggleEditableMark(schema.marks.strong));
@@ -77,15 +83,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Mod-u', toggleEditableMark(schema.marks.underline));
bind('Mod-U', toggleEditableMark(schema.marks.underline));
- //Commands for lists
+ // Commands for lists
bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
- bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true));
- bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Ctrl-Tab', () => onKey() || true);
+ bind('Alt-Tab', () => onKey() || true);
+ bind('Meta-Tab', () => onKey() || true);
+ bind('Meta-Enter', () => onKey() || true);
bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
@@ -103,8 +109,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (
!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
const tx25 = updateBullets(tx2, schema);
- const ol_node = tx25.doc.nodeAt(range!.start)!;
- const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
+ const olNode = tx25.doc.nodeAt(range!.start)!;
+ const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks);
// when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -115,10 +121,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
console.log('bullet promote fail');
}
}
+ return undefined;
});
bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -132,15 +139,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
) {
console.log('bullet demote fail');
}
+ return undefined;
});
- //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 });
+ // Command to create a new Tab with a PDF of all the command shortcuts
+ bind('Mod-/', () => {
+ const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
props.addDocTab(newDoc, OpenWhere.addRight);
});
- //Commands to modify BlockType
+ // Commands to modify BlockType
bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
@@ -156,25 +164,25 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
}
- //Command to create a horizontal break line
+ // Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
- //Command to unselect all
+ // Command to unselect all
bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
});
- bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
- bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Alt-Enter', () => onKey() || true);
+ bind('Ctrl-Enter', () => onKey() || true);
bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
return true;
});
- bind('Cmd-?', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- RTFMarkup.Instance.open();
+ bind('Cmd-?', () => {
+ RTFMarkup.Instance.setOpen(true);
return true;
});
bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => {
@@ -189,14 +197,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -204,14 +212,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -219,14 +227,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const resolved = state.doc.resolve(state.selection.from) as any;
- const tr = state.tr;
+ const { tr } = state;
if (resolved?.parent.type.name === 'paragraph') {
tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
} else {
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
if (node) {
- tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]);
}
}
dispatch(tr);
@@ -236,7 +244,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => {
const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);
const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);
- const tr = state.tr;
+ const { tr } = state;
tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
dispatch(
tr.setSelection(
@@ -258,7 +266,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
if (
@@ -271,7 +279,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
dispatch(updateBullets(tx, schema));
if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
// gets rid of an extra paragraph when joining two list items together.
- joinBackward(view.state, (tx: Transaction) => view.dispatch(tx));
+ joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2));
}
})
) {
@@ -288,11 +296,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
bind('Backspace', backspace);
- //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
- //command to break line
+ // newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
+ // command to break line
const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
- if (props.onKey?.(event, props)) return true;
+ if (onKey()) return true;
if (!canEdit(state)) return true;
const trange = state.selection.$from.blockRange(state.selection.$to);
@@ -337,7 +345,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
!splitBlockKeepMarks(state, (tx3: Transaction) => {
const tonode = tx3.selection.$to.node();
if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
- const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+ const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []);
dispatch(tx4);
}
@@ -356,9 +364,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
bind('Enter', enter);
- //Command to create a blank space
- bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
+ // Command to create a blank space
+ bind('Space', () => {
+ const editDoc = props.TemplateDataDocument ?? props.Document[DocData];
+ if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;
return false;
});
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index cecf106a3..a612f3c65 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -3,30 +3,30 @@ import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { lift, wrapIn } from 'prosemirror-commands';
-import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
+import { Mark, MarkType } from 'prosemirror-model';
import { wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
-import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DocumentView } from '../DocumentView';
import { EquationBox } from '../EquationBox';
import { FieldViewProps } from '../FieldView';
import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
+
const { toggleMark } = require('prosemirror-commands');
@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
static get Instance() {
return RichTextMenu._instance?.menu;
@@ -115,8 +115,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
_disposer: IReactionDisposer | undefined;
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views.slice(),
- views => this.updateMenu(undefined, undefined, undefined, undefined)
+ () => DocumentView.Selected().slice(),
+ () => this.updateMenu(undefined, undefined, undefined, undefined)
);
}
componentWillUnmount() {
@@ -139,18 +139,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setActiveMarkButtons(this.getActiveMarksOnSelection());
const active = this.getActiveFontStylesOnSelection();
- const activeFamilies = active.activeFamilies;
- const activeSizes = active.activeSizes;
- const activeColors = active.activeColors;
- const activeHighlights = active.activeHighlights;
- const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
- const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
+ const { activeFamilies } = active;
+ const { activeSizes } = active;
+ const { activeColors } = active;
+ const { activeHighlights } = active;
+ const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc();
+ const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey);
+ const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));
this._activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
@@ -159,12 +160,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
- const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
- const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
- const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
- const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
- const fromRange = numberRange(state.selection.from).reverse();
- const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
+ const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
const hasMark = node.marks.some(m => m.type === mark.type);
@@ -172,25 +168,23 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
dispatch(updateBullets(markup, state.schema));
- } else {
- const state = this.view?.state;
- const tr = this.view?.state.tr;
- if (tr && state) {
- if (dontToggle) {
- 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);
- }
+ } else if (state) {
+ const { tr } = state;
+ if (dontToggle) {
+ 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);
}
}
+ this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
}
};
// finds font sizes and families in selection
getActiveAlignment() {
if (this.view && this.TextView?._props.rootSelected?.()) {
- const path = (this.view.state.selection.$from as any).path;
+ const { path } = this.view.state.selection.$from as any;
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) {
return path[i].attrs.align || 'left';
@@ -222,27 +216,28 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeColors = new Set<string>();
const activeHighlights = new Set<string>();
if (this.view && this.TextView?._props.rootSelected?.()) {
- const state = this.view.state;
+ const { state } = this.view;
const pos = this.view.state.selection.$from;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
marks.forEach(m => {
- 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.pFontFamily && activeFamilies.add(m.attrs.fontFamily);
+ m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
- m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
+ m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
});
- } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
- SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
+ } else if (DocumentView.Selected().some(dv => dv.ComponentView instanceof EquationBox)) {
+ DocumentView.Selected().forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
}
return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) };
}
@@ -254,26 +249,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return found;
}
- //finds all active marks on selection in given group
+ // finds all active marks on selection in given group
getActiveMarksOnSelection() {
if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
- const state = this.view.state;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ const { state } = this.view;
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
const pos = this.view.state.selection.$from;
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
- return markGroup.filter(mark_type => {
- const mark = state.schema.mark(mark_type);
+ return markGroup.filter(markType => {
+ const mark = state.schema.mark(markType);
return mark.isInSet(marks);
});
}
@@ -291,7 +287,6 @@ 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;
@@ -300,7 +295,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
case 'strikethrough': this._strikethroughActive = true; break;
case 'subscript': this._subscriptActive = true; break;
case 'superscript': this._superscriptActive = true; break;
- }
+ default:
+ } // prettier-ignore
});
}
@@ -353,53 +349,25 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- setFontSize = (fontSize: string) => {
+ setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
if (this.view) {
- 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[this.TextView.fieldKey + '_fontSize'] = fontSize;
- this.view.focus();
- } 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);
+ const { text, paragraph } = this.view.state.schema.nodes;
+ const selNode = this.view.state.selection.$anchor.node();
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
this.view.focus();
}
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
- } else Doc.UserDoc().fontSize = fontSize;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- };
-
- setFontFamily = (family: string) => {
- if (this.view) {
- const fmark = this.view.state.schema.marks.pFontFamily.create({ family });
+ const attrs: { [key: string]: string } = {};
+ attrs[fontField] = value;
+ const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
- } else Doc.UserDoc().fontFamily = family;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ } else {
+ Doc.UserDoc()[fontField] = value;
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ }
};
- 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, this.layoutDoc);
- }
-
- setColor(color: string) {
- if (this.view) {
- 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);
- this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
- } else Doc.UserDoc().fontColor = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- }
-
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
changeListType = (mapStyle: string) => {
@@ -407,7 +375,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const newMapStyle = active === mapStyle ? '' : mapStyle;
if (!this.view || newMapStyle === '') return;
- let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
+ const inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
if (inList) {
const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
@@ -428,7 +396,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
insertSummarizer(state: EditorState, dispatch: any) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
- const tr = state.tr;
+ const { tr } = state;
tr.addMark(state.selection.from, state.selection.to, mark);
const content = tr.selection.content();
const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
@@ -436,13 +404,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- vcenterToggle = (view: EditorView, dispatch: any) => {
+ vcenterToggle = () => {
this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
};
align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView?._props.rootSelected?.()) {
- var tr = view.state.tr;
- view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
+ let { tr } = view.state;
+ view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {
if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks);
return false;
@@ -455,56 +423,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- insetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
- outsetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
-
- indentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- const heading = false;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
- return false;
- }
- return true;
- });
- !heading && dispatch?.(tr);
- return true;
- }
-
- hangingIndentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ paragraphSetup(state: EditorState, dispatch: any, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
+ let { tr } = state;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+ const newValue = !value ?
+ (node.attrs[field] ? 0 : node.attrs[field] + 10) :
+ Math.max(0, value); // prettier-ignore
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, ...(field === 'inset' ? { inset: newValue } : { indent: newValue }) }, node.marks);
return false;
}
return true;
@@ -514,7 +440,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
insertBlockquote(state: EditorState, dispatch: any) {
- const path = (state.selection.$from as any).path;
+ const { path } = state.selection.$from as any;
if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
lift(state, dispatch);
} else {
@@ -547,13 +473,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- fillBrush(state: EditorState, dispatch: any) {
+ fillBrush() {
if (!this.view) return;
if (!Array.from(this.brushMarks.keys()).length) {
- const selected_marks = this.getMarksInSelection(this.view.state);
- if (selected_marks.size >= 0) {
- this.brushMarks = selected_marks;
+ const selectedMarks = this.getMarksInSelection(this.view.state);
+ if (selectedMarks.size >= 0) {
+ this.brushMarks = selectedMarks;
}
} else {
const { from, to, $from } = this.view.state.selection;
@@ -597,9 +523,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const button = (
<Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button">
- <FontAwesomeIcon icon="link" size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="antimodeMenu-button color-preview-button">
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
+ }
</Tooltip>
);
@@ -607,21 +536,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
<div className="dropdown link-menu">
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
- <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+ <button type="button" className="make-button" onPointerDown={() => this.makeLinkToURL(link)}>
Apply hyperlink
</button>
<div className="divider" />
- <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+ <button type="button" className="remove-button" onPointerDown={() => this.deleteLink()}>
Remove link
</button>
</div>
);
- return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+ // eslint-disable-next-line no-use-before-define
+ return <ButtonDropdown view={this.view} key="link button" button={button} dropdownContent={dropdownContent} openDropdownOnButton link />;
}
async getTextLinkTargetTitle() {
- if (!this.view) return;
+ if (!this.view) return undefined;
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === 'link');
@@ -633,15 +563,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
- const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc);
- const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc);
- const currentDoc = SelectionManager.Docs.lastElement();
- if (currentDoc && link_anchor_1 && link_anchor_2) {
- if (Doc.AreProtosEqual(currentDoc, link_anchor_1)) {
- return StrCast(link_anchor_2.title);
+ const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc);
+ const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc);
+ const currentDoc = DocumentView.Selected().lastElement().Document;
+ if (currentDoc && linkAnchor1 && linkAnchor2) {
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) {
+ return StrCast(linkAnchor2.title);
}
- if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) {
- return StrCast(link_anchor_1.title);
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor2)) {
+ return StrCast(linkAnchor1.title);
}
}
}
@@ -653,11 +583,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return link.attrs.title;
}
}
+ return undefined;
}
// TODO: should check for valid URL
@undoBatch
- makeLinkToURL = (target: string, lcoation: string) => {
+ makeLinkToURL = (target: string) => {
((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
};
@@ -673,7 +604,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0)
.forEach((aref: any) => {
const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
- anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ anchorId && DocServer.GetRefField(anchorId).then(linkDoc => Doc.DeleteLink?.(linkDoc as Doc));
});
}
}
@@ -736,7 +667,11 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
render() {
return (
- <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
+ <div
+ className="button-dropdown-wrapper"
+ ref={node => {
+ this.ref = node;
+ }}>
{!this._props.pdf ? (
<div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
{this._props.button}
@@ -747,9 +682,12 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
) : (
<>
{this._props.button}
- <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </button>
+ }
</>
)}
{this.showDropdown ? this._props.dropdownContent : null}
@@ -762,10 +700,11 @@ interface RichTextMenuPluginProps {
editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- render() {
- return null;
- }
+ // eslint-disable-next-line react/no-unused-class-component-methods
update(view: EditorView, lastState: EditorState | undefined) {
RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
}
+ render() {
+ return null;
+ }
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 42665830f..bf11dfe62 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,17 +1,17 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { ClientUtils } from '../../../../ClientUtils';
import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { NumCast, StrCast } from '../../../../fields/Types';
import { Utils } from '../../../../Utils';
-import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { DocUtils } from '../../../documents/DocUtils';
import { CollectionView } from '../../collections/CollectionView';
import { ContextMenu } from '../../ContextMenu';
-import { KeyValueBox } from '../KeyValueBox';
import { FormattedTextBox } from './FormattedTextBox';
import { wrappingInputRule } from './prosemirrorPatches';
import { RichTextMenu } from './RichTextMenu';
@@ -48,13 +48,9 @@ export class RichTextRules {
/^A\.\s$/,
schema.nodes.ordered_list,
// match => {
- () => {
- return { mapStyle: 'multi', bulletStyle: 1 };
- // return ({ order: +match[1] })
- },
- (match: any, node: any) => {
- return node.childCount + node.attrs.order === +match[1];
- },
+ () => ({ mapStyle: 'multi', bulletStyle: 1 }),
+ // return ({ order: +match[1] })
+ (match: any, node: any) => node.childCount + node.attrs.order === +match[1],
((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
),
@@ -70,7 +66,7 @@ export class RichTextRules {
// ``` create code block
new InputRule(/^```$/, (state, match, start, end) => {
- let $start = state.doc.resolve(start);
+ const $start = state.doc.resolve(start);
if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null;
// this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script
@@ -86,13 +82,13 @@ export class RichTextRules {
}),
// %<font-size> set the font size
- new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
+ new InputRule(/%([0-9]+)\s$/, (state, match, start, end) => {
const size = Number(match[1]);
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
}),
- //Create annotation to a field on the text document
- new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+ // Create annotation to a field on the text document
+ new InputRule(/>::$/, (state, match, start, end) => {
const creator = (doc: Doc) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
@@ -107,7 +103,7 @@ export class RichTextRules {
.insert(start, newNode)
.replaceRangeWith(start + 1, end + 2, dashDoc)
.insertText(' ', start + 2)
- .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ .setStoredMarks([...node.marks, ...(sm || [])])
: this.TextBox.EditorView.state.tr
);
};
@@ -117,8 +113,8 @@ export class RichTextRules {
return null;
}),
- //Create annotation to a field on the text document
- new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
+ // Create annotation to a field on the text document
+ new InputRule(/>>$/, (state, match, start, end) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
textDoc.inlineTextCount = numInlines + 1;
@@ -150,13 +146,13 @@ export class RichTextRules {
.insert(start, newNode)
.replaceRangeWith(start + 1, end + 1, dashDoc)
.insertText(' ', start + 2)
- .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ .setStoredMarks([...node.marks, ...(sm || [])])
: state.tr;
return replaced;
}),
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => {
+ new InputRule(/(%d|d)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -171,7 +167,7 @@ export class RichTextRules {
}),
// set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => {
+ new InputRule(/(%h|h)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -186,11 +182,11 @@ export class RichTextRules {
}),
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => {
+ new InputRule(/(%q|q)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
const pos = state.doc.resolve(start) as any;
if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
- const node = state.selection.node;
+ const { node } = state.selection;
return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
}
for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -205,46 +201,43 @@ export class RichTextRules {
}),
// center justify text
- new InputRule(new RegExp(/%\^/), (state, match, start, end) => {
+ new InputRule(/%\^/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// left justify text
- new InputRule(new RegExp(/%\[/), (state, match, start, end) => {
+ new InputRule(/%\[/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// right justify text
- new InputRule(new RegExp(/%\]/), (state, match, start, end) => {
+ new InputRule(/%\]/, (state, match, start, end) => {
const resolved = state.doc.resolve(start) as any;
if (resolved?.parent.type.name === 'paragraph') {
return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
// activate a style by name using prefix '%<color name>'
- new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
+ new InputRule(/%[a-zA-Z_]+$/, (state, match, start, end) => {
const color = match[0].substring(1, match[0].length);
const marks = RichTextMenu.Instance?._brushMap.get(color);
@@ -259,7 +252,7 @@ export class RichTextRules {
}
if (marks) {
const tr = state.tr.deleteRange(start, end);
- return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+ return marks ? Array.from(marks).reduce((tr2, m) => tr2.addStoredMark(m), tr) : tr;
}
const isValidColor = (strColor: string) => {
@@ -269,33 +262,33 @@ export class RichTextRules {
};
if (isValidColor(color)) {
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ fontColor: color }));
}
return null;
}),
// toggle alternate text UI %/
- new InputRule(new RegExp(/%\//), (state, match, start, end) => {
+ new InputRule(/%\//, (state, match, start, end) => {
setTimeout(() => this.TextBox.cycleAlternateText(true));
return state.tr.deleteRange(start, end);
}),
// stop using active style
- new InputRule(new RegExp(/%%$/), (state, match, start, end) => {
+ new InputRule(/%%$/, (state, match, start, end) => {
const tr = state.tr.deleteRange(start, end);
const marks = state.tr.selection.$anchor.nodeBefore?.marks;
return marks
? Array.from(marks)
.filter(m => m.type !== state.schema.marks.user_mark)
- .reduce((tr, m) => tr.removeStoredMark(m), tr)
+ .reduce((tr2, m) => tr2.removeStoredMark(m), tr)
: tr;
}),
// create a hyperlink to a titled document
// @(<doctitle>)
- new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
+ new InputRule(/@\(([a-zA-Z_@.? \-0-9]+)\)/, (state, match, start, end) => {
const docTitle = match[1];
const prefixLength = '@('.length;
if (docTitle) {
@@ -315,11 +308,11 @@ export class RichTextRules {
teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection))));
}
};
- const getTitledDoc = (docTitle: string) => {
- if (!DocServer.FindDocByTitle(docTitle)) {
- Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
+ const getTitledDoc = (title: string) => {
+ if (!Doc.FindDocByTitle(title)) {
+ Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
}
- const titledDoc = DocServer.FindDocByTitle(docTitle);
+ const titledDoc = Doc.FindDocByTitle(title);
return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
};
const target = getTitledDoc(docTitle);
@@ -335,22 +328,31 @@ export class RichTextRules {
// [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
// [@{this,doctitle,}.fieldKey]
new InputRule(
- new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
+ /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/,
(state, match, start, end) => {
const docTitle = match[1].substring(1).replace(/\.$/, '');
const fieldKey = match[2];
const assign = match[4] === ':' ? (match[4] = '') : match[4];
const value = match[5];
const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
- const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
+ const getTitledDoc = (title: string) => Doc.FindDocByTitle(title);
// if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
if (value?.includes(',') && !value.startsWith('((')) {
const values = value.split(',');
const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
} else if (value) {
- KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined:
- (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore
+ Doc.SetField(
+ this.Document,
+ fieldKey,
+ assign + value,
+ Doc.IsDataProto(this.Document) ? true : undefined,
+ assign.includes(':=')
+ ? undefined
+ : (gptval: FieldResult) => {
+ (dataDoc ? this.Document[DocData] : this.Document)[fieldKey] = gptval as string;
+ }
+ );
if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
}
const target = docTitle ? getTitledDoc(docTitle) : undefined;
@@ -361,9 +363,9 @@ export class RichTextRules {
),
// pass the contents between '((' and '))' to chatGPT and append the result
- new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => {
- var count = 0; // ignore first return value which will be the notation that chat is pending a result
- KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
+ new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => {
+ let count = 0; // ignore first return value which will be the notation that chat is pending a result
+ Doc.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
if (count) {
const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string));
tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length))));
@@ -376,7 +378,7 @@ export class RichTextRules {
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// @(wiki:title)
- new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => {
+ new InputRule(/@\(wiki:([a-zA-Z_@:.?\-0-9 ]+)\)$/, (state, match, start, end) => {
const title = match[1].trim().replace(/ /g, '_');
this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
@@ -392,7 +394,7 @@ export class RichTextRules {
// create an inline equation node
// %eq
- new InputRule(new RegExp(/%eq/), (state, match, start, end) => {
+ new InputRule(/%eq/, (state, match, start, end) => {
const fieldKey = 'math' + Utils.GenerateGuid();
this.TextBox.dataDoc[fieldKey] = 'y=';
const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
@@ -400,10 +402,10 @@ export class RichTextRules {
}),
// create an inline view of a tag stored under the '#' field
- new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
+ new InputRule(/#([a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- //this.Document[DocData]['#' + tag] = '#' + tag;
+ // this.Document[DocData]['#' + tag] = '#' + tag;
const tags = StrListCast(this.Document[DocData].tags);
if (!tags.includes(tag)) {
tags.push(tag);
@@ -417,29 +419,25 @@ export class RichTextRules {
}),
// # heading
- textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => {
- return { level: match[1].length };
- }),
+ textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })),
// set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => {
+ new InputRule(/[ti!x]$/, (state, match, start, end) => {
if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
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);
- 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) }))
+ .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
+ .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
: state.tr;
}),
- new InputRule(new RegExp(/%\(/), (state, match, start, end) => {
+ new InputRule(/%\(/, (state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks?.slice() || [];
const mark = state.schema.marks.summarizeInclusive.create();
@@ -452,9 +450,7 @@ export class RichTextRules {
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]);
}),
- new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
- }),
+ new InputRule(/%\)/, (state, match, start, end) => state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create())),
],
};
}
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 7ec296ed2..238267f6e 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -3,6 +3,15 @@ import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom/client';
import * as React from 'react';
+interface ISummaryView {}
+// currently nothing needs to be rendered for the internal view of a summary.
+// eslint-disable-next-line react/prefer-stateless-function
+export class SummaryViewInternal extends React.Component<ISummaryView> {
+ render() {
+ return null;
+ }
+}
+
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
@@ -12,11 +21,10 @@ export class SummaryView {
root: any;
constructor(node: any, view: any, getPos: any) {
- const self = this;
this.dom = document.createElement('span');
this.dom.className = this.className(node.attrs.visibility);
- this.dom.onpointerdown = function (e: any) {
- self.onPointerDown(e, node, view, getPos);
+ this.dom.onpointerdown = (e: any) => {
+ this.onPointerDown(e, node, view, getPos);
};
this.dom.onkeypress = function (e: any) {
e.stopPropagation();
@@ -32,8 +40,8 @@ export class SummaryView {
};
const js = node.toJSON;
- node.toJSON = function () {
- return js.apply(this, arguments);
+ node.toJSON = function (...args: any[]) {
+ return js.apply(this, args);
};
this.root = ReactDOM.createRoot(this.dom);
@@ -54,7 +62,8 @@ export class SummaryView {
const visited = new Set();
for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
let skip = false;
- view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+ // eslint-disable-next-line no-loop-func
+ view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
if (node.isLeaf && !visited.has(node) && !skip) {
if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
@@ -87,11 +96,3 @@ export class SummaryView {
this.dom.className = this.className(visible);
};
}
-
-interface ISummaryView {}
-// currently nothing needs to be rendered for the internal view of a summary.
-export class SummaryViewInternal extends React.Component<ISummaryView> {
- render() {
- return <> </>;
- }
-}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ccf7de4a1..6e1f325cf 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,5 @@
-import * as React from 'react';
-import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
-import { Doc } from '../../../../fields/Doc';
+import { DOMOutputSpec, MarkSpec } from 'prosemirror-model';
+import { ClientUtils } from '../../../../ClientUtils';
import { Utils } from '../../../../Utils';
const emDOM: DOMOutputSpec = ['em', 0];
@@ -13,7 +12,7 @@ export const marks: { [index: string]: MarkSpec } = {
attrs: {
id: { default: '' },
},
- toDOM(node: any) {
+ toDOM() {
return ['div', { className: 'dummy' }, 0];
},
},
@@ -45,7 +44,7 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM(node: any) {
const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
- return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+ return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /* 'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
},
},
noAutoLinkAnchor: {
@@ -61,7 +60,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
],
- toDOM(node: any) {
+ toDOM() {
return ['span', { 'data-noAutoLink': 'true' }, 0];
},
},
@@ -128,29 +127,29 @@ export const marks: { [index: string]: MarkSpec } = {
/* FONTS */
pFontFamily: {
- attrs: { family: { default: '' } },
+ attrs: { fontFamily: { default: '' } },
parseDOM: [
{
tag: 'span',
getAttrs(dom: any) {
const cstyle = getComputedStyle(dom);
if (cstyle.font) {
- if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' };
- if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' };
- if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' };
- if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' };
- if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' };
- if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' };
+ if (cstyle.font.indexOf('Times New Roman') !== -1) return { fontFamily: 'Times New Roman' };
+ if (cstyle.font.indexOf('Arial') !== -1) return { fontFamily: 'Arial' };
+ if (cstyle.font.indexOf('Georgia') !== -1) return { fontFamily: 'Georgia' };
+ if (cstyle.font.indexOf('Comic Sans') !== -1) return { fontFamily: 'Comic Sans MS' };
+ if (cstyle.font.indexOf('Tahoma') !== -1) return { fontFamily: 'Tahoma' };
+ if (cstyle.font.indexOf('Crimson') !== -1) return { fontFamily: 'Crimson Text' };
}
- return { family: '' };
+ return { fontFamily: '' };
},
},
],
- toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]),
+ toDOM: node => (node.attrs.fontFamily ? ['span', { style: `font-family: "${node.attrs.fontFamily}";` }] : ['span', 0]),
},
// :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
pFontColor: {
- attrs: { color: { default: '' } },
+ attrs: { fontColor: { default: '' } },
inclusive: true,
parseDOM: [
{
@@ -160,24 +159,24 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
],
- toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]),
+ toDOM: node => (node.attrs.fontColor ? ['span', { style: 'color:' + node.attrs.fontColor }] : ['span', 0]),
},
- marker: {
+ pFontHighlight: {
attrs: {
- highlight: { default: 'transparent' },
+ fontHighlight: { default: 'transparent' },
},
inclusive: true,
parseDOM: [
{
tag: 'span',
getAttrs(dom: any) {
- return { highlight: dom.getAttribute('backgroundColor') };
+ return { fontHighlight: dom.getAttribute('background-color') };
},
},
],
toDOM(node: any) {
- return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
+ return node.attrs.fontHighlight ? ['span', { style: 'background-color:' + node.attrs.fontHighlight }] : ['span', { style: 'background-color: transparent' }];
},
},
@@ -336,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = {
const min = Math.round(node.attrs.modified / 60);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
+ const remote = node.attrs.userid !== ClientUtils.CurrentUserEmail() ? ' UM-remote' : '';
return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0];
},
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 62b8b03d6..5bf942218 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -2,16 +2,17 @@ import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
import { listItem, orderedList } from 'prosemirror-schema-list';
import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
import { DocServer } from '../../../DocServer';
-import { Doc, Field } from '../../../../fields/Doc';
+import { Doc, Field, FieldType } from '../../../../fields/Doc';
+import { schema } from './schema_rts';
-const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
- hrDOM: DOMOutputSpec = ['hr'],
- preDOM: DOMOutputSpec = ['pre', ['code', 0]],
- brDOM: DOMOutputSpec = ['br'],
- ulDOM: DOMOutputSpec = ['ul', 0];
+const blockquoteDOM: DOMOutputSpec = ['blockquote', 0];
+const hrDOM: DOMOutputSpec = ['hr'];
+const preDOM: DOMOutputSpec = ['pre', ['code', 0]];
+const brDOM: DOMOutputSpec = ['br'];
+// const ulDOM: DOMOutputSpec = ['ul', 0];
-function formatAudioTime(time: number) {
- time = Math.round(time);
+function formatAudioTime(timeIn: number) {
+ const time = Math.round(timeIn);
const hours = Math.floor(time / 60 / 60);
const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
@@ -266,7 +267,7 @@ export const nodes: { [index: string]: NodeSpec } = {
hideValue: { default: false },
editable: { default: true },
},
- leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
+ leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as FieldType),
group: 'inline',
draggable: false,
toDOM(node) {
@@ -353,7 +354,7 @@ export const nodes: { [index: string]: NodeSpec } = {
},
{
style: 'list-style-type=disc',
- getAttrs(dom: any) {
+ getAttrs() {
return { mapStyle: 'bullet' };
},
},
@@ -373,10 +374,10 @@ export const nodes: { [index: string]: NodeSpec } = {
],
toDOM(node: Node) {
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
if (node.attrs.mapStyle === 'bullet') {
return [
@@ -421,10 +422,10 @@ export const nodes: { [index: string]: NodeSpec } = {
},
],
toDOM(node: Node) {
- const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
- const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
- const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
- const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
return [
'li',