aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/DataViz.tsx20
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx12
-rw-r--r--src/client/views/nodes/button/textButton/TextButton.tsx23
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx77
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx181
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx229
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx99
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1008
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx107
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts213
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx361
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts574
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx73
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts382
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts361
15 files changed, 2028 insertions, 1692 deletions
diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx
new file mode 100644
index 000000000..df4c8f937
--- /dev/null
+++ b/src/client/views/nodes/DataViz.tsx
@@ -0,0 +1,20 @@
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import './DataViz.scss';
+import { FieldView, FieldViewProps } from './FieldView';
+
+@observer
+export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(DataVizBox, fieldKey);
+ }
+
+ render() {
+ return (
+ <div>
+ <div>Hi</div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 798759c01..28874220a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,14 +1,13 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { StringIterator } from 'lodash';
+import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
-import { createSchema } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { WebField } from '../../../../fields/URLField';
@@ -33,9 +32,6 @@ import { RichTextMenu } from '../formattedText/RichTextMenu';
import { WebBox } from '../WebBox';
import { FontIconBadge } from './FontIconBadge';
import './FontIconBox.scss';
-const FontIconSchema = createSchema({
- icon: "string",
-});
export enum ButtonType {
TextButton = "textBtn",
@@ -630,11 +626,7 @@ ScriptingGlobals.add(function setBulletList(mapStyle: "bullet" | "decimal", chec
if (active === mapStyle) return Colors.MEDIUM_BLUE;
return "transparent";
}
- if (editorView) {
- const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
- editorView?.state && RichTextMenu.Instance.changeListType(
- editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }));
- }
+ editorView?.state && RichTextMenu.Instance.changeListType(mapStyle);
});
// toggle: Set overlay status of selected document
diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx
index e18590a95..5d7d55863 100644
--- a/src/client/views/nodes/button/textButton/TextButton.tsx
+++ b/src/client/views/nodes/button/textButton/TextButton.tsx
@@ -9,9 +9,22 @@ export class TextButton extends Component<IButtonProps> {
// Determine the type of toggle button
const buttonText: boolean = BoolCast(this.props.rootDoc.switchToggle);
- return (<div className={`menuButton ${this.props.type}`} style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
- {this.props.label}
- </div>);
+ return (
+ <div
+ className={`menuButton ${this.props.type}`}
+ style={{
+ opacity: 1,
+ backgroundColor: this.props.backgroundColor,
+ color: this.props.color,
+ }}
+ >
+ <FontAwesomeIcon
+ className={`fontIconBox-icon-${this.props.type}`}
+ icon={this.props.icon}
+ color={this.props.color}
+ />
+ {this.props.label}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index 5c75a589a..40dd6fbc7 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,37 +1,44 @@
-import { TextSelection } from "prosemirror-state";
+import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../../fields/Doc";
-import { DocServer } from "../../../DocServer";
-import React = require("react");
-
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
+import React = require('react');
// 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 {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any) {
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.fontWeight = "bold";
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
- ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ 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();
+ };
+
+ ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this.dom);
+ (this as any).dom = this.dom;
}
destroy() {
- ReactDOM.unmountComponentAtNode(this._fieldWrapper);
+ ReactDOM.unmountComponentAtNode(this.dom);
}
- selectNode() { }
+ selectNode() {}
}
interface IDashDocCommentViewInternal {
@@ -40,8 +47,7 @@ interface IDashDocCommentViewInternal {
getPos: any;
}
-export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal>{
-
+export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
constructor(props: IDashDocCommentViewInternal) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -71,7 +77,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
- 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) { }
+ 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) {}
}, 0);
}
e.stopPropagation();
@@ -81,32 +89,35 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
e.stopPropagation();
}
- targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+ 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;
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) {
- return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+ return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any; pos: number; hidden: boolean };
}
}
- const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.docid, float: "right" });
+ const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: this.props.docid, float: 'right' });
this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc));
- setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
+ setTimeout(() => {
+ try {
+ this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
+ } catch (e) {}
+ }, 0);
return undefined;
- }
+ };
render() {
return (
<span
className="formattedTextBox-inlineComment"
- id={"DashDocCommentView-" + this.props.docid}
+ id={'DashDocCommentView-' + this.props.docid}
onPointerLeave={this.onPointerLeaveCollapsed}
onPointerEnter={this.onPointerEnterCollapsed}
onPointerUp={this.onPointerUpCollapsed}
- onPointerDown={this.onPointerDownCollapsed}
- >
- </span>
+ onPointerDown={this.onPointerDownCollapsed}></span>
);
}
}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 1d8e3a2cf..9d203b6cc 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,52 +1,54 @@
-import { IReactionDisposer, reaction, observable, action } from "mobx";
-import { NodeSelection } from "prosemirror-state";
-import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc";
-import { Cast, StrCast, NumCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { Transform } from "../../../util/Transform";
-import { DocumentView } from "../DocumentView";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
+import { IReactionDisposer, reaction, observable, action } from 'mobx';
+import { NodeSelection } from 'prosemirror-state';
+import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc';
+import { Cast, StrCast, NumCast } from '../../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from '../../../../Utils';
+import { DocServer } from '../../../DocServer';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
+import { Transform } from '../../../util/Transform';
+import { DocumentView } from '../DocumentView';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
import * as ReactDOM from 'react-dom';
-import { observer } from "mobx-react";
-import { ColorScheme } from "../../../util/SettingsManager";
+import { observer } from 'mobx-react';
+import { ColorScheme } from '../../../util/SettingsManager';
export class DashDocView {
- _fieldWrapper: HTMLSpanElement; // container for label and value
+ dom: HTMLSpanElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this._fieldWrapper = document.createElement("span");
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.textIndent = "0";
- this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "lightGray"));
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block";
- (this._fieldWrapper.style as any).float = node.attrs.float;
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
- ReactDOM.render(<DashDocViewInternal
- docid={node.attrs.docid}
- alias={node.attrs.alias}
- width={node.attrs.width}
- height={node.attrs.height}
- hidden={node.attrs.hidden}
- fieldKey={node.attrs.fieldKey}
- tbox={tbox}
- view={view}
- node={node}
- getPos={getPos}
- />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ this.dom = document.createElement('span');
+ this.dom.style.position = 'relative';
+ this.dom.style.textIndent = '0';
+ this.dom.style.border = '1px solid ' + StrCast(tbox.layoutDoc.color, CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'dimgray' : 'lightGray');
+ this.dom.style.width = node.attrs.width;
+ 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();
+ };
+
+ ReactDOM.render(
+ <DashDocViewInternal docid={node.attrs.docid} alias={node.attrs.alias} width={node.attrs.width} height={node.attrs.height} hidden={node.attrs.hidden} fieldKey={node.attrs.fieldKey} tbox={tbox} view={view} node={node} getPos={getPos} />,
+ this.dom
+ );
+ (this as any).dom = this.dom;
}
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
}
interface IDashDocViewInternal {
@@ -70,7 +72,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
@observable _finalLayout: any;
@observable _resolvedDataDoc: any;
-
updateDoc = action((dashDoc: Doc) => {
this._dashDoc = dashDoc;
this._finalLayout = this.props.docid ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc, this.props.fieldKey);
@@ -81,13 +82,18 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
}
this._resolvedDataDoc = Cast(this._finalLayout.resolvedDataDoc, Doc, null);
}
- if (this.props.width !== (this._dashDoc?._width ?? "") + "px" || this.props.height !== (this._dashDoc?._height ?? "") + "px") {
- try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
- ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px"
- }));
+ if (this.props.width !== (this._dashDoc?._width ?? '') + 'px' || this.props.height !== (this._dashDoc?._height ?? '') + 'px') {
+ try {
+ // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+ this.props.view.dispatch(
+ this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
+ ...this.props.node.attrs,
+ width: (this._dashDoc?._width ?? '') + 'px',
+ height: (this._dashDoc?._height ?? '') + 'px',
+ })
+ );
} catch (e) {
- console.log("DashDocView:" + e);
+ console.log('DashDocView:' + e);
}
}
});
@@ -98,14 +104,15 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
DocServer.GetRefField(this.props.docid + this.props.alias).then(async dashDoc => {
if (!(dashDoc instanceof Doc)) {
- this.props.alias && DocServer.GetRefField(this.props.docid).then(async dashDocBase => {
- if (dashDocBase instanceof Doc) {
- const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias);
- aliasedDoc.layoutKey = "layout";
- this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined);
- this.updateDoc(aliasedDoc);
- }
- });
+ this.props.alias &&
+ DocServer.GetRefField(this.props.docid).then(async dashDocBase => {
+ if (dashDocBase instanceof Doc) {
+ const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias);
+ aliasedDoc.layoutKey = 'layout';
+ this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined);
+ this.updateDoc(aliasedDoc);
+ }
+ });
} else {
this.updateDoc(dashDoc);
}
@@ -113,67 +120,70 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
}
componentDidMount() {
- this._disposers.upater = reaction(() => this._dashDoc && (NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width)),
+ this._disposers.upater = reaction(
+ () => this._dashDoc && NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width),
() => {
if (this._dashDoc) {
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
- ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px"
- }));
+ this.props.view.dispatch(
+ this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
+ ...this.props.node.attrs,
+ width: (this._dashDoc?._width ?? '') + 'px',
+ height: (this._dashDoc?._height ?? '') + 'px',
+ })
+ );
}
- });
+ }
+ );
}
-
removeDoc = () => {
- this.props.view.dispatch(this.props.view.state.tr
- .setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.getPos())))
- .deleteSelection());
+ this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.getPos()))).deleteSelection());
return true;
- }
+ };
getDocTransform = () => {
if (!this._spanRef.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
return new Transform(-translateX, -translateY, 1).scale(1 / scale);
- }
- outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
+ };
+ outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
onKeyDown = (e: any) => {
e.stopPropagation();
- if (e.key === "Tab" || e.key === "Enter") {
+ if (e.key === 'Tab' || e.key === 'Enter') {
e.preventDefault();
}
- }
+ };
onPointerLeave = () => {
- const ele = document.getElementById("DashDocCommentView-" + this.props.docid) as HTMLDivElement;
- ele && (ele.style.backgroundColor = "");
- }
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ ele && (ele.style.backgroundColor = '');
+ };
onPointerEnter = () => {
- const ele = document.getElementById("DashDocCommentView-" + this.props.docid) as HTMLDivElement;
- ele && (ele.style.backgroundColor = "orange");
- }
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ ele && (ele.style.backgroundColor = 'orange');
+ };
componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
render() {
- return !this._dashDoc || !this._finalLayout || this.props.hidden ? null :
- <div ref={this._spanRef}
+ return !this._dashDoc || !this._finalLayout || this.props.hidden ? null : (
+ <div
+ ref={this._spanRef}
className="dash-span"
style={{
width: this.props.width,
height: this.props.height,
position: 'absolute',
- display: 'inline-block'
+ display: 'inline-block',
}}
onPointerLeave={this.onPointerLeave}
onPointerEnter={this.onPointerEnter}
onKeyDown={this.onKeyDown}
onKeyPress={e => e.stopPropagation()}
onKeyUp={e => e.stopPropagation()}
- onWheel={e => e.preventDefault()}
- >
+ onWheel={e => e.preventDefault()}>
<DocumentView
Document={this._finalLayout}
DataDoc={this._resolvedDataDoc}
@@ -200,6 +210,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
ContainingCollectionView={this._textBox.props.ContainingCollectionView}
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
/>
- </div>;
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index bb3791f1e..940ed6386 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,52 +1,55 @@
-import { action, computed, IReactionDisposer, observable } from "mobx";
-import { observer } from "mobx-react";
+import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import { DataSym, Doc, DocListCast, Field } from "../../../../fields/Doc";
-import { List } from "../../../../fields/List";
-import { listSpec } from "../../../../fields/Schema";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { DocServer } from "../../../DocServer";
-import { CollectionViewType } from "../../collections/CollectionView";
-import "./DashFieldView.scss";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils";
-import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
-import { Tooltip } from "@material-ui/core";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+import { ComputedField } from '../../../../fields/ScriptField';
+import { Cast, StrCast } from '../../../../fields/Types';
+import { DocServer } from '../../../DocServer';
+import { CollectionViewType } from '../../collections/CollectionView';
+import './DashFieldView.scss';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { Tooltip } from '@material-ui/core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
export class DashFieldView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey);
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.fontWeight = "bold";
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.textContent = node.attrs.fieldKey.startsWith("#") ? node.attrs.fieldKey : node.attrs.fieldKey + " " + strVal;
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.fontWeight = 'bold';
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.textContent = node.attrs.fieldKey.startsWith('#') ? node.attrs.fieldKey : node.attrs.fieldKey + ' ' + strVal;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
- setTimeout(() => ReactDOM.render(<DashFieldViewInternal
- fieldKey={node.attrs.fieldKey}
- docid={node.attrs.docid}
- width={node.attrs.width}
- height={node.attrs.height}
- hideKey={node.attrs.hideKey}
- tbox={tbox}
- />, this._fieldWrapper));
- (this as any).dom = this._fieldWrapper;
+ setTimeout(() => ReactDOM.render(<DashFieldViewInternal fieldKey={node.attrs.fieldKey} docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} tbox={tbox} />, this.dom));
+ (this as any).dom = this.dom;
}
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
}
interface IDashFieldViewInternal {
@@ -72,8 +75,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._textBoxDoc = this.props.tbox.props.Document;
if (this.props.docid) {
- DocServer.GetRefField(this.props.docid).
- then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
} else {
this._dashDoc = this.props.tbox.rootDoc;
}
@@ -82,11 +84,11 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._reactionDisposer?.();
}
- public static multiValueDelimeter = ";";
+ public static multiValueDelimeter = ';';
public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
- const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === "PARAMS" ? textBoxDoc[fieldKey] : "");
- const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
- return { boolVal: Cast(fval, "boolean", null), strVal: Field.toString(fval as Field) || "" };
+ const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === 'PARAMS' ? textBoxDoc[fieldKey] : '');
+ const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
+ return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' };
}
// set the display of the field's value (checkbox for booleans, span of text for strings)
@@ -95,30 +97,40 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
const { boolVal, strVal } = DashFieldViewInternal.fieldContent(this._textBoxDoc, this._dashDoc, this._fieldKey);
// field value is a boolean, so use a checkbox or similar widget to display it
if (boolVal === true || boolVal === false) {
- return <input
- className="dashFieldView-fieldCheck"
- type="checkbox" checked={boolVal}
- onChange={e => {
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
- Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
- }}
- />;
- }
- else // field value is a string, so display it as an editable span
- {
+ return (
+ <input
+ className="dashFieldView-fieldCheck"
+ type="checkbox"
+ checked={boolVal}
+ onChange={e => {
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
+ Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
+ }}
+ />
+ );
+ } // field value is a string, so display it as an editable span
+ else {
// bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
// use React events. Essentially, React events occur after native events have been processed, so corresponding React events
// will never fire because Prosemirror has handled the native events. So we add listeners for native events here.
- return <span className="dashFieldView-fieldSpan" contentEditable={true}
- style={{ display: strVal.length < 2 ? "inline-block" : undefined }}
- suppressContentEditableWarning={true} defaultValue={strVal}
- ref={r => {
- r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
- r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
- r?.addEventListener("pointerdown", action(e => e.stopPropagation()));
- }} >
- {strVal}
- </span>;
+ return (
+ <span
+ className="dashFieldView-fieldSpan"
+ contentEditable={true}
+ style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
+ suppressContentEditableWarning={true}
+ defaultValue={strVal}
+ ref={r => {
+ r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r));
+ r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false));
+ r?.addEventListener(
+ 'pointerdown',
+ action(e => e.stopPropagation())
+ );
+ }}>
+ {strVal}
+ </span>
+ );
}
}
}
@@ -126,11 +138,13 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// we need to handle all key events on the input span or else they will propagate to prosemirror.
@action
fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
- if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database.
+ if (e.key === 'Enter') {
+ // handle the enter key by "submitting" the current text to Dash's database.
this.updateText(span.textContent!, true);
- e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
+ e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view
}
- if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span
+ if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
+ // handle ctrl-A to select all the text within the span
if (window.getSelection) {
const range = document.createRange();
range.selectNodeContents(span);
@@ -139,44 +153,44 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
}
- e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
- }
+ e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
+ };
@action
updateText = (nodeText: string, forceMatch: boolean) => {
if (nodeText) {
- const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
+ const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText;
// look for a document whose id === the fieldKey being displayed. If there's a match, then that document
// holds the different enumerated values for the field in the titles of its collected documents.
// if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
DocServer.GetRefField(this._fieldKey).then(options => {
- let modText = "";
- (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
+ let modText = '';
+ options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
if (modText) {
// elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true);
} // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
- else if (nodeText.startsWith(":=")) {
+ else if (nodeText.startsWith(':=')) {
this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
- } else if (nodeText.startsWith("=:=")) {
+ } else if (nodeText.startsWith('=:=')) {
Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
} else {
if (Number(newText).toString() === newText) {
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true);
} else {
const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
- if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
+ if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
const strVal = splits.length > 1 ? new List<string>(splits) : newText;
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true);
}
}
}
});
}
- }
+ };
createPivotForField = (e: React.MouseEvent) => {
let container = this.props.tbox.props.ContainingCollectionView;
@@ -190,36 +204,39 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
if (!list) {
alias._columnHeaders = list = new List<SchemaHeaderField>();
}
- list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
- list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
- alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey;
- this.props.tbox.props.addDocTab(alias, "add:right");
+ list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
+ list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
+ alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey;
+ this.props.tbox.props.addDocTab(alias, 'add:right');
}
- }
-
+ };
// 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: any) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, (e) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16);
});
- }
+ };
render() {
- return <div className="dashFieldView" style={{
- width: this.props.width,
- height: this.props.height,
- }}>
- {this.props.hideKey ? (null) :
- <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {this._fieldKey}
- </span>}
-
- {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent}
+ return (
+ <div
+ className="dashFieldView"
+ style={{
+ width: this.props.width,
+ height: this.props.height,
+ }}>
+ {this.props.hideKey ? null : (
+ <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
+ {this._fieldKey}
+ </span>
+ )}
- </div >;
+ {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+ </div>
+ );
}
}
@observer
@@ -234,19 +251,19 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
showFields = (e: React.MouseEvent) => {
DashFieldViewMenu.createFieldView(e);
DashFieldViewMenu.Instance.fadeOut(true);
- }
+ };
public show = (x: number, y: number) => {
this.jumpTo(x, y, true);
const hideMenu = () => {
this.fadeOut(true);
- document.removeEventListener("pointerdown", hideMenu);
+ document.removeEventListener('pointerdown', hideMenu);
};
- document.addEventListener("pointerdown", hideMenu);
- }
+ document.addEventListener('pointerdown', hideMenu);
+ };
render() {
const buttons = [
- <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
+ <Tooltip key="trash" title={<div className="dash-tooltip">{'Remove Link Anchor'}</div>}>
<button className="antimodeMenu-button" onPointerDown={this.showFields}>
<FontAwesomeIcon icon="eye" size="lg" />
</button>
@@ -255,4 +272,4 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
return this.getElement(buttons);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 508500ab6..98d611ca6 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,38 +1,38 @@
-import EquationEditor from "equation-editor-react";
-import { IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
+import EquationEditor from 'equation-editor-react';
+import { IReactionDisposer } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../../fields/Doc";
-import { StrCast } from "../../../../fields/Types";
-import "./DashFieldView.scss";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
+import { Doc } from '../../../../fields/Doc';
+import { StrCast } from '../../../../fields/Types';
+import './DashFieldView.scss';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
export class EquationView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ 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();
+ };
- ReactDOM.render(<EquationViewInternal
- fieldKey={node.attrs.fieldKey}
- width={node.attrs.width}
- height={node.attrs.height}
- setEditor={this.setEditor}
- tbox={tbox}
- />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ ReactDOM.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} setEditor={this.setEditor} tbox={tbox} />, this.dom);
+ (this as any).dom = this.dom;
}
_editor: EquationEditor | undefined;
- setEditor = (editor?: EquationEditor) => this._editor = editor;
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { this._editor?.mathField.focus(); }
- deselectNode() { }
+ setEditor = (editor?: EquationEditor) => (this._editor = editor);
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {
+ this._editor?.mathField.focus();
+ }
+ deselectNode() {}
}
interface IEquationViewInternal {
@@ -56,24 +56,33 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
this._textBoxDoc = this.props.tbox.props.Document;
}
- componentWillUnmount() { this._reactionDisposer?.(); }
- componentDidMount() { this.props.setEditor(this._ref.current ?? undefined); }
+ componentWillUnmount() {
+ this._reactionDisposer?.();
+ }
+ componentDidMount() {
+ this.props.setEditor(this._ref.current ?? undefined);
+ }
render() {
- return <div className="equationView" style={{
- position: "relative",
- display: "inline-block",
- width: this.props.width,
- height: this.props.height,
- bottom: 3,
- }}>
- <EquationEditor ref={this._ref}
- value={StrCast(this._textBoxDoc[this._fieldKey], "y=")}
- onChange={str => this._textBoxDoc[this._fieldKey] = str}
- autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
- autoOperatorNames="sin cos tan"
- spaceBehavesLikeTab={true}
- />
- </div >;
+ return (
+ <div
+ className="equationView"
+ style={{
+ position: 'relative',
+ display: 'inline-block',
+ width: this.props.width,
+ height: this.props.height,
+ bottom: 3,
+ }}>
+ <EquationEditor
+ ref={this._ref}
+ value={StrCast(this._textBoxDoc[this._fieldKey], 'y=')}
+ onChange={str => (this._textBoxDoc[this._fieldKey] = str)}
+ autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
+ autoOperatorNames="sin cos tan"
+ spaceBehavesLikeTab={true}
+ />
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f83fdffc9..e4f47953d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,89 +1,90 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { baseKeymap, selectAll } from "prosemirror-commands";
-import { history } from "prosemirror-history";
+import { isEqual } from 'lodash';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { baseKeymap, selectAll } from 'prosemirror-commands';
+import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
-import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Slice } from "prosemirror-model";
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
+import { keymap } from 'prosemirror-keymap';
+import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
+import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
-import { RichTextField } from "../../../../fields/RichTextField";
+import { RichTextField } from '../../../../fields/RichTextField';
import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { ComputedField } from '../../../../fields/ScriptField';
-import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
-import { DocServer } from "../../../DocServer";
+import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from "../../../util/DragManager";
+import { DragManager } from '../../../util/DragManager';
import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from "../../../util/SelectionManager";
+import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from "../../DocComponent";
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { DocumentButtonBar } from '../../DocumentButtonBar';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
-import { FieldView, FieldViewProps } from "../FieldView";
+import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
-import { DashDocCommentView } from "./DashDocCommentView";
-import { DashDocView } from "./DashDocView";
-import { DashFieldView } from "./DashFieldView";
-import { EquationView } from "./EquationView";
-import { FootnoteView } from "./FootnoteView";
-import "./FormattedTextBox.scss";
+import { DashDocCommentView } from './DashDocCommentView';
+import { DashDocView } from './DashDocView';
+import { DashFieldView } from './DashFieldView';
+import { EquationView } from './EquationView';
+import { FootnoteView } from './FootnoteView';
+import './FormattedTextBox.scss';
import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment';
-import { OrderedListView } from "./OrderedListView";
-import { buildKeymap, updateBullets } from "./ProsemirrorExampleTransfer";
-import { removeMarkWithAttrs } from "./prosemirrorPatches";
+import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer';
+import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
-import { RichTextRules } from "./RichTextRules";
-import { schema } from "./schema_rts";
-import { SummaryView } from "./SummaryView";
-import applyDevTools = require("prosemirror-dev-tools");
-import React = require("react");
-const translateGoogleApi = require("translate-google-api");
+import { RichTextRules } from './RichTextRules';
+import { schema } from './schema_rts';
+import { SummaryView } from './SummaryView';
+import applyDevTools = require('prosemirror-dev-tools');
+import React = require('react');
+const translateGoogleApi = require('translate-google-api');
export interface FormattedTextBoxProps {
- makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
- xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
+ makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
+ xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yPadding?: number;
noSidebar?: boolean;
dontScale?: boolean;
dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
}
-export const GoogleRef = "googleDocId";
+export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() {
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(FormattedTextBox, fieldStr);
+ }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
- static _globalHighlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
+ static _globalHighlights: string[] = ['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items'];
static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
@@ -93,7 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
- private _applyingChange: string = "";
+ private _applyingChange: string = '';
private _searchIndex = 0;
private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
@@ -102,7 +103,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _dropDisposer?: DragManager.DragDropDisposer;
private _recordingStart: number = 0;
private _ignoreScroll = false;
- private _lastText = "";
+ private _lastText = '';
private _focusSpeed: Opt<number>;
private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
@@ -113,21 +114,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _downY = 0;
private _break = true;
public ProseRef?: HTMLDivElement;
- public get EditorView() { return this._editorView; }
- public get SidebarKey() { return this.fieldKey + "-sidebar"; }
- @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); }
-
- @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
- @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
- @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; }
- @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
- @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
- @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
- @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
- @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); }
- @computed get _recording() { return this.dataDoc?.mediaState === "recording"; }
+ public get EditorView() {
+ return this._editorView;
+ }
+ public get SidebarKey() {
+ return this.fieldKey + '-sidebar';
+ }
+ @computed get allSidebarDocs() {
+ return DocListCast(this.dataDoc[this.SidebarKey]);
+ }
+
+ @computed get sidebarWidthPercent() {
+ return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%');
+ }
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4'));
+ }
+ @computed get autoHeight() {
+ return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight;
+ }
+ @computed get textHeight() {
+ return NumCast(this.rootDoc[this.fieldKey + '-height']);
+ }
+ @computed get scrollHeight() {
+ return NumCast(this.rootDoc[this.fieldKey + '-scrollHeight']);
+ }
+ @computed get sidebarHeight() {
+ return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '-height']);
+ }
+ @computed get titleHeight() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ }
+ @computed get autoHeightMargins() {
+ return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins);
+ }
+ @computed get _recording() {
+ return this.dataDoc?.mediaState === 'recording';
+ }
set _recording(value) {
- !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined);
+ !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this.props);
@@ -140,28 +165,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
history(),
keymap(this._keymap),
keymap(baseKeymap),
- new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }),
- new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } })
- ]
+ new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
+ new Plugin({
+ view(editorView) {
+ return new FormattedTextBoxComment(editorView);
+ },
+ }),
+ ],
};
}
public static PasteOnLoad: ClipboardEvent | undefined;
- public static SelectOnLoad = "";
+ public static SelectOnLoad = '';
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 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) {
+ 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 "";
+ return '';
}
public static GetDocFromUrl(url: string) {
- return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid
+ return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid
}
constructor(props: any) {
@@ -172,7 +202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
// removes all hyperlink anchors for the removed linkDoc
- // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
+ // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
// but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
public RemoveLinkFromDoc(linkDoc?: Doc) {
this.unhighlightSearchTerms();
@@ -195,9 +225,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- // removes all the specified link references from the selection.
+ // removes all the specified link references from the selection.
// NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
- public RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) {
+ public RemoveAnchorFromSelection(allAnchors: { href: string; title: string; linkId: string; targetId: string }[]) {
const state = this._editorView?.state;
if (state && this._editorView) {
this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors }));
@@ -205,11 +235,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
- getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection");
+ getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection');
@action
setupAnchorMenu = () => {
- AnchorMenu.Instance.Status = "marquee";
+ AnchorMenu.Instance.Status = 'marquee';
AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
!this.layoutDoc.showSidebar && this.toggleSidebar();
@@ -222,14 +252,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
- * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
+ * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
*/
AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
e.preventDefault();
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
- const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);
+ const target = CurrentUserUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);
FormattedTextBox.SelectOnLoad = target[Id];
return target;
};
@@ -238,55 +268,56 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
- }
+ };
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
- const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box
- const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
+ const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+ const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box
+ const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
const json = JSON.stringify(state.toJSON());
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
- const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ?
- json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
+ const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
const accumTags = [] as string[];
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("#")) {
+ if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
accumTags.push(node.attrs.fieldKey);
}
});
- const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#"));
+ const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith('#'));
const added = accumTags.filter(tag => !curTags.includes(tag));
const removed = curTags.filter(tag => !accumTags.includes(tag));
- removed.forEach(r => this.dataDoc[r] = undefined);
- added.forEach(a => this.dataDoc[a] = a);
+ removed.forEach(r => (this.dataDoc[r] = undefined));
+ added.forEach(a => (this.dataDoc[a] = a));
let unchanged = true;
if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
this._applyingChange = this.fieldKey;
- (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
- if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+ curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text && (this.dataDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now())));
+ if ((!curTemp && !curProto) || curText || json.includes('dash')) {
+ // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ this.dataDoc[this.props.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
- } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+ } else {
+ // if we've deleted all the text in a note driven by a template, then restore the template data
this.dataDoc[this.props.fieldKey] = undefined;
this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+ this.dataDoc[this.props.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
- this._applyingChange = "";
+ this._applyingChange = '';
if (!unchanged) {
this.updateTitle();
this.tryUpdateScrollHeight();
@@ -304,16 +335,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
AnchorMenu.Instance.fadeOut(true);
}
}
- }
+ };
- // for inserting timestamps
+ // for inserting timestamps
insertTime = () => {
let linkTime;
let linkAnchor;
let link;
DocListCast(this.dataDoc.links).forEach((l, i) => {
- const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === "recording") {
+ const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
+ if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
link = l;
@@ -344,7 +375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
}
}
- }
+ };
autoLink = () => {
if (this._editorView?.state.doc.textContent) {
@@ -355,38 +386,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
var tr = this._editorView.state.tr as any;
const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
- DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks));
+ DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
}
- }
+ };
updateTitle = () => {
const title = StrCast(this.dataDoc.title);
- if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
- (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] &&
- (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
+ if (
+ !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ (title.startsWith('-') || title.startsWith('@')) &&
+ this._editorView &&
+ !this.dataDoc['title-custom'] &&
+ (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text')
+ ) {
let node = this._editorView.state.doc;
- while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
+ while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
- const prefix = str.startsWith("@") ? "" : "-";
+ const prefix = str.startsWith('@') ? '' : '-';
const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
if (!(cfield instanceof ComputedField)) {
- this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : "");
- if (str.startsWith("@") && str.length > 1) {
+ this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '');
+ if (str.startsWith('@') && str.length > 1) {
Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, this.rootDoc);
}
}
}
- }
+ };
// creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
const editorView = this._editorView;
if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
- const autoLinkTerm = StrCast(target.title).replace(/^@/, "");
+ const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm);
var alink: Doc | undefined;
flattened1.forEach((flat, i) => {
@@ -397,14 +432,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
tr = tr.addMark(sel.from, sel.to, splitter);
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 ?? (DocListCast(this.Document.links).find(link =>
- Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
- Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target },
- LinkManager.AutoKeywords)!);
+ alink =
+ alink ??
+ (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
+ DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
newAutoLinks.add(alink);
- const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }];
+ const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
- const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" });
+ const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
tr = tr.addMark(pos, pos + node.nodeSize, link);
}
});
@@ -412,13 +447,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
}
return tr;
- }
+ };
@action
search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
if (clear) this.unhighlightSearchTerms();
else this.highlightSearchTerms([searchString], bwd!);
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);
@@ -432,21 +467,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (backward === true) {
if (this._searchIndex > 1) {
this._searchIndex += -2;
- }
- else if (this._searchIndex === 1) {
+ } else if (this._searchIndex === 1) {
this._searchIndex = length - 1;
- }
- else if (this._searchIndex === 0 && length !== 1) {
+ } else if (this._searchIndex === 0 && length !== 1) {
this._searchIndex = length - 2;
}
-
}
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.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());
}
- }
+ };
unhighlightSearchTerms = () => {
if (window.screen.width < 600) null;
@@ -455,20 +487,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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 (FormattedTextBox.PasteOnLoad) {
- const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin");
- const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfRegion");
+ const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin');
+ const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion');
FormattedTextBox.PasteOnLoad = undefined;
setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10);
}
- }
+ };
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
- }
+ };
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
this.ProseRef = ele;
@@ -476,8 +507,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.setupEditor(this.config, this.props.fieldKey);
this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc);
}
- // if (this.autoHeight) this.tryUpdateScrollHeight();
- }
+ // if (this.autoHeight) this.tryUpdateScrollHeight();
+ };
@undoBatch
@action
@@ -499,18 +530,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const node = schema.nodes.dashDoc.create({
width: target[WidthSym](),
height: target[HeightSym](),
- title: "dashDoc",
+ title: 'dashDoc',
docid: target[Id],
- float: "unset"
+ float: 'unset',
});
const view = this._editorView!;
view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
e.stopPropagation();
} // otherwise, fall through to outer collection to handle drop
}
- }
+ };
- getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
+ getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null {
let offset = 0;
if (context === node) return { from: offset, to: offset + node.nodeSize };
@@ -521,8 +552,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const result = this.getNodeEndpoints((context.content as any).content[i], node);
if (result) {
return {
- from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
- to: result.to + offset + (context.type.name === "doc" ? 0 : 1)
+ from: result.from + offset + (context.type.name === 'doc' ? 0 : 1),
+ to: result.to + offset + (context.type.name === 'doc' ? 0 : 1),
};
}
offset += (context.content as any).content[i].nodeSize;
@@ -538,9 +569,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let ret: TextSelection[] = [];
if (node.isTextblock) {
- let index = 0, foundAt;
+ let index = 0,
+ foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
- const regexp = new RegExp(find.replace("*", ""), "i");
+ const regexp = new RegExp(find.replace('*', ''), 'i');
if (regexp) {
while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
@@ -549,7 +581,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
} else {
- node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
+ node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));
}
return ret;
}
@@ -557,162 +589,178 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
updateHighlights = () => {
const highlights = FormattedTextBox._globalHighlights;
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (highlights.indexOf("Audio Tags") === -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, "");
+ if (highlights.indexOf('Audio Tags') === -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
}
- if (highlights.indexOf("Text from Others") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
+ if (highlights.indexOf('Text from Others') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
- if (highlights.indexOf("My Text") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
+ if (highlights.indexOf('My Text') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });
}
- if (highlights.indexOf("Todo Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });
+ if (highlights.indexOf('Todo Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'todo', { outline: 'black solid 1px' });
}
- if (highlights.indexOf("Important Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });
+ if (highlights.indexOf('Important Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'important', { 'font-size': 'larger' });
}
- if (highlights.indexOf("Bold Text") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror strong > span", { "font-size": "large" }, "");
- addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror :not(strong > span)", { "font-size": "0px" }, "");
+ if (highlights.indexOf('Bold Text') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, '');
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
}
- if (highlights.indexOf("Disagree Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });
+ if (highlights.indexOf('Disagree Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'disagree', { 'text-decoration': 'line-through' });
}
- if (highlights.indexOf("Ignore Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });
+ if (highlights.indexOf('Ignore Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'ignore', { 'font-size': '1' });
}
- if (highlights.indexOf("By Recent Minute") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ if (highlights.indexOf('By Recent Minute') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.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() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
setTimeout(this.updateHighlights);
}
- if (highlights.indexOf("By Recent Hour") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ if (highlights.indexOf('By Recent Hour') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.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() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
- }
+ };
@observable _showSidebar = false;
- @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ @computed get SidebarShown() {
+ return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
+ }
@action
toggleSidebar = (preview: boolean = false) => {
const prevWidth = this.sidebarWidth();
if (preview) this._showSidebar = true;
- else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
+ else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
- }
+ };
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
- }
+ };
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const bounds = this._ref.current!.getBoundingClientRect();
- this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
- this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+ this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
+ this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%';
e.preventDefault();
return false;
- }
+ };
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const changeItems: ContextMenuProps[] = [];
changeItems.push({
- description: "plain", event: undoBatch(() => {
+ description: 'plain',
+ event: undoBatch(() => {
Doc.setNativeView(this.rootDoc);
this.layoutDoc.autoHeightMargins = undefined;
- }), icon: "eye"
+ }),
+ icon: 'eye',
});
changeItems.push({
- description: "metadata", event: undoBatch(() => {
+ description: 'metadata',
+ event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
- this.rootDoc.layoutKey = "layout_meta";
- setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50);
- }), icon: "eye"
+ this.rootDoc.layoutKey = 'layout_meta';
+ setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50), 50);
+ }),
+ icon: 'eye',
});
- const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
+ const noteTypesDoc = Cast(Doc.UserDoc()['template-notes'], Doc, null);
DocListCast(noteTypesDoc?.data).forEach(note => {
const icon: IconProp = StrCast(note.icon) as IconProp;
changeItems.push({
- description: StrCast(note.title), event: undoBatch(() => {
+ description: StrCast(note.title),
+ event: undoBatch(() => {
this.layoutDoc.autoHeightMargins = undefined;
Doc.setNativeView(this.rootDoc);
DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
- }), icon: icon
+ }),
+ icon: icon,
});
});
const highlighting: ContextMenuProps[] = [];
- const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"];
- const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"];
+ const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text'];
+ const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
(Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
highlighting.push({
- description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option,
+ event: () => {
e.stopPropagation();
if (FormattedTextBox._globalHighlights.indexOf(option) === -1) {
FormattedTextBox._globalHighlights.push(option);
} else {
FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);
}
- runInAction(() => this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join(""));
+ runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join('')));
this.updateHighlights();
- }, icon: "expand-arrows-alt"
- }));
+ },
+ icon: 'expand-arrows-alt',
+ })
+ );
const uicontrols: ContextMenuProps[] = [];
- !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" });
- uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" });
- uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
- uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
- !Doc.noviceMode && uicontrols.push({
- description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
- proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
- });
- cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+ !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
+ !Doc.noviceMode &&
+ uicontrols.push({
+ description: 'Broadcast Message',
+ event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)),
+ icon: 'expand-arrows-alt',
+ });
+ cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' });
- const appearance = cm.findByDescription("Appearance...");
- const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
- appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
+ const appearance = cm.findByDescription('Appearance...');
+ const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
+ appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
// this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
- !Doc.noviceMode && appearanceItems.push({
- description: "Make Default Layout", event: () => {
- if (!this.layoutDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = "text";
- MakeTemplate(this.rootDoc, true, title);
- } else if (!this.rootDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = "text";
- this.rootDoc.layout = this.layoutDoc.layout as string;
- this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
- this.rootDoc.isTemplateDoc = false;
- this.rootDoc.isTemplateForField = "";
- this.rootDoc.layoutKey = "layout";
- MakeTemplate(this.rootDoc, true, title);
- setTimeout(() => {
- this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height
- this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template
- this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
- this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
- this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null);
- }, 10);
- }
- Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
- Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
- }, icon: "eye"
- });
- cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+ !Doc.noviceMode &&
+ appearanceItems.push({
+ description: 'Make Default Layout',
+ event: () => {
+ if (!this.layoutDoc.isTemplateDoc) {
+ const title = StrCast(this.rootDoc.title);
+ this.rootDoc.title = 'text';
+ MakeTemplate(this.rootDoc, true, title);
+ } else if (!this.rootDoc.isTemplateDoc) {
+ const title = StrCast(this.rootDoc.title);
+ this.rootDoc.title = 'text';
+ this.rootDoc.layout = this.layoutDoc.layout as string;
+ this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
+ this.rootDoc.isTemplateDoc = false;
+ this.rootDoc.isTemplateForField = '';
+ this.rootDoc.layoutKey = 'layout';
+ MakeTemplate(this.rootDoc, true, title);
+ setTimeout(() => {
+ this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height
+ this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template
+ this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
+ this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null);
+ this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, 'string', null);
+ }, 10);
+ }
+ Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
+ Doc.AddDocToList(Cast(Doc.UserDoc()['template-notes'], Doc, null), 'data', this.rootDoc);
+ },
+ icon: 'eye',
+ });
+ cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
- const options = cm.findByDescription("Options...");
- const optionItems = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
- optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+ const options = cm.findByDescription('Options...');
+ const optionItems = options && 'subitems' in options ? options.subitems : [];
+ optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' });
+ optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
- }
+ };
breakupDictation = () => {
if (this._editorView && this._recording) {
@@ -721,12 +769,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const state = this._editorView.state;
const to = state.selection.to;
const updated = TextSelection.create(state.doc, to, to);
- this._editorView.dispatch(state.tr.setSelection(updated).insertText("\n", to));
+ this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));
if (this._recording) {
this.recordDictation();
}
}
- }
+ };
recordDictation = () => {
DictationManager.Controls.listen({
interimHandler: this.setDictationContent,
@@ -736,26 +784,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DictationManager.Controls.stop();
}
});
- }
+ };
stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
setDictationContent = (value: string) => {
if (this._editorView && this._recordingStart) {
if (this._break) {
- const textanchor = Docs.Create.TextanchorDocument({ title: "dictation anchor" });
+ const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
this.addDocument(textanchor);
const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement();
link && (Doc.GetProto(link).isDictation = true);
if (!link) return;
const audioanchor = Cast(link.anchor2, Doc, null);
if (!audioanchor) return;
- audioanchor.backgroundColor = "tan";
+ audioanchor.backgroundColor = 'tan';
const audiotag = this._editorView.state.schema.nodes.audiotag.create({
timeCode: NumCast(audioanchor._timecodeToShow),
audioId: audioanchor[Id],
- textId: textanchor[Id]
+ textId: textanchor[Id],
});
- Doc.GetProto(textanchor).title = "dictation:" + audiotag.attrs.timeCode;
+ Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -765,7 +813,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const tr = this._editorView.state.tr.insertText(value);
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
}
- }
+ };
// TODO: nda -- Look at how link anchors are added
makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
@@ -775,7 +823,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
- const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
+ const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
@@ -786,7 +834,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
tr = tr.addMark(pos, pos + node.nodeSize, link);
}
});
- this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
+ this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
return anchor;
@@ -797,7 +845,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
scrollFocus = (textAnchor: Doc, smooth: boolean) => {
- if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) {
+ if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {
this.toggleSidebar(!smooth);
}
const textAnchorId = textAnchor[Id];
@@ -827,7 +875,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
- return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;
};
let start = 0;
@@ -845,59 +893,69 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
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);
+ addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
+ setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
}
}
return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed
- }
+ };
getScrollHeight = () => this.scrollHeight;
// if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc.
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.rootDoc[this.fieldKey + "-height"] = scrollHeight;
+ this.rootDoc[this.fieldKey + '-height'] = scrollHeight;
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
- }
+ };
- @computed get contentScaling() { return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;}
+ @computed get contentScaling() {
+ return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;
+ }
componentDidMount() {
!this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._cachedLinks = DocListCast(this.Document.links);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
- this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight());
- this._disposers.scrollHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
+ this._disposers.autoHeight = reaction(
+ () => this.autoHeight,
+ autoHeight => autoHeight && this.tryUpdateScrollHeight()
+ );
+ this._disposers.scrollHeight = reaction(
+ () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
({ width, scrollHeight, autoHeight }) => {
width && autoHeight && this.resetNativeHeight(scrollHeight);
- }, { fireImmediately: true }
+ },
+ { fireImmediately: true }
);
- this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on
+ this._disposers.componentHeights = reaction(
+ // set the document height when one of the component heights changes and autoHeight is on
() => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)));
- }, { fireImmediately: true });
- this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ },
+ { fireImmediately: true }
+ );
+ this._disposers.links = reaction(
+ () => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
this._cachedLinks = newLinks;
- });
+ }
+ );
this._disposers.buttonBar = reaction(
() => DocumentButtonBar.Instance,
instance => {
if (instance) {
this.pullFromGoogleDoc(this.checkState);
- this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true);
+ this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true));
}
}
);
this._disposers.editorState = reaction(
() => {
- const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined :
- this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ?
- this.dataDoc : this.layoutDoc;
+ const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : this.dataDoc?.[this.props.fieldKey + '-noTemplate'] || !this.layoutDoc[this.props.fieldKey] ? this.dataDoc : this.layoutDoc;
return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) };
},
incomingValue => {
@@ -912,7 +970,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
}
}
- },
+ }
);
this._disposers.pullDoc = reaction(
() => this.props.Document[Pulls],
@@ -933,13 +991,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
);
- this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc),
- search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(),
- { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
+ this._disposers.search = reaction(
+ () => Doc.IsSearchMatch(this.rootDoc),
+ search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
+ { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }
+ );
- this._disposers.selected = reaction(() => this.props.isSelected(),
+ this._disposers.selected = reaction(
+ () => this.props.isSelected(),
action(selected => {
- this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join("") : "";
+ this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : '';
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
@@ -947,21 +1008,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
this.autoLink();
}
- }), { fireImmediately: true });
+ }),
+ { fireImmediately: true }
+ );
if (!this.props.dontRegisterView) {
- this._disposers.record = reaction(() => this._recording,
+ this._disposers.record = reaction(
+ () => this._recording,
() => {
this.stopDictation(true);
if (this._recording) {
this.recordDictation();
}
- },
+ }
);
if (this._recording) setTimeout(this.recordDictation);
}
- var quickScroll: string | undefined = "";
- this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
+ var quickScroll: string | undefined = '';
+ this._disposers.scroll = reaction(
+ () => NumCast(this.layoutDoc._scrollTop),
pos => {
if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
@@ -974,7 +1039,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._scrollRef.current.scrollTo({ top: pos });
}
}
- }, { fireImmediately: true }
+ },
+ { fireImmediately: true }
);
quickScroll = undefined;
this.tryUpdateScrollHeight();
@@ -984,7 +1050,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
const modes = GoogleApiClientUtils.Docs.WriteMode;
let mode = modes.Replace;
- let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+ let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], 'string');
if (!reference) {
mode = modes.Insert;
reference = { title: StrCast(this.dataDoc.title) };
@@ -994,7 +1060,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
response && (this.dataDoc[GoogleRef] = response.documentId);
- const pushSuccess = response !== undefined && !("errors" in response);
+ const pushSuccess = response !== undefined && !('errors' in response);
dataDoc.googleDocUnchanged = pushSuccess;
DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
}
@@ -1003,15 +1069,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (exportState && reference) {
const content: GoogleApiClientUtils.Docs.Content = {
text: exportState.text,
- requests: []
+ requests: [],
};
GoogleApiClientUtils.Docs.write({ reference, content, mode });
}
};
- UndoManager.AddEvent({ undo, redo, prop: "" });
+ UndoManager.AddEvent({ undo, redo, prop: '' });
redo();
});
- }
+ };
pullFromGoogleDoc = async (handler: PullHandler) => {
const dataDoc = this.dataDoc;
@@ -1021,7 +1087,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
}
exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
- }
+ };
updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
let pullSuccess = false;
@@ -1036,13 +1102,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}, 0);
dataDoc.title = exportState.title;
- this.dataDoc["title-custom"] = true;
+ this.dataDoc['title-custom'] = true;
dataDoc.googleDocUnchanged = true;
} else {
delete dataDoc[GoogleRef];
}
DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
- }
+ };
checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
if (exportState && this._editorView) {
@@ -1052,56 +1118,61 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
dataDoc.googleDocUnchanged = unchanged;
DocumentButtonBar.Instance.setPullState(unchanged);
}
- }
+ };
clipboardTextSerializer = (slice: Slice): string => {
- let text = "", separated = true;
- const from = 0, to = slice.content.size;
- slice.content.nodesBetween(from, to, (node, pos) => {
- if (node.isText) {
- text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
- separated = false;
- } else if (!separated && node.isBlock) {
- text += "\n";
- separated = true;
- } else if (node.type.name === "hard_break") {
- text += "\n";
- }
- }, 0);
+ let text = '',
+ separated = true;
+ const from = 0,
+ to = slice.content.size;
+ slice.content.nodesBetween(
+ from,
+ to,
+ (node, pos) => {
+ if (node.isText) {
+ text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
+ separated = false;
+ } else if (!separated && node.isBlock) {
+ text += '\n';
+ separated = true;
+ } else if (node.type.name === 'hard_break') {
+ text += '\n';
+ }
+ },
+ 0
+ );
return text;
- }
+ };
handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
const cbe = event as ClipboardEvent;
- const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin");
- const pdfRegionId = cbe.clipboardData?.getData("dash/pdfRegion");
+ const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin');
+ const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion');
return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false;
- }
+ };
addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => {
const view = this._editorView!;
if (pdfDocId && pdfRegionId) {
DocServer.GetRefField(pdfDocId).then(pdfDoc => {
DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
- if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
+ if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) {
setTimeout(async () => {
const targetField = Doc.LayoutFieldKey(pdfDoc);
- const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
+ const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations
if (targetAnnotations) targetAnnotations.push(pdfRegion);
- else Doc.AddDocToList(pdfDoc[DataSym], targetField + "-annotations", pdfRegion);
+ else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion);
});
- const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted");
+ const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted');
if (link) {
const linkId = link[Id];
- const quote = view.state.schema.nodes.blockquote.create();
- quote.content = addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+ const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) });
const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0);
if (slice) {
- view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
+ view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
} else {
selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView()));
-
}
}
}
@@ -1111,31 +1182,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
return false;
-
function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
const nodes: Node[] = [];
frag.forEach(node => nodes.push(marker(node)));
return Fragment.fromArray(nodes);
}
-
function addLinkMark(node: Node, title: string, linkId: string) {
if (!node.isText) {
const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
return node.copy(content);
}
const marks = [...node.marks];
- const linkIndex = marks.findIndex(mark => mark.type.name === "link");
+ const linkIndex = marks.findIndex(mark => mark.type.name === 'link');
const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
- const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
+ const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
- }
+ };
isActiveTab(el: Element | null | undefined) {
while (el && el !== document.body) {
- if (getComputedStyle(el).display === "none") return false;
+ if (getComputedStyle(el).display === 'none') return false;
el = el.parentNode as any;
}
return true;
@@ -1147,7 +1216,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
view(newView) {
runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
return new RichTextMenuPlugin({ editorProps: this.props });
- }
+ },
});
}
_didScroll = false;
@@ -1159,7 +1228,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView?.destroy();
this._editorView = new EditorView(this.ProseRef, {
state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
- handleScrollToSelection: (editorView) => {
+ handleScrollToSelection: editorView => {
const docPos = editorView.coordsAtPos(editorView.state.selection.to);
const viewRect = self._ref.current!.getBoundingClientRect();
const scrollRef = self._scrollRef.current;
@@ -1179,13 +1248,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
},
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);
+ },
+ 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);
+ },
},
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
@@ -1196,9 +1277,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (startupText) {
dispatch(state.tr.insertText(startupText));
}
- const textAlign = StrCast(this.dataDoc["text-align"], StrCast(Doc.UserDoc().textAlign, "left"));
- if (textAlign !== "left") {
- selectAll(this._editorView.state, (tr) => {
+ const textAlign = StrCast(this.dataDoc['text-align'], StrCast(Doc.UserDoc().textAlign, 'left'));
+ if (textAlign !== 'left') {
+ selectAll(this._editorView.state, tr => {
this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign })));
});
}
@@ -1209,14 +1290,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
- FormattedTextBox.SelectOnLoad = "";
+ FormattedTextBox.SelectOnLoad = '';
this.props.select(false);
if (selLoadChar && this._editorView) {
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 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.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks);
+ const tr = this._editorView.state.tr
+ .setStoredMarks(storedMarks)
+ .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size)
+ .setStoredMarks(storedMarks);
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
@@ -1229,14 +1313,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
selectOnLoad && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
- this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []),
- schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
- ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
- ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []),
- ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
- ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])];
+ this._editorView.state.storedMarks = [
+ ...(this._editorView.state.storedMarks ?? []),
+ schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
+ ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
+ ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
+ ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
+ ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
+ ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
+ ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
+ ];
}
}
@@ -1247,13 +1333,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.unhighlightSearchTerms();
this._editorView?.destroy();
RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+ FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none');
}
onPointerDown = (e: React.PointerEvent): void => {
if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
- if ((e.target as any).tagName === "AUDIOTAG") {
+ if ((e.target as any).tagName === 'AUDIOTAG') {
e.preventDefault();
e.stopPropagation();
const timecode = Number((e.target as any)?.dataset?.timecode);
@@ -1264,10 +1350,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const func = () => {
const docView = DocumentManager.Instance.getDocumentView(audiodoc);
if (!docView) {
- this.props.addDocTab(audiodoc, "add:bottom");
+ this.props.addDocTab(audiodoc, 'add:bottom');
setTimeout(func);
- }
- else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that
+ } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
};
func();
}
@@ -1283,29 +1368,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._downEvent = true;
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
+ // stop propagation if not in sidebar
// bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView,
// but that's changed, so this shouldn't be needed.
//e.stopPropagation(); // if the text box is selected, then it consumes all down events
- document.addEventListener("pointerup", this.onSelectEnd);
- document.addEventListener("pointermove", this.onSelectMove);
+ document.addEventListener('pointerup', this.onSelectEnd);
+ document.addEventListener('pointermove', this.onSelectMove);
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
- }
+ };
onSelectMove = (e: PointerEvent) => e.stopPropagation();
onSelectEnd = (e: PointerEvent) => {
- document.removeEventListener("pointerup", this.onSelectEnd);
- document.removeEventListener("pointermove", this.onSelectMove);
- }
+ document.removeEventListener('pointerup', this.onSelectEnd);
+ document.removeEventListener('pointermove', this.onSelectMove);
+ };
onPointerUp = (e: React.PointerEvent): void => {
if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu();
if (!this._downEvent) return;
this._downEvent = false;
if ((e.nativeEvent as any).formattedHandled) {
- console.log("handled");
+ console.log('handled');
}
if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) {
const editor = this._editorView!;
@@ -1320,13 +1406,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
- }
+ };
@action
onDoubleClick = (e: React.MouseEvent): void => {
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
- e.stopPropagation(); // if the text box is selected, then it consumes all click events
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
+ // stop propagation if not in sidebar
+ e.stopPropagation(); // if the text box is selected, then it consumes all click events
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
@@ -1339,18 +1426,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
- }
+ };
setFocus = () => {
const pos = this._editorView?.state.selection.$from.pos || 1;
(this.ProseRef?.children?.[0] as any).focus();
setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
- }
+ };
@action
onFocused = (e: React.FocusEvent): void => {
//applyDevTools.applyDevTools(this._editorView);
FormattedTextBox.Focused = this;
this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
- }
+ };
@observable public static Focused: FormattedTextBox | undefined;
onClick = (e: React.MouseEvent): void => {
@@ -1358,7 +1445,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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.
+ 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 });
const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
@@ -1368,13 +1456,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (!node && this.ProseRef) {
const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
const boundsRect = lastNode?.getBoundingClientRect();
- if (e.clientX > boundsRect.left && e.clientX < boundsRect.right &&
- e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+ if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && e.clientY > boundsRect.bottom) {
+ // if we clicked below the last prosemirror div, then set the selection to be the end of the document
this._editorView?.focus();
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
}
- } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&
- node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
+ } else if (node && [this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node.type) && node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
}
}
@@ -1382,7 +1469,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
e.stopPropagation();
return;
}
- if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
+ if (this.props.isSelected(true)) {
+ // if text box is selected, then it consumes all click events
(e.nativeEvent as any).formattedHandled = true;
if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)
@@ -1390,7 +1478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
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) {
this._forceUncollapse = false;
@@ -1420,7 +1508,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos)));
}
}
- addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+ addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' });
}
}
}
@@ -1432,20 +1520,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// are nested prosemirrors. We only want the lowest level prosemirror to be invoked.
if (view.mouseDown) {
const originalUpHandler = view.mouseDown.up;
- view.root.removeEventListener("mouseup", originalUpHandler);
+ view.root.removeEventListener('mouseup', originalUpHandler);
view.mouseDown.up = (e: MouseEvent) => {
if (!(e as any).formattedHandled) {
originalUpHandler(e);
(e as any).formattedHandled = true;
} else {
- console.log("prehandled");
+ console.log('prehandled');
}
};
- view.root.addEventListener("mouseup", view.mouseDown.up);
+ view.root.addEventListener('mouseup', view.mouseDown.up);
}
- }
+ };
startUndoTypingBatch() {
- !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping"));
+ !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping'));
}
public endUndoTypingBatch() {
const wasUndoing = this._undoTyping;
@@ -1461,33 +1549,39 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
- FormattedTextBox._hadSelection = window.getSelection()?.toString() !== "";
+ FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
this.endUndoTypingBatch();
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
const state = this._editorView!.state;
- const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
- if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
+ const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+ if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
try {
- translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => {
- setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => {
- this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0];
- }), 1000);
+ translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => {
+ setTimeout(
+ () =>
+ translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => {
+ this.dataDoc[this.fieldKey + '-translation'] = result1 + '\r\n\r\n' + result[0];
+ }),
+ 1000
+ );
});
- } catch (e: any) { console.log(e.message); }
+ } catch (e: any) {
+ console.log(e.message);
+ }
this._lastText = curText;
}
- if (StrCast(this.rootDoc.title).startsWith("@") && !this.dataDoc["title-custom"]) {
+ if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc['title-custom']) {
UndoManager.RunInBatch(() => {
- this.dataDoc["title-custom"] = true;
- this.dataDoc.showTitle = "title";
+ this.dataDoc['title-custom'] = true;
+ this.dataDoc.showTitle = 'title';
const tr = this._editorView!.state.tr;
this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection());
- }, "titler");
+ }, 'titler');
}
- }
+ };
onKeyDown = (e: React.KeyboardEvent) => {
if (e.altKey) {
@@ -1495,7 +1589,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return;
}
const state = this._editorView!.state;
- if (!state.selection.empty && e.key === "%") {
+ if (!state.selection.empty && e.key === '%') {
this._rules!.EnteringStyle = true;
e.preventDefault();
e.stopPropagation();
@@ -1508,32 +1602,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
e.stopPropagation();
for (var 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.rootDoc))) {
+ 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.rootDoc))) {
e.preventDefault();
}
}
switch (e.key) {
- case "Escape":
+ 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();
RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
return;
- case "Enter": this.insertTime();
- case "Tab": e.preventDefault(); break;
- default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
- case " ":
- [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({}))
- .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+ case 'Enter':
+ this.insertTime();
+ case 'Tab':
+ e.preventDefault();
+ break;
+ default:
+ if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
+ case ' ':
+ [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) &&
+ this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
this.startUndoTypingBatch();
- }
+ };
ondrop = (e: React.DragEvent) => {
this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
- }
+ };
onScroll = (e: React.UIEvent) => {
if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
if (!this.props.dontSelectOnLoad) {
@@ -1542,15 +1638,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._ignoreScroll = false;
}
}
- }
+ };
tryUpdateScrollHeight = () => {
const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children) {
- const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins);
const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
+ if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {
+ // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight);
if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
} else {
@@ -1558,7 +1655,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- }
+ };
fitContentsToBox = () => this.props.Document._fitContentsToBox;
sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
@@ -1566,40 +1663,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// console.log("printting allSideBarDocs");
// console.log(this.allSidebarDocs);
return this.addDocument(doc, sidebarKey);
- }
+ };
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height;
- sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
- sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1));
+ setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '-height'] = height);
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ sidebarScreenToLocal = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0)
+ .scale(1 / NumCast(this.layoutDoc._viewScale, 1));
@computed get audioHandle() {
- return <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
- <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" />
- </div>;
+ return (
+ <div className="formattedTextBox-dictation" onClick={action(e => (this._recording = !this._recording))}>
+ <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
+ </div>
+ );
}
@computed get sidebarHandle() {
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
const color = !annotated ? Colors.WHITE : Colors.BLACK;
- const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
+ const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
- return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) :
- <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+ return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging()) ? null : (
+ <div
+ className="formattedTextBox-sidebar-handle"
+ onPointerDown={this.sidebarDown}
style={{
left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
backgroundColor: backgroundColor,
color: color,
- opacity: annotated ? 1 : undefined
- }} >
- <FontAwesomeIcon icon={"comment-alt"} />
- </div>;
+ opacity: annotated ? 1 : undefined,
+ }}>
+ <FontAwesomeIcon icon={'comment-alt'} />
+ </div>
+ );
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
- const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView;
- return ComponentTag === CollectionStackingView ?
- <SidebarAnnos ref={this._sidebarRef}
+ const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+ return ComponentTag === CollectionStackingView ? (
+ <SidebarAnnos
+ ref={this._sidebarRef}
{...this.props}
fieldKey={this.fieldKey}
rootDoc={this.rootDoc}
@@ -1614,16 +1721,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
- /> :
+ />
+ ) : (
<ComponentTag
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
NativeWidth={returnZero}
NativeHeight={returnZero}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
- scaleField={this.SidebarKey + "-scale"}
+ scaleField={this.SidebarKey + '-scale'}
isAnnotationOverlay={false}
select={emptyFunction}
scaling={this.sidebarContentScaling}
@@ -1637,48 +1745,54 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
setHeight={this.setSidebarHeight}
fitContentsToBox={this.fitContentsToBox}
noSidebar={true}
- fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />;
+ fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`}
+ />
+ );
};
- return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.ActiveTool !== InkTool.None ? "-inking" : "")}
- style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
- </div>;
+ return (
+ <div className={'formattedTextBox-sidebar' + (CurrentUserUtils.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
+ </div>
+ );
}
render() {
TraceMobx();
const active = this.props.isContentActive() || this.props.isSelected();
const selected = active;
const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
- const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
+ const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : '';
const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
const minimal = this.props.ignoreAutoHeight;
const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
- const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0);
- const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : "";
- const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
- return (styleFromString?.height === "0px" ? (null) :
- <div className="formattedTextBox-cont"
+ const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
+ const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : '';
+ const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ return styleFromString?.height === '0px' ? null : (
+ <div
+ className="formattedTextBox-cont"
onWheel={e => this.props.isContentActive() && e.stopPropagation()}
style={{
transform: this.props.dontScale ? undefined : `scale(${scale})`,
- transformOrigin: this.props.dontScale ? undefined : "top left",
+ transformOrigin: this.props.dontScale ? undefined : 'top left',
width: this.props.dontScale ? undefined : `${100 / scale}%`,
height: this.props.dontScale ? undefined : `${100 / scale}%`,
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
- ...styleFromString
+ ...styleFromString,
}}>
- <div className={`formattedTextBox-cont`} ref={this._ref}
+ <div
+ className={`formattedTextBox-cont`}
+ ref={this._ref}
style={{
- overflow: this.autoHeight ? "hidden" : undefined,
- height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? "max-content" : undefined),
+ overflow: this.autoHeight ? 'hidden' : undefined,
+ height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
- fontWeight: Cast(this.layoutDoc._fontWeight, "string", null) as any,
- fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
- pointerEvents: interactive ? undefined : "none",
+ fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any,
+ fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'),
+ pointerEvents: interactive ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -1689,31 +1803,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
onMouseUp={this.onMouseUp}
- onDoubleClick={this.onDoubleClick}
- >
- <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
+ onDoubleClick={this.onDoubleClick}>
+ <div
+ className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
+ ref={this._scrollRef}
style={{
- width: this.props.dontSelectOnLoad ? "100%" : `calc(100% - ${this.sidebarWidthPercent})`,
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
- overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
+ width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
+ pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+ overflow: this.layoutDoc._singleLine ? 'hidden' : undefined,
}}
- onScroll={this.onScroll} onDrop={this.ondrop} >
- <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
+ onScroll={this.onScroll}
+ onDrop={this.ondrop}>
+ <div
+ className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+ ref={this.createDropTarget}
style={{
padding: StrCast(this.layoutDoc._textBoxPadding),
paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? "none" : undefined) : undefined
+ pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
}}
/>
</div>
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle}
- {!this.layoutDoc._showAudio ? (null) : this.audioHandle}
+ {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
+ {!this.layoutDoc._showAudio ? null : this.audioHandle}
</div>
- </div >
+ </div>
);
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 3e673c0b2..bdf59863b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,18 +1,25 @@
-import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc } from "../../../../fields/Doc";
-import { DocServer } from "../../../DocServer";
-import { LinkDocPreview } from "../LinkDocPreview";
-import { FormattedTextBox } from "./FormattedTextBox";
+import { Mark, ResolvedPos } from 'prosemirror-model';
+import { EditorState } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
+import { LinkDocPreview } from '../LinkDocPreview';
+import { FormattedTextBox } from './FormattedTextBox';
import './FormattedTextBoxComment.scss';
-import { schema } from "./schema_rts";
+import { schema } from './schema_rts';
-export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); }
-export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); }
-export function findLinkMark(marks: 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: Mark[]) => Mark | undefined) {
- let before = 0, nbef = rpos.nodeBefore;
+export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid);
+}
+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;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
@@ -20,8 +27,9 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma
}
return before;
}
-export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let after = 0, naft = rpos.nodeAfter;
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
+ let after = 0,
+ naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
@@ -32,7 +40,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
// this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
// this will also display metadata information about text when the view is configured to display things like other people who authored text.
-//
+//
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
@@ -43,11 +51,11 @@ export class FormattedTextBoxComment {
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
- const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div");
- const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div");
- tooltip.className = "FormattedTextBox-tooltip";
- tooltipText.className = "FormattedTextBox-tooltipText";
- tooltip.style.display = "none";
+ const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div'));
+ const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div'));
+ tooltip.className = 'FormattedTextBox-tooltip';
+ tooltipText.className = 'FormattedTextBox-tooltipText';
+ tooltip.style.display = 'none';
tooltip.appendChild(tooltipText);
tooltip.onpointerdown = (e: PointerEvent) => {
const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
@@ -55,38 +63,47 @@ export class FormattedTextBoxComment {
e.stopPropagation();
e.preventDefault();
};
- document.getElementById("root")?.appendChild(tooltip);
+ document.getElementById('root')?.appendChild(tooltip);
}
}
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
- FormattedTextBoxComment.tooltip.style.display = "none";
+ FormattedTextBoxComment.tooltip.style.display = 'none';
}
public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
FormattedTextBoxComment.startUserMarkRegion = start;
FormattedTextBoxComment.endUserMarkRegion = end;
FormattedTextBoxComment.userMark = mark;
- FormattedTextBoxComment.tooltip.style.display = "";
+ FormattedTextBoxComment.tooltip.style.display = '';
}
static showCommentbox(view: EditorView, nbef: number) {
const state = view.state;
// 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),
+ 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();
+ 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)
const left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
- FormattedTextBoxComment.tooltip.style.display = "";
+ FormattedTextBoxComment.tooltip.style.left = left - box.left + 'px';
+ FormattedTextBoxComment.tooltip.style.bottom = box.bottom - start.top + 'px';
+ FormattedTextBoxComment.tooltip.style.display = '';
}
- static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "", linkDoc: string = "") {
+ static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '') {
FormattedTextBoxComment.textBox = textBox;
- if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) {
- FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h), linkDoc);
+ if (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection)) {
+ FormattedTextBoxComment.setupPreview(
+ view,
+ textBox,
+ hrefs
+ ?.trim()
+ .split(' ')
+ .filter(h => h),
+ linkDoc
+ );
}
}
@@ -104,25 +121,27 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
}
if (mark && child && ((nbef && naft) || !noselection)) {
- FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString();
+ FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + ' on ' + new Date(mark.attrs.modified * 1000).toLocaleString();
FormattedTextBoxComment.showCommentbox(view, nbef);
} else FormattedTextBoxComment.Hide();
}
- // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
+ // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
if (state.selection.$from && hrefs?.length) {
const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- nbef && naft && LinkDocPreview.SetLinkInfo({
- docProps: textBox.props,
- linkSrc: textBox.rootDoc,
- linkDoc: linkDoc ? DocServer.GetCachedRefField(linkDoc) as Doc : undefined,
- location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
- hrefs,
- showHeader: true
- });
+ nbef &&
+ naft &&
+ LinkDocPreview.SetLinkInfo({
+ docProps: textBox.props,
+ linkSrc: textBox.rootDoc,
+ linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
+ location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
+ hrefs,
+ showHeader: true,
+ });
}
}
- destroy() { }
-} \ No newline at end of file
+ destroy() {}
+}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index c66cb502e..31552cf1b 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,17 +1,17 @@
-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 } from "prosemirror-schema-list";
-import { EditorState, TextSelection, Transaction } from "prosemirror-state";
-import { liftTarget } from "prosemirror-transform";
-import { AclAugment, AclSelfEdit, Doc } from "../../../../fields/Doc";
-import { GetEffectiveAcl } from "../../../../fields/util";
-import { Utils } from "../../../../Utils";
-import { Docs } from "../../../documents/Documents";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
-
-const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
+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 } from 'prosemirror-schema-list';
+import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
+import { liftTarget } from 'prosemirror-transform';
+import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc';
+import { GetEffectiveAcl } from '../../../../fields/util';
+import { Utils } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
+
+const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
export type KeyMap = { [key: string]: any };
@@ -20,12 +20,12 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
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);
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && 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++;
}
- tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth, }, node.marks);
+ tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth }, node.marks);
}
});
return tx2;
@@ -45,7 +45,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const canEdit = (state: any) => {
switch (GetEffectiveAcl(props.DataDoc)) {
- case AclAugment: return false;
+ case AclAugment:
+ return false;
case AclSelfEdit:
for (var i = state.selection.from; i < state.selection.to; i++) {
const marks = state.doc.resolve(i)?.marks?.();
@@ -58,95 +59,102 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return true;
};
- const toggleEditableMark = (mark: any) => (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+ const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
//History commands
- bind("Mod-z", undo);
- bind("Shift-Mod-z", redo);
- !mac && bind("Mod-y", redo);
+ bind('Mod-z', undo);
+ bind('Shift-Mod-z', redo);
+ !mac && bind('Mod-y', redo);
//Commands to modify Mark
- bind("Mod-b", toggleEditableMark(schema.marks.strong));
- bind("Mod-B", toggleEditableMark(schema.marks.strong));
+ bind('Mod-b', toggleEditableMark(schema.marks.strong));
+ bind('Mod-B', toggleEditableMark(schema.marks.strong));
- bind("Mod-e", toggleEditableMark(schema.marks.em));
- bind("Mod-E", toggleEditableMark(schema.marks.em));
+ bind('Mod-e', toggleEditableMark(schema.marks.em));
+ bind('Mod-E', toggleEditableMark(schema.marks.em));
- bind("Mod-*", toggleEditableMark(schema.marks.code));
+ bind('Mod-*', toggleEditableMark(schema.marks.code));
- bind("Mod-u", toggleEditableMark(schema.marks.underline));
- bind("Mod-U", toggleEditableMark(schema.marks.underline));
+ bind('Mod-u', toggleEditableMark(schema.marks.underline));
+ bind('Mod-U', toggleEditableMark(schema.marks.underline));
//Commands for lists
- bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
+ 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("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ 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('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) { // couldn't sink into an existing list, so wrap in a new one
- const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
- if (!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+ if (
+ !sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
- // 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]);
dispatch(tx3);
- })) {
- console.log("bullet promote fail");
+ })
+ ) {
+ // couldn't sink into an existing list, so wrap in a new one
+ const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
+ if (
+ !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ // 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]);
+ dispatch(tx3);
+ })
+ ) {
+ console.log('bullet promote fail');
}
}
});
- bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) {
- console.log("bullet demote fail");
+ if (
+ !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
+ console.log('bullet demote fail');
}
});
//Command to create a new Tab with a PDF of all the command shortcuts
- bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 });
- props.addDocTab(newDoc, "add:right");
+ bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
+ props.addDocTab(newDoc, 'add:right');
});
//Commands to modify BlockType
- bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
- bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
- bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
+ 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));
- bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))));
+ bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() }))));
for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
+ 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
const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
+ bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
//Command to unselect all
- bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ 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();
@@ -158,24 +166,29 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return tx;
};
-
- bind("Alt-Enter", () => props.onKey?.(event, props) ? true : true);
- bind("Ctrl-Enter", () => props.onKey?.(event, props) ? true : true);
+ bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
- bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
- if (!deleteSelection(state, (tx: Transaction<S>) => {
- dispatch(updateBullets(tx, schema));
- })) {
- if (!joinBackward(state, (tx: Transaction<S>) => {
+ if (
+ !deleteSelection(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
- })) {
- if (!selectNodeBackward(state, (tx: Transaction<S>) => {
+ })
+ ) {
+ if (
+ !joinBackward(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
- })) {
+ })
+ ) {
+ if (
+ !selectNodeBackward(state, (tx: Transaction) => {
+ dispatch(updateBullets(tx, schema));
+ })
+ ) {
return false;
}
}
@@ -185,8 +198,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
//command to break line
- bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-
+ bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
@@ -200,25 +212,29 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
}
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- const cr = state.selection.$from.node().textContent.endsWith("\n");
+ const cr = state.selection.$from.node().textContent.endsWith('\n');
if (cr || !newlineInCode(state, dispatch as any)) {
- if (!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) {
+ if (
+ !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
const fromattrs = state.selection.$from.node().attrs;
- if (!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);
- splitMetadata(marks, tx4);
- if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
- dispatch(tx4);
- }
- } else dispatch(tx3.insertText("\r\n"));
- })) {
+ if (
+ !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);
+ splitMetadata(marks, tx4);
+ if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) {
+ dispatch(tx4);
+ }
+ } else dispatch(tx3.insertText('\r\n'));
+ })
+ ) {
return false;
}
}
@@ -227,16 +243,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
//Command to create a blank space
- bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
- bind("Alt-ArrowUp", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinUp(state, dispatch as any));
- bind("Alt-ArrowDown", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinDown(state, dispatch as any));
- bind("Mod-BracketLeft", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && lift(state, dispatch as any));
+ bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any));
+ bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any));
+ bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any));
const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
@@ -246,8 +262,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return false;
});
- bind("Shift-Enter", cmd);
+ bind('Shift-Enter', cmd);
return keys;
}
-
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 98343a261..22ca76b2e 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,36 +1,35 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { lift, wrapIn } from "prosemirror-commands";
-import { Mark, MarkType, Node as ProsNode, ResolvedPos } from "prosemirror-model";
-import { wrapInList } from "prosemirror-schema-list";
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc } from "../../../../fields/Doc";
-import { Cast, StrCast } from "../../../../fields/Types";
-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 { FieldViewProps } from "../FieldView";
-import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox";
-import { updateBullets } from "./ProsemirrorExampleTransfer";
-import "./RichTextMenu.scss";
-import { schema } from "./schema_rts";
-const { toggleMark } = require("prosemirror-commands");
-
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { lift, wrapIn } from 'prosemirror-commands';
+import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
+import { wrapInList } from 'prosemirror-schema-list';
+import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
+import { Doc } from '../../../../fields/Doc';
+import { Cast, StrCast } from '../../../../fields/Types';
+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 { FieldViewProps } from '../FieldView';
+import { FormattedTextBox, FormattedTextBoxProps } 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> {
+export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable static Instance: RichTextMenu;
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
private _linkToRef = React.createRef<HTMLInputElement>();
@observable public view?: EditorView;
- public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+ public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
@@ -43,21 +42,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _subscriptActive: boolean = false;
@observable private _superscriptActive: boolean = false;
- @observable private _activeFontSize: string = "13px";
- @observable private _activeFontFamily: string = "";
- @observable private activeListType: string = "";
- @observable private _activeAlignment: string = "left";
+ @observable private _activeFontSize: string = '13px';
+ @observable private _activeFontFamily: string = '';
+ @observable private activeListType: string = '';
+ @observable private _activeAlignment: string = 'left';
@observable private brushMarks: Set<Mark> = new Set();
@observable private showBrushDropdown: boolean = false;
- @observable private _activeFontColor: string = "black";
+ @observable private _activeFontColor: string = 'black';
@observable private showColorDropdown: boolean = false;
- @observable private activeHighlightColor: string = "transparent";
+ @observable private activeHighlightColor: string = 'transparent';
@observable private showHighlightDropdown: boolean = false;
- @observable private currentLink: string | undefined = "";
+ @observable private currentLink: string | undefined = '';
@observable private showLinkDropdown: boolean = false;
_reaction: IReactionDisposer | undefined;
@@ -72,24 +71,44 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
componentDidMount() {
- this._reaction = reaction(() => SelectionManager.Views(),
- () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true));
+ this._reaction = reaction(
+ () => SelectionManager.Views(),
+ () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true)
+ );
}
componentWillUnmount() {
this._reaction?.();
}
- @computed get noAutoLink() { return this._noLinkActive; }
- @computed get bold() { return this._boldActive; }
- @computed get underline() { return this._underlineActive; }
- @computed get italics() { return this._italicsActive; }
- @computed get strikeThrough() { return this._strikethroughActive; }
- @computed get fontColor() { return this._activeFontColor; }
- @computed get fontFamily() { return this._activeFontFamily; }
- @computed get fontSize() { return this._activeFontSize; }
- @computed get textAlign() { return this._activeAlignment; }
+ @computed get noAutoLink() {
+ return this._noLinkActive;
+ }
+ @computed get bold() {
+ return this._boldActive;
+ }
+ @computed get underline() {
+ return this._underlineActive;
+ }
+ @computed get italics() {
+ return this._italicsActive;
+ }
+ @computed get strikeThrough() {
+ return this._strikethroughActive;
+ }
+ @computed get fontColor() {
+ return this._activeFontColor;
+ }
+ @computed get fontFamily() {
+ return this._activeFontFamily;
+ }
+ @computed get fontSize() {
+ return this._activeFontSize;
+ }
+ @computed get textAlign() {
+ return this._activeAlignment;
+ }
- public delayHide = () => this._delayHide = true;
+ public delayHide = () => (this._delayHide = true);
@action
public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) {
@@ -118,16 +137,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, "10px")) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "...";
- this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "...";
+ this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
}
- setMark = (mark: Mark, state: EditorState<any>, dispatch: any, dontToggle: boolean = false) => {
+ setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
const node = (state.selection as NodeSelection).node;
if (node?.type === schema.nodes.ordered_list) {
@@ -140,7 +159,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
} else if (dontToggle) {
toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
const { from, $from, to, empty } = tx.selection;
- if (!tx.doc.rangeHasMark(from, to, mark.type)) { // hack -- should have just set the mark in the first place
+ if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+ // hack -- should have just set the mark in the first place
toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
} else dispatch(tx);
});
@@ -148,7 +168,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
- }
+ };
// finds font sizes and families in selection
getActiveAlignment() {
@@ -156,11 +176,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const path = (this.view.state.selection.$from as any).path;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
- return path[i].attrs.align || "left";
+ return path[i].attrs.align || 'left';
}
}
}
- return "left";
+ return 'left';
}
// finds font sizes and families in selection
@@ -176,7 +196,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle;
}
}
- return "";
+ return '';
}
// finds font sizes and families in selection
@@ -190,7 +210,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.TextView.props.isSelected(true)) {
const state = this.view.state;
const pos = this.view.state.selection.$from;
- const marks: Mark<any>[] = [...(state.storedMarks ?? [])];
+ const marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.selection.empty) {
const ref_node = this.reference_node(pos);
marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
@@ -209,10 +229,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return { activeFamilies, activeSizes, activeColors, activeHighlights };
}
- getMarksInSelection(state: EditorState<any>) {
+ getMarksInSelection(state: EditorState) {
const found = new Set<Mark>();
const { from, to } = state.selection as TextSelection;
- state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+ state.doc.nodesBetween(from, to, node => node.marks.forEach(m => found.add(m)));
return found;
}
@@ -234,14 +254,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
return false;
});
- }
- else {
+ } else {
const pos = this.view.state.selection.$from;
const ref_node: ProsNode | null = this.reference_node(pos);
if (ref_node !== null && ref_node !== this.view.state.doc) {
if (ref_node.isText) {
- }
- else {
+ } else {
return [];
}
activeMarks = markGroup.filter(mark_type => {
@@ -275,13 +293,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
activeMarks.forEach(mark => {
switch (mark.name) {
- case "noAutoLinkAnchor": this._noLinkActive = true; break;
- case "strong": this._boldActive = true; break;
- case "em": this._italicsActive = true; break;
- case "underline": this._underlineActive = true; break;
- case "strikethrough": this._strikethroughActive = true; break;
- case "subscript": this._subscriptActive = true; break;
- case "superscript": this._superscriptActive = true; break;
+ case 'noAutoLinkAnchor':
+ this._noLinkActive = true;
+ break;
+ case 'strong':
+ this._boldActive = true;
+ break;
+ case 'em':
+ this._italicsActive = true;
+ break;
+ case 'underline':
+ this._underlineActive = true;
+ break;
+ case 'strikethrough':
+ this._strikethroughActive = true;
+ break;
+ case 'subscript':
+ this._subscriptActive = true;
+ break;
+ case 'superscript':
+ this._superscriptActive = true;
+ break;
}
});
}
@@ -293,14 +325,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.TextView.autoLink();
this.view.focus();
}
- }
+ };
toggleBold = () => {
if (this.view) {
const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong);
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
+ };
toggleUnderline = () => {
if (this.view) {
@@ -308,7 +340,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
+ };
toggleItalics = () => {
if (this.view) {
@@ -316,13 +348,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
-
+ };
setFontSize = (fontSize: string) => {
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))) {
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
this.TextView.dataDoc.fontSize = fontSize;
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
@@ -333,7 +363,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.updateMenu(this.view, undefined, this.props);
}
}
- }
+ };
setFontFamily = (family: string) => {
if (this.view) {
@@ -342,7 +372,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
}
- }
+ };
setHighlight(color: String, view: EditorView, dispatch: any) {
const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color });
@@ -362,8 +392,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
- changeListType = (nodeType: Node | undefined) => {
- if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return;
+ changeListType = (mapStyle: string) => {
+ const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
+ const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle });
+ if (!this.view || nodeType?.attrs.mapStyle === '') return;
const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
let inList: any = undefined;
@@ -377,17 +409,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
- if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
+ if (
+ inList ||
+ !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
- this.view!.dispatch(tx2);
- })) {
+ this.view!.dispatch(tx2);
+ })
+ ) {
const tx2 = this.view.state.tr;
if (nodeType && (inList || nextIsOL)) {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from,
- inList ? fromList + inList.nodeSize : this.view.state.selection.to);
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
this.view.dispatch(tx3);
@@ -395,9 +429,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
- }
+ };
- insertSummarizer(state: EditorState<any>, dispatch: any) {
+ insertSummarizer(state: EditorState, dispatch: any) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
const tr = state.tr;
@@ -408,7 +442,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- align = (view: EditorView, dispatch: any, alignment: "left" | "right" | "center") => {
+ align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView.props.isSelected(true)) {
var tr = view.state.tr;
view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
@@ -422,9 +456,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
view.focus();
dispatch?.(tr);
}
- }
+ };
- insetParagraph(state: EditorState<any>, dispatch: any) {
+ 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) {
@@ -437,7 +471,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
dispatch?.(tr);
return true;
}
- outsetParagraph(state: EditorState<any>, dispatch: any) {
+ 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) {
@@ -451,7 +485,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- indentParagraph(state: EditorState<any>, dispatch: any) {
+ 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) => {
@@ -467,7 +501,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- hangingIndentParagraph(state: EditorState<any>, dispatch: any) {
+ hangingIndentParagraph(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) {
@@ -482,7 +516,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertBlockquote(state: EditorState<any>, dispatch: any) {
+ insertBlockquote(state: EditorState, dispatch: any) {
const path = (state.selection.$from as any).path;
if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
lift(state, dispatch);
@@ -492,20 +526,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertHorizontalRule(state: EditorState<any>, dispatch: any) {
+ insertHorizontalRule(state: EditorState, dispatch: any) {
dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView());
return true;
}
- @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+ @action toggleBrushDropdown() {
+ this.showBrushDropdown = !this.showBrushDropdown;
+ }
// todo: add brushes to brushMap to save with a style name
onBrushNameKeyPress = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
- this._brushNameRef.current!.style.background = "lightGray";
+ this._brushNameRef.current!.style.background = 'lightGray';
}
- }
+ };
_brushNameRef = React.createRef<HTMLInputElement>();
@action
@@ -514,7 +550,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- fillBrush(state: EditorState<any>, dispatch: any) {
+ fillBrush(state: EditorState, dispatch: any) {
if (!this.view) return;
if (!Array.from(this.brushMarks.keys()).length) {
@@ -522,68 +558,81 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (selected_marks.size >= 0) {
this.brushMarks = selected_marks;
}
- }
- else {
+ } else {
const { from, to, $from } = this.view.state.selection;
if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
if (to - from > 0) {
this.view.dispatch(this.view.state.tr.removeMark(from, to));
- Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
- this.setMark(mark, this.view!.state, this.view!.dispatch);
- });
+ Array.from(this.brushMarks)
+ .filter(m => m.type !== schema.marks.user_mark)
+ .forEach((mark: Mark) => {
+ this.setMark(mark, this.view!.state, this.view!.dispatch);
+ });
}
}
}
}
- get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
- get TextViewFieldKey() { return this.TextView?.props.fieldKey; }
-
-
-
- @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
+ get TextView() {
+ return (this.view as any)?.TextView as FormattedTextBox;
+ }
+ get TextViewFieldKey() {
+ return this.TextView?.props.fieldKey;
+ }
+ @action setActiveHighlight(color: string) {
+ this.activeHighlightColor = color;
+ }
- @action setCurrentLink(link: string) { this.currentLink = link; }
+ @action setCurrentLink(link: string) {
+ this.currentLink = link;
+ }
createLinkButton() {
const self = this;
function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
self.TextView?.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change");
+ UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change');
}
- const link = this.currentLink ? this.currentLink : "";
+ const link = this.currentLink ? this.currentLink : '';
- 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>
- </Tooltip>;
+ 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>
+ </Tooltip>
+ );
- const dropdownContent =
+ const dropdownContent = (
<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")}>Apply hyperlink</button>
+ <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+ Apply hyperlink
+ </button>
<div className="divider" />
- <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
- </div>;
+ <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+ Remove link
+ </button>
+ </div>
+ );
- return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+ return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
}
async getTextLinkTargetTitle() {
if (!this.view) return;
const node = this.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type.name === "link");
+ const link = node && node.marks.find(m => m.type.name === 'link');
if (link) {
const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
if (href) {
if (href.indexOf(Doc.localServerPath()) === 0) {
- const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0];
+ const linkclicked = href.replace(Doc.localServerPath(), '').split('?')[0];
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
@@ -612,8 +661,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
- }
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
+ };
@undoBatch
@action
@@ -624,13 +673,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const allAnchors = linkAnchor.attrs.allAnchors.slice();
this.TextView.RemoveAnchorFromSelection(allAnchors);
// bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
- allAnchors.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));
- });
+ allAnchors
+ .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));
+ });
}
}
- }
+ };
linkExtend($start: ResolvedPos, href: string) {
const mark = this.view!.state.schema.marks.linkAnchor;
@@ -651,7 +702,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return { from: startPos, to: endPos };
}
- reference_node(pos: ResolvedPos<any>): ProsNode | null {
+ reference_node(pos: ResolvedPos): ProsNode | null {
if (!this.view) return null;
let ref_node: ProsNode = this.view.state.doc;
@@ -671,7 +722,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
ref_node = node;
skip = true;
}
-
});
}
}
@@ -755,21 +805,19 @@ interface ButtonDropdownProps {
openDropdownOnButton?: boolean;
link?: boolean;
pdf?: boolean;
-
}
@observer
export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
-
@observable private showDropdown: boolean = false;
private ref: HTMLDivElement | null = null;
componentDidMount() {
- document.addEventListener("pointerdown", this.onBlur);
+ document.addEventListener('pointerdown', this.onBlur);
}
componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onBlur);
+ document.removeEventListener('pointerdown', this.onBlur);
}
@action
@@ -785,7 +833,7 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
e.preventDefault();
e.stopPropagation();
this.toggleDropdown();
- }
+ };
onBlur = (e: PointerEvent) => {
setTimeout(() => {
@@ -793,37 +841,40 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
this.setShowDropdown(false);
}
}, 0);
- }
-
+ };
render() {
return (
- <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
- {!this.props.pdf ?
+ <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}
- <div style={{ marginTop: "-8.5", position: "relative" }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+ <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
- :
+ ) : (
<>
{this.props.button}
<button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</button>
- </>}
- {this.showDropdown ? this.props.dropdownContent : (null)}
+ </>
+ )}
+ {this.showDropdown ? this.props.dropdownContent : null}
</div>
);
}
}
-
interface RichTextMenuPluginProps {
editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- render() { return null; }
- update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); }
-} \ No newline at end of file
+ render() {
+ return null;
+ }
+ update(view: EditorView, lastState: EditorState | undefined) {
+ RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps);
+ }
+}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 8851d52e4..1916b94bf 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 { DataSym, Doc } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { NumCast, StrCast } from "../../../../fields/Types";
-import { normalizeEmail } from "../../../../fields/util";
-import { returnFalse, Utils } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { wrappingInputRule } from "./prosemirrorPatches";
-import { RichTextMenu } from "./RichTextMenu";
-import { schema } from "./schema_rts";
+import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
+import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { DataSym, Doc } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { ComputedField } from '../../../../fields/ScriptField';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { normalizeEmail } from '../../../../fields/util';
+import { returnFalse, Utils } from '../../../../Utils';
+import { DocServer } from '../../../DocServer';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { FormattedTextBox } from './FormattedTextBox';
+import { wrappingInputRule } from './prosemirrorPatches';
+import { RichTextMenu } from './RichTextMenu';
+import { schema } from './schema_rts';
export class RichTextRules {
public Document: Doc;
@@ -34,9 +34,9 @@ export class RichTextRules {
wrappingInputRule(
/^1\.\s$/,
schema.nodes.ordered_list,
- () => ({ mapStyle: "decimal", bulletStyle: 1 }),
+ () => ({ mapStyle: 'decimal', bulletStyle: 1 }),
(match: any, node: any) => node.childCount + node.attrs.order === +match[1],
- ((type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'decimal', bulletStyle: 1 } })) as any
),
// A. create alphabetical ordered list
@@ -45,360 +45,324 @@ export class RichTextRules {
schema.nodes.ordered_list,
// match => {
() => {
- return ({ mapStyle: "multi", bulletStyle: 1 });
+ return { mapStyle: 'multi', bulletStyle: 1 };
// return ({ order: +match[1] })
},
(match: any, node: any) => {
return node.childCount + node.attrs.order === +match[1];
},
- ((type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
),
// * + - create bullet list
- wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list,
+ wrappingInputRule(
+ /^\s*([-+*])\s$/,
+ schema.nodes.ordered_list,
// match => {
- () => ({ mapStyle: "bullet" }), // ({ order: +match[1] })
+ () => ({ mapStyle: 'bullet' }), // ({ order: +match[1] })
(match: any, node: any) => node.childCount + node.attrs.order === +match[1],
- ((type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'bullet' } })) as any
),
// ``` create code block
textblockTypeInputRule(/^```$/, schema.nodes.code_block),
- // %<font-size> set the font size
- new InputRule(
- new RegExp(/%([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 }));
- }),
+ // %<font-size> set the font size
+ new InputRule(new RegExp(/%([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) => {
- const textDoc = this.Document[DataSym];
- const numInlines = NumCast(textDoc.inlineTextCount);
- textDoc.inlineTextCount = numInlines + 1;
- const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
- const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
- const textDocInline = Docs.Create.TextDocument("", { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9px", title: "inline comment" });
- textDocInline.title = inlineFieldKey; // give the annotation its own title
- textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
- textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
- textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
- textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
- textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
- textDoc[inlineFieldKey] = ""; // set a default value for the annotation
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" });
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced;
- }),
-
+ new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
+ const textDoc = this.Document[DataSym];
+ const numInlines = NumCast(textDoc.inlineTextCount);
+ textDoc.inlineTextCount = numInlines + 1;
+ const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to
+ const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
+ const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' });
+ textDocInline.title = inlineFieldKey; // give the annotation its own title
+ textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+ textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+ textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
+ textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
+ textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+ textDoc[inlineFieldKey] = ''; // set a default value for the annotation
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: textDocInline[Id], float: 'right' });
+ const sm = state.storedMarks || undefined;
+ const replaced = node
+ ? state.tr
+ .insert(start, newNode)
+ .replaceRangeWith(start + 1, end + 1, dashDoc)
+ .insertText(' ', start + 2)
+ .setStoredMarks([...node.marks, ...(sm ? 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) => {
- 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--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%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--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// 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) => {
- 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--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%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--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// 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) => {
- 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;
- 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--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%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;
+ 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--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// center justify text
- new InputRule(
- new RegExp(/%\^/),
- (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)));
- }
- }),
+ new InputRule(new RegExp(/%\^/), (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)));
+ }
+ }),
// left justify text
- new InputRule(
- new RegExp(/%\[/),
- (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)));
- }
- }),
+ new InputRule(new RegExp(/%\[/), (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)));
+ }
+ }),
// right justify text
- new InputRule(
- new RegExp(/%\]/),
- (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)));
- }
- }),
-
+ new InputRule(new RegExp(/%\]/), (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)));
+ }
+ }),
// %f create footnote
- new InputRule(
- new RegExp(/%f$/),
- (state, match, start, end) => {
- const newNode = schema.nodes.footnote.create({});
- const tr = state.tr;
- tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
- return tr.setSelection(new NodeSelection( // select the footnote node to open its display
- tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
- tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0))));
- }),
+ new InputRule(new RegExp(/%f$/), (state, match, start, end) => {
+ const newNode = schema.nodes.footnote.create({});
+ const tr = state.tr;
+ tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
+ return tr.setSelection(
+ new NodeSelection( // select the footnote node to open its display
+ tr.doc.resolve(
+ // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+ tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)
+ )
+ )
+ );
+ }),
// activate a style by name using prefix '%<color name>'
- new InputRule(
- new RegExp(/%[a-z]+$/),
- (state, match, start, end) => {
-
- const color = match[0].substring(1, match[0].length);
- const marks = RichTextMenu.Instance._brushMap.get(color);
+ new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => {
+ const color = match[0].substring(1, match[0].length);
+ const marks = RichTextMenu.Instance._brushMap.get(color);
- if (marks) {
- const tr = state.tr.deleteRange(start, end);
- return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
- }
+ if (marks) {
+ const tr = state.tr.deleteRange(start, end);
+ return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+ }
- const isValidColor = (strColor: string) => {
- const s = new Option().style;
- s.color = strColor;
- return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
- };
+ const isValidColor = (strColor: string) => {
+ const s = new Option().style;
+ s.color = strColor;
+ return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+ };
- if (isValidColor(color)) {
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
- }
+ if (isValidColor(color)) {
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+ }
- return null;
- }),
+ return null;
+ }),
// stop using active style
- new InputRule(
- new RegExp(/%%$/),
- (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 !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
- }),
-
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
- // [[<fieldKey> : <Doc>]]
- // [[:Doc]] => hyperlink
- // [[fieldKey]] => show field
+ new InputRule(new RegExp(/%%$/), (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)
+ : tr;
+ }),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // [[<fieldKey> : <Doc>]]
+ // [[:Doc]] => hyperlink
+ // [[fieldKey]] => show field
// [[fieldKey=value]] => show field and also set its value
// [[fieldKey:Doc]] => show field of doc
- new InputRule(
- new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
- (state, match, start, end) => {
- const fieldKey = match[1];
- const rawdocid = match[3];
- const docid = rawdocid ? normalizeEmail((!rawdocid.includes("@") ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1))) : undefined;
- const value = match[2]?.substring(1);
- if (!fieldKey) {
- if (docid) {
- DocServer.GetRefField(docid).then(docx => {
- const rstate = this.TextBox.EditorView?.state;
- const selection = rstate?.selection.$from.pos;
- if (rstate) {
- this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
- }
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid);
- DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to:portal from", undefined);
-
- const fstate = this.TextBox.EditorView?.state;
- if (fstate && selection) {
- this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
- }
- });
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
- }
- return state.tr;
+ new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
+ const fieldKey = match[1];
+ const rawdocid = match[3];
+ const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined;
+ const value = match[2]?.substring(1);
+ if (!fieldKey) {
+ if (docid) {
+ DocServer.GetRefField(docid).then(docx => {
+ const rstate = this.TextBox.EditorView?.state;
+ const selection = rstate?.selection.$from.pos;
+ if (rstate) {
+ this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+ }
+ const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid);
+ DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined);
+
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate && selection) {
+ this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
+ }
+ });
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
}
- if (value !== "" && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
- }
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
-
+ return state.tr;
+ }
+ if (value !== '' && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // 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) => {
- const title = match[1];
- this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
-
- this.TextBox.makeLinkAnchor(undefined, "add:right", `https://en.wikipedia.org/wiki/${title.trim()}`, "wikipedia reference");
-
- const fstate = this.TextBox.EditorView?.state;
- if (fstate) {
- const tr = fstate?.tr.deleteRange(start, start + 5);
- return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(" ");
- }
- return state.tr;
- }),
+ new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
+ const title = match[1];
+ this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
+
+ this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference');
+
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate) {
+ const tr = fstate?.tr.deleteRange(start, start + 5);
+ return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' ');
+ }
+ return state.tr;
+ }),
- // create an inline view of a document {{ <layoutKey> : <Doc> }}
- // {{:Doc}} => show default view of document
- // {{<layout>}} => show layout for this doc
+ // create an inline view of a document {{ <layoutKey> : <Doc> }}
+ // {{:Doc}} => show default view of document
+ // {{<layout>}} => show layout for this doc
// {{<layout> : Doc}} => show layout for another doc
- new InputRule(
- new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/),
- (state, match, start, end) => {
- const fieldKey = match[1] || "";
- const fieldParam = match[2]?.replace("…", "...") || "";
- const rawdocid = match[3]?.substring(1);
- const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined;
- if (!fieldKey && !docid) return state.tr;
- docid && DocServer.GetRefField(docid).then(docx => {
+ new InputRule(new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), (state, match, start, end) => {
+ const fieldKey = match[1] || '';
+ const fieldParam = match[2]?.replace('…', '...') || '';
+ const rawdocid = match[3]?.substring(1);
+ const docid = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined;
+ if (!fieldKey && !docid) return state.tr;
+ docid &&
+ DocServer.GetRefField(docid).then(docx => {
if (!(docx instanceof Doc && docx)) {
Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docid);
}
});
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
- const sm = state.storedMarks || undefined;
- return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }),
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docid, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() });
+ const sm = state.storedMarks || undefined;
+ return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ }),
// 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) => {
- const tag = match[1];
- if (!tag) return state.tr;
- this.Document[DataSym]["#" + tag] = "#" + tag;
- const tags = StrCast(this.Document[DataSym].tags, ":");
- if (!tags.includes(`#${tag}:`)) {
- this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`;
- }
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
- return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(" ");
- }),
-
+ new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
+ const tag = match[1];
+ if (!tag) return state.tr;
+ this.Document[DataSym]['#' + tag] = '#' + tag;
+ const tags = StrCast(this.Document[DataSym].tags, ':');
+ if (!tags.includes(`#${tag}:`)) {
+ this.Document[DataSym].tags = `${tags + '#' + tag + ':'}`;
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag });
+ return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(' ');
+ }),
// # heading
- textblockTypeInputRule(
- new RegExp(/^(#{1,6})\s$/),
- schema.nodes.heading,
- match => {
- return ({ level: match[1].length });
- }
- ),
+ textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => {
+ return { 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) => {
-
- 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;
+ new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => {
+ if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+ 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;
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
+ if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- new InputRule(
- new RegExp(/%\(/),
- (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || [];
- const mark = state.schema.marks.summarizeInclusive.create();
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
- sm.push(mark);
- const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
- const content = selected.selection.content();
- const replaced = node ? selected.replaceRangeWith(start, end,
- schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
- state.tr;
+ new InputRule(new RegExp(/%\(/), (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();
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
- }),
+ sm.push(mark);
+ const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+ const content = selected.selection.content();
+ const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr;
- new InputRule(
- new RegExp(/%\)/),
- (state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
- }),
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+ }),
- ]
+ new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
+ return 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 c017db034..01acc3de9 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,35 +1,49 @@
-import { TextSelection } from "prosemirror-state";
-import { Fragment, Node, Slice } from "prosemirror-model";
+import { TextSelection } from 'prosemirror-state';
+import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom';
-import React = require("react");
+import React = require('react');
// 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
// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
export class SummaryView {
- _fieldWrapper: HTMLSpanElement; // container for label and value
+ dom: HTMLSpanElement; // container for label and value
constructor(node: any, view: any, getPos: any) {
const self = this;
- this._fieldWrapper = document.createElement("span");
- this._fieldWrapper.className = this.className(node.attrs.visibility);
- this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); };
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ 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.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();
+ };
const js = node.toJSON;
- node.toJSON = function () { return js.apply(this, arguments); };
+ node.toJSON = function () {
+ return js.apply(this, arguments);
+ };
- ReactDOM.render(<SummaryViewInternal />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ ReactDOM.render(<SummaryViewInternal />, this.dom);
+ (this as any).dom = this.dom;
}
- className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed');
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
updateSummarizedText(start: any, view: any) {
const mtype = view.state.schema.marks.summarize;
@@ -44,8 +58,7 @@ export class SummaryView {
if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
endPos = i + node.nodeSize - 1;
- }
- else skip = true;
+ } else skip = true;
}
});
}
@@ -56,26 +69,28 @@ export class SummaryView {
const visible = !node.attrs.visibility;
const attrs = { ...node.attrs, visibility: visible };
let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
- if (!visible) { // update summarized text and save in attrs
+ if (!visible) {
+ // update summarized text and save in attrs
textSelection = this.updateSummarizedText(getPos() + 1, view);
attrs.text = textSelection.content();
attrs.textslice = attrs.text.toJSON();
}
- view.dispatch(view.state.tr.
- setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
- replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
- setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
+ view.dispatch(
+ view.state.tr
+ .setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed)
+ .replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it
+ .setNodeMarkup(getPos(), undefined, attrs)
+ ); // update the attrs
e.preventDefault();
e.stopPropagation();
- this._fieldWrapper.className = this.className(visible);
- }
+ this.dom.className = this.className(visible);
+ };
}
-interface ISummaryView {
-}
+interface ISummaryView {}
// currently nothing needs to be rendered for the internal view of a summary.
export class SummaryViewInternal extends React.Component<ISummaryView> {
render() {
return <> </>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 2fde5c7ba..00c41e187 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,66 +1,70 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { Doc } from "../../../../fields/Doc";
+import React = require('react');
+import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
+import { Doc } from '../../../../fields/Doc';
-
-const emDOM: DOMOutputSpecArray = ["em", 0];
-const strongDOM: DOMOutputSpecArray = ["strong", 0];
-const codeDOM: DOMOutputSpecArray = ["code", 0];
+const emDOM: DOMOutputSpec = ['em', 0];
+const strongDOM: DOMOutputSpec = ['strong', 0];
+const codeDOM: DOMOutputSpec = ['code', 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
splitter: {
attrs: {
- id: { default: "" }
+ id: { default: '' },
},
toDOM(node: any) {
- return ["div", { className: "dummy" }, 0];
- }
+ return ['div', { className: 'dummy' }, 0];
+ },
},
-
// :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the
- // published document's title.
+ // published document's title.
// NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because
- // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since
+ // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since
// multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to
// disambiguate links from one another.
// Rendered and parsed as an `<a>`
// element.
autoLinkAnchor: {
attrs: {
- allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
+ allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
location: { default: null },
title: { default: null },
},
inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return {
- location: dom.getAttribute("location"),
- title: dom.getAttribute("title")
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'a[href]',
+ getAttrs(dom: any) {
+ return {
+ location: dom.getAttribute('location'),
+ title: dom.getAttribute('title'),
+ };
+ },
+ },
+ ],
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", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
- }
+ 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', { class: anchorids, 'data-targethrefs': targethrefs, 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
+ },
},
noAutoLinkAnchor: {
attrs: {},
inclusive: false,
- parseDOM: [{
- tag: "div", getAttrs(dom: any) {
- return {
- noAutoLink: dom.getAttribute("data-noAutoLink"),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'div',
+ getAttrs(dom: any) {
+ return {
+ noAutoLink: dom.getAttribute('data-noAutoLink'),
+ };
+ },
+ },
+ ],
toDOM(node: any) {
- return ["span", { "data-noAutoLink": "true" }, 0];
- }
+ return ['span', { 'data-noAutoLink': 'true' }, 0];
+ },
},
// :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text,
// and a title for use in menus and hover. `title`
@@ -68,31 +72,46 @@ export const marks: { [index: string]: MarkSpec } = {
// element.
linkAnchor: {
attrs: {
- allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
+ allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
location: { default: null },
title: { default: null },
- docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
+ docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
},
inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return {
- location: dom.getAttribute("location"),
- title: dom.getAttribute("title")
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'a[href]',
+ getAttrs(dom: any) {
+ return {
+ location: dom.getAttribute('location'),
+ title: dom.getAttribute('title'),
+ };
+ },
+ },
+ ],
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 node.attrs.docref && node.attrs.title ?
- ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", {
- ...node.attrs,
- class: "prosemirror-attribution",
- href: node.attrs.allAnchors[0].href,
- }, node.attrs.title], ["br"]] :
- //node.attrs.allLinks.length === 1 ?
- ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
+ 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 node.attrs.docref && node.attrs.title
+ ? [
+ 'div',
+ ['span', `"`],
+ ['span', 0],
+ ['span', `"`],
+ ['br'],
+ [
+ 'a',
+ {
+ ...node.attrs,
+ class: 'prosemirror-attribution',
+ href: node.attrs.allAnchors[0].href,
+ },
+ node.attrs.title,
+ ],
+ ['br'],
+ ]
+ : //node.attrs.allLinks.length === 1 ?
+ ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
// ["div", { class: "prosemirror-anchor" },
// ["span", { class: "prosemirror-linkBtn" },
// ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
@@ -102,254 +121,273 @@ export const marks: { [index: string]: MarkSpec } = {
// ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
// )]
// ];
- }
+ },
},
/** FONT SIZES */
pFontSize: {
- attrs: { fontSize: { default: "10px" } },
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" };
- }
- }],
- toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]
+ attrs: { fontSize: { default: '10px' } },
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]),
},
/* FONTS */
pFontFamily: {
- attrs: { family: { 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" };
- }
- }
- }],
- toDOM: (node) => node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]
+ attrs: { family: { 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' };
+ }
+ return { family: '' };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]),
},
// :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
pFontColor: {
- attrs: { color: { default: "" } },
+ attrs: { color: { default: '' } },
inclusive: true,
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { color: dom.getAttribute("color") };
- }
- }],
- toDOM: (node) => node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { color: dom.getAttribute('color') };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]),
},
marker: {
attrs: {
- highlight: { default: "transparent" }
+ highlight: { default: 'transparent' },
},
inclusive: true,
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { highlight: dom.getAttribute("backgroundColor") };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { highlight: dom.getAttribute('backgroundColor') };
+ },
+ },
+ ],
toDOM(node: any) {
return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
- }
+ },
},
// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
// Has parse rules that also match `<i>` and `font-style: italic`.
em: {
- parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
- toDOM() { return emDOM; }
+ parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style: italic' }],
+ toDOM() {
+ return emDOM;
+ },
},
// :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
// also match `<b>` and `font-weight: bold`.
strong: {
- parseDOM: [{ tag: "strong" },
- { tag: "b" },
- { style: "font-weight" }],
- toDOM() { return strongDOM; }
+ parseDOM: [{ tag: 'strong' }, { tag: 'b' }, { style: 'font-weight' }],
+ toDOM() {
+ return strongDOM;
+ },
},
strikethrough: {
- parseDOM: [
- { tag: 'strike' },
- { style: 'text-decoration=line-through' },
- { style: 'text-decoration-line=line-through' }
+ parseDOM: [{ tag: 'strike' }, { style: 'text-decoration=line-through' }, { style: 'text-decoration-line=line-through' }],
+ toDOM: () => [
+ 'span',
+ {
+ style: 'text-decoration-line:line-through',
+ },
],
- toDOM: () => ['span', {
- style: 'text-decoration-line:line-through'
- }]
},
subscript: {
excludes: 'superscript',
- parseDOM: [
- { tag: 'sub' },
- { style: 'vertical-align=sub' }
- ],
- toDOM: () => ['sub']
+ parseDOM: [{ tag: 'sub' }, { style: 'vertical-align=sub' }],
+ toDOM: () => ['sub'],
},
superscript: {
excludes: 'subscript',
- parseDOM: [
- { tag: 'sup' },
- { style: 'vertical-align=super' }
- ],
- toDOM: () => ['sup']
+ parseDOM: [{ tag: 'sup' }, { style: 'vertical-align=super' }],
+ toDOM: () => ['sup'],
},
mbulletType: {
attrs: {
- bulletType: { default: "decimal" }
+ bulletType: { default: 'decimal' },
},
toDOM(node: any) {
- return ['span', {
- style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
- }];
- }
+ return [
+ 'span',
+ {
+ style: `background: ${node.attrs.bulletType === 'decimal' ? 'yellow' : node.attrs.bulletType === 'upper-alpha' ? 'blue' : 'green'}`,
+ },
+ ];
+ },
},
metadata: {
toDOM() {
return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
- }
+ },
},
metadataKey: {
toDOM() {
return ['span', { style: 'font-style:italic; ' }];
- }
+ },
},
metadataVal: {
toDOM() {
return ['span'];
- }
+ },
},
summarizeInclusive: {
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline") return null;
- if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
+ if (style.textDecoration === 'underline') return null;
+ if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: solid') !== -1) {
return null;
}
}
return false;
- }
+ },
},
],
inclusive: true,
toDOM() {
- return ['span', {
- style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
- }];
- }
+ return [
+ 'span',
+ {
+ style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)',
+ },
+ ];
+ },
},
summarize: {
inclusive: false,
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline") return null;
- if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+ if (style.textDecoration === 'underline') return null;
+ if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: dotted') !== -1) {
return null;
}
}
return false;
- }
+ },
},
],
toDOM() {
- return ['span', {
- style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
- }];
- }
+ return [
+ 'span',
+ {
+ style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)',
+ },
+ ];
+ },
},
underline: {
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
+ if (style.textDecoration === 'underline' || p.parentElement.outerHTML.indexOf('text-decoration-style:line') !== -1) {
return null;
}
}
return false;
- }
- }
+ },
+ },
// { style: "text-decoration=underline" }
],
- toDOM: () => ['span', {
- style: 'text-decoration:underline;text-decoration-style:line'
- }]
+ toDOM: () => [
+ 'span',
+ {
+ style: 'text-decoration:underline;text-decoration-style:line',
+ },
+ ],
},
search_highlight: {
attrs: {
- selected: { default: false }
+ selected: { default: false },
},
parseDOM: [{ style: 'background: yellow' }],
toDOM(node: any) {
- return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }];
- }
+ return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }];
+ },
},
// the id of the user who entered the text
user_mark: {
attrs: {
- userid: { default: "" },
- modified: { default: "when?" }, // 1 second intervals since 1970
+ userid: { default: '' },
+ modified: { default: 'when?' }, // 1 second intervals since 1970
},
- excludes: "user_mark",
- group: "inline",
+ excludes: 'user_mark',
+ group: 'inline',
toDOM(node: any) {
- const uid = node.attrs.userid.replace(".", "").replace("@", "");
+ const uid = node.attrs.userid.replace('.', '').replace('@', '');
const min = Math.round(node.attrs.modified / 12);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : "";
- return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0];
- }
+ const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
+ return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0];
+ },
},
// the id of the user who entered the text
user_tag: {
attrs: {
- userid: { default: "" },
- modified: { default: "when?" }, // 1 second intervals since 1970
- tag: { default: "" }
+ userid: { default: '' },
+ modified: { default: 'when?' }, // 1 second intervals since 1970
+ tag: { default: '' },
},
- group: "inline",
+ group: 'inline',
inclusive: false,
toDOM(node: any) {
- const uid = node.attrs.userid.replace(".", "").replace("@", "");
- return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0];
- }
+ const uid = node.attrs.userid.replace('.', '').replace('@', '');
+ return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0];
+ },
},
-
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
- parseDOM: [{ tag: "code" }],
- toDOM() { return codeDOM; }
+ parseDOM: [{ tag: 'code' }],
+ toDOM() {
+ return codeDOM;
+ },
},
};
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 2fe0a67cb..5142b7da6 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,15 +1,18 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec";
+import React = require('react');
+import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
+import { listItem, orderedList } from 'prosemirror-schema-list';
+import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
-const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
- preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
+const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
+ hrDOM: DOMOutputSpec = ['hr'],
+ preDOM: DOMOutputSpec = ['pre', ['code', 0]],
+ brDOM: DOMOutputSpec = ['br'],
+ ulDOM: DOMOutputSpec = ['ul', 0];
function formatAudioTime(time: number) {
time = Math.round(time);
const hours = Math.floor(time / 60 / 60);
- const minutes = Math.floor(time / 60) - (hours * 60);
+ const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
@@ -19,67 +22,70 @@ function formatAudioTime(time: number) {
export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec The top level document node.
doc: {
- content: "block+"
+ content: 'block+',
},
paragraph: ParagraphNodeSpec,
audiotag: {
- group: "block",
+ group: 'block',
attrs: {
timeCode: { default: 0 },
- audioId: { default: "" },
- textId: { default: "" }
+ audioId: { default: '' },
+ textId: { default: '' },
},
toDOM(node) {
- return ['audiotag',
+ return [
+ 'audiotag',
{
class: node.attrs.textId,
// style: see FormattedTextBox.scss
- "data-timecode": node.attrs.timeCode,
- "data-audioid": node.attrs.audioId,
- "data-textid": node.attrs.textId,
+ 'data-timecode': node.attrs.timeCode,
+ 'data-audioid': node.attrs.audioId,
+ 'data-textid': node.attrs.textId,
},
- formatAudioTime(node.attrs.timeCode.toString())
+ formatAudioTime(node.attrs.timeCode.toString()),
];
},
parseDOM: [
{
- tag: "audiotag", getAttrs(dom: any) {
+ tag: 'audiotag',
+ getAttrs(dom: any) {
return {
- timeCode: dom.getAttribute("data-timecode"),
- audioId: dom.getAttribute("data-audioid"),
- textId: dom.getAttribute("data-textid")
+ timeCode: dom.getAttribute('data-timecode'),
+ audioId: dom.getAttribute('data-audioid'),
+ textId: dom.getAttribute('data-textid'),
};
- }
+ },
},
- ]
+ ],
},
footnote: {
- group: "inline",
- content: "inline*",
+ group: 'inline',
+ content: 'inline*',
inline: true,
attrs: {
- visibility: { default: false }
+ visibility: { default: false },
},
// This makes the view treat the node as a leaf, even though it
// technically has content
atom: true,
- toDOM: () => ["footnote", 0],
- parseDOM: [{ tag: "footnote" }]
+ toDOM: () => ['footnote', 0],
+ parseDOM: [{ tag: 'footnote' }],
},
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
blockquote: {
- content: "block*",
- group: "block",
+ content: 'block*',
+ group: 'block',
defining: true,
- parseDOM: [{ tag: "blockquote" }],
- toDOM() { return blockquoteDOM; }
+ parseDOM: [{ tag: 'blockquote' }],
+ toDOM() {
+ return blockquoteDOM;
+ },
},
-
// blockquote: {
// ...ParagraphNodeSpec,
// defining: true,
@@ -97,9 +103,11 @@ export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec A horizontal rule (`<hr>`).
horizontal_rule: {
- group: "block",
- parseDOM: [{ tag: "hr" }],
- toDOM() { return hrDOM; }
+ group: 'block',
+ parseDOM: [{ tag: 'hr' }],
+ toDOM() {
+ return hrDOM;
+ },
},
// :: NodeSpec A heading textblock, with a `level` attribute that
@@ -112,12 +120,14 @@ export const nodes: { [index: string]: NodeSpec } = {
level: { default: 1 },
},
defining: true,
- parseDOM: [{ tag: "h1", attrs: { level: 1 } },
- { tag: "h2", attrs: { level: 2 } },
- { tag: "h3", attrs: { level: 3 } },
- { tag: "h4", attrs: { level: 4 } },
- { tag: "h5", attrs: { level: 5 } },
- { tag: "h6", attrs: { level: 6 } }],
+ parseDOM: [
+ { tag: 'h1', attrs: { level: 1 } },
+ { tag: 'h2', attrs: { level: 2 } },
+ { tag: 'h3', attrs: { level: 3 } },
+ { tag: 'h4', attrs: { level: 4 } },
+ { tag: 'h5', attrs: { level: 5 } },
+ { tag: 'h6', attrs: { level: 6 } },
+ ],
toDOM(node) {
const dom = toParagraphDOM(node) as any;
const level = node.attrs.level || 1;
@@ -129,36 +139,38 @@ export const nodes: { [index: string]: NodeSpec } = {
const level = Number(dom.nodeName.substring(1)) || 1;
attrs.level = level;
return attrs;
- }
+ },
},
// :: NodeSpec A code listing. Disallows marks or non-text inline
// nodes by default. Represented as a `<pre>` element with a
// `<code>` element inside of it.
code_block: {
- content: "inline*",
- marks: "_",
- group: "block",
+ content: 'inline*',
+ marks: '_',
+ group: 'block',
code: true,
defining: true,
- parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
- toDOM() { return preDOM; }
+ parseDOM: [{ tag: 'pre', preserveWhitespace: 'full' }],
+ toDOM() {
+ return preDOM;
+ },
},
// :: NodeSpec The text node.
text: {
- group: "inline"
+ group: 'inline',
},
dashComment: {
attrs: {
- docid: { default: "" },
+ docid: { default: '' },
},
inline: true,
- group: "inline",
+ group: 'inline',
toDOM(node) {
const attrs = { style: `width: 40px` };
- return ["span", { ...node.attrs, ...attrs }, "←"];
+ return ['span', { ...node.attrs, ...attrs }, '←'];
},
},
@@ -169,10 +181,10 @@ export const nodes: { [index: string]: NodeSpec } = {
text: { default: undefined },
textslice: { default: undefined },
},
- group: "inline",
+ group: 'inline',
toDOM(node) {
const attrs = { style: `width: 40px` };
- return ["span", { ...node.attrs, ...attrs }];
+ return ['span', { ...node.attrs, ...attrs }];
},
},
@@ -187,27 +199,30 @@ export const nodes: { [index: string]: NodeSpec } = {
width: { default: 100 },
alt: { default: null },
title: { default: null },
- float: { default: "left" },
- location: { default: "add:right" },
- docid: { default: "" }
+ float: { default: 'left' },
+ location: { default: 'add:right' },
+ docid: { default: '' },
},
- group: "inline",
+ group: 'inline',
draggable: true,
- parseDOM: [{
- tag: "img[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt"),
- width: Math.min(100, Number(dom.getAttribute("width"))),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'img[src]',
+ getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute('src'),
+ title: dom.getAttribute('title'),
+ alt: dom.getAttribute('alt'),
+ width: Math.min(100, Number(dom.getAttribute('width'))),
+ };
+ },
+ },
+ ],
// TODO if we don't define toDom, dragging the image crashes. Why?
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}` };
- return ["img", { ...node.attrs, ...attrs }];
- }
+ return ['img', { ...node.attrs, ...attrs }];
+ },
},
dashDoc: {
@@ -216,82 +231,87 @@ export const nodes: { [index: string]: NodeSpec } = {
width: { default: 200 },
height: { default: 100 },
title: { default: null },
- float: { default: "right" },
+ float: { default: 'right' },
hidden: { default: false }, // whether dashComment node has toggle the dashDoc's display off
- fieldKey: { default: "" },
- docid: { default: "" },
- alias: { default: "" }
+ fieldKey: { default: '' },
+ docid: { default: '' },
+ alias: { default: '' },
},
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
dashField: {
inline: true,
attrs: {
- fieldKey: { default: "" },
- docid: { default: "" },
- hideKey: { default: false }
+ fieldKey: { default: '' },
+ docid: { default: '' },
+ hideKey: { default: false },
},
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
equation: {
inline: true,
attrs: {
- fieldKey: { default: "" },
+ fieldKey: { default: '' },
},
atom: true,
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
video: {
inline: true,
attrs: {
src: {},
- width: { default: "100px" },
+ width: { default: '100px' },
alt: { default: null },
- title: { default: null }
+ title: { default: null },
},
- group: "inline",
+ group: 'inline',
draggable: true,
- parseDOM: [{
- tag: "video[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt"),
- width: Math.min(100, Number(dom.getAttribute("width"))),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'video[src]',
+ getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute('src'),
+ title: dom.getAttribute('title'),
+ alt: dom.getAttribute('alt'),
+ width: Math.min(100, Number(dom.getAttribute('width'))),
+ };
+ },
+ },
+ ],
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}` };
- return ["video", { ...node.attrs, ...attrs }];
- }
+ return ['video', { ...node.attrs, ...attrs }];
+ },
},
// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
hard_break: {
inline: true,
- group: "inline",
+ group: 'inline',
selectable: false,
- parseDOM: [{ tag: "br" }],
- toDOM() { return brDOM; }
+ parseDOM: [{ tag: 'br' }],
+ toDOM() {
+ return brDOM;
+ },
},
ordered_list: {
@@ -300,85 +320,108 @@ export const nodes: { [index: string]: NodeSpec } = {
group: 'block',
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: "decimal" },// "decimal", "multi", "bullet"
- fontColor: { default: "inherit" },
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
+ fontColor: { default: 'inherit' },
fontSize: { default: undefined },
fontFamily: { default: undefined },
visibility: { default: true },
- indent: { default: undefined }
+ indent: { default: undefined },
},
parseDOM: [
{
- tag: "ul", getAttrs(dom: any) {
+ tag: 'ul',
+ getAttrs(dom: any) {
return {
- bulletStyle: dom.getAttribute("data-bulletStyle"),
- mapStyle: dom.getAttribute("data-mapStyle"),
+ bulletStyle: dom.getAttribute('data-bulletStyle'),
+ mapStyle: dom.getAttribute('data-mapStyle'),
fontColor: dom.style.color,
- fontSize: dom.style["font-size"],
- fontFamily: dom.style["font-family"],
- indent: dom.style["margin-left"]
+ fontSize: dom.style['font-size'],
+ fontFamily: dom.style['font-family'],
+ indent: dom.style['margin-left'],
};
- }
+ },
},
{
- style: 'list-style-type=disc', getAttrs(dom: any) {
- return { mapStyle: "bullet" };
- }
+ style: 'list-style-type=disc',
+ getAttrs(dom: any) {
+ return { mapStyle: 'bullet' };
+ },
},
{
- tag: "ol", getAttrs(dom: any) {
+ tag: 'ol',
+ getAttrs(dom: any) {
return {
- bulletStyle: dom.getAttribute("data-bulletStyle"),
- mapStyle: dom.getAttribute("data-mapStyle"),
+ bulletStyle: dom.getAttribute('data-bulletStyle'),
+ mapStyle: dom.getAttribute('data-mapStyle'),
fontColor: dom.style.color,
- fontSize: dom.style["font-size"],
- fontFamily: dom.style["font-family"],
- indent: dom.style["margin-left"]
+ fontSize: dom.style['font-size'],
+ fontFamily: dom.style['font-family'],
+ indent: dom.style['margin-left'],
};
- }
- }],
- toDOM(node: Node<any>) {
- const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
- const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : "";
- const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : "";
- const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : "";
- const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : "";
- if (node.attrs.mapStyle === "bullet") {
- return ['ul', {
- "data-mapStyle": node.attrs.mapStyle,
- "data-bulletStyle": node.attrs.bulletStyle,
- style: `${fsize} ${ffam} ${fcol} ${marg}`
- }, 0];
+ },
+ },
+ ],
+ toDOM(node: Node) {
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
+ const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : '';
+ const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : '';
+ const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : '';
+ const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
+ if (node.attrs.mapStyle === 'bullet') {
+ return [
+ 'ul',
+ {
+ 'data-mapStyle': node.attrs.mapStyle,
+ 'data-bulletStyle': node.attrs.bulletStyle,
+ style: `${fsize} ${ffam} ${fcol} ${marg}`,
+ },
+ 0,
+ ];
}
- return node.attrs.visibility ?
- ['ol', {
- class: `${map}-ol`,
- "data-mapStyle": node.attrs.mapStyle,
- "data-bulletStyle": node.attrs.bulletStyle,
- style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`
- }, 0] :
- ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
- }
+ return node.attrs.visibility
+ ? [
+ 'ol',
+ {
+ class: `${map}-ol`,
+ 'data-mapStyle': node.attrs.mapStyle,
+ 'data-bulletStyle': node.attrs.bulletStyle,
+ style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`,
+ },
+ 0,
+ ]
+ : ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+ },
},
list_item: {
...listItem,
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet"
- visibility: { default: true }
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
+ visibility: { default: true },
},
content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)',
- parseDOM: [{
- tag: "li", getAttrs(dom: any) {
- return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'li',
+ getAttrs(dom: any) {
+ return { mapStyle: dom.getAttribute('data-mapStyle'), bulletStyle: dom.getAttribute('data-bulletStyle') };
+ },
+ },
+ ],
toDOM(node: any) {
- const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
- return ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, node.attrs.visibility ? 0 :
- ["span", { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== "bullet" ? "inline-block" : "list-item"}; text-overflow: ellipsis; white-space: pre` },
- `${node.firstChild?.textContent}...`]];
- }
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
+ return [
+ 'li',
+ { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
+ node.attrs.visibility
+ ? 0
+ : [
+ 'span',
+ { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` },
+ `${node.firstChild?.textContent}...`,
+ ],
+ ];
+ },
},
-}; \ No newline at end of file
+};