aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-09-02 09:26:37 -0400
committerbobzel <zzzman@gmail.com>2024-09-02 09:26:37 -0400
commitcda69e48361fce8d71a4dc66edd9dd976a27f52d (patch)
tree82b9a1a5967ae88a9534f89f7eaed3aeb289652f /src/client/views/nodes/formattedText
parentc01828308714874589d1f60c33ca59df4c656c0c (diff)
parenta958577d4c27b276aa37484e3f895e196138b17c (diff)
Merge branch 'master' into alyssa-starter
Diffstat (limited to 'src/client/views/nodes/formattedText')
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx38
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx29
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx34
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.tsx17
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx26
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx384
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx14
-rw-r--r--src/client/views/nodes/formattedText/ParagraphNodeSpec.ts36
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx103
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts59
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts40
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts91
12 files changed, 436 insertions, 435 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index 3ec49fa27..0304ddc86 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -5,18 +5,20 @@ import { IReactionDisposer, computed, reaction } from 'mobx';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
import { NumCast } from '../../../../fields/Types';
+import { Node } from 'prosemirror-model';
+import { EditorView } from 'prosemirror-view';
interface IDashDocCommentViewInternal {
docId: string;
- view: any;
- getPos: any;
+ view: EditorView;
+ getPos: () => number;
setHeight: (height: number) => void;
}
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
_reactionDisposer: IReactionDisposer | undefined;
- constructor(props: any) {
+ constructor(props: IDashDocCommentViewInternal) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
this.onPointerEnterCollapsed = this.onPointerEnterCollapsed.bind(this);
@@ -43,19 +45,19 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
return DocServer.GetRefField(this.props.docId);
}
- onPointerLeaveCollapsed = (e: any) => {
+ onPointerLeaveCollapsed = (e: React.PointerEvent) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
};
- onPointerEnterCollapsed = (e: any) => {
+ onPointerEnterCollapsed = (e: React.PointerEvent) => {
this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
};
- onPointerUpCollapsed = (e: any) => {
+ onPointerUpCollapsed = (e: React.PointerEvent) => {
const target = this.targetNode();
if (target) {
@@ -65,7 +67,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
setTimeout(() => {
expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
- this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
+ this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, (this.props.getPos() ?? 0) + (expand ? 2 : 1))));
} catch (err) {
/* empty */
}
@@ -74,7 +76,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
e.stopPropagation();
};
- onPointerDownCollapsed = (e: any) => {
+ onPointerDownCollapsed = (e: React.PointerEvent) => {
e.stopPropagation();
};
@@ -84,7 +86,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
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: Node; pos: number; hidden: boolean };
}
}
@@ -119,10 +121,10 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
// the comment can be toggled on/off with the '<-' text anchor.
export class DashDocCommentView {
dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
+ root: ReactDOM.Root;
+ node: Node;
- constructor(node: any, view: any, getPos: any) {
+ constructor(node: Node, view: EditorView, getPos: () => number | undefined) {
this.node = node;
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
@@ -130,22 +132,22 @@ export class DashDocCommentView {
this.dom.style.fontWeight = 'bold';
this.dom.style.position = 'relative';
this.dom.style.display = 'inline-block';
- this.dom.onkeypress = function (e: any) {
+ this.dom.onkeypress = function (e) {
e.stopPropagation();
};
- this.dom.onkeydown = function (e: any) {
+ this.dom.onkeydown = function (e) {
e.stopPropagation();
};
- this.dom.onkeyup = function (e: any) {
+ this.dom.onkeyup = function (e) {
e.stopPropagation();
};
- this.dom.onmousedown = function (e: any) {
+ this.dom.onmousedown = function (e) {
e.stopPropagation();
};
+ const getPosition = () => getPos() ?? 0;
this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
- (this as any).dom = this.dom;
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPosition} setHeight={this.setHeight} docId={node.attrs.docId} />);
}
setHeight = (hgt: number) => {
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 93371685d..e7f2cdba8 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { NodeSelection } from 'prosemirror-state';
@@ -16,6 +15,8 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocumentView } from '../DocumentView';
import { FocusViewOptions } from '../FocusViewOptions';
import { FormattedTextBox } from './FormattedTextBox';
+import { EditorView } from 'prosemirror-view';
+import { Node } from 'prosemirror-model';
const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
interface IDashDocViewInternal {
@@ -26,9 +27,9 @@ interface IDashDocViewInternal {
height: string;
hidden: boolean;
fieldKey: string;
- view: any;
- node: any;
- getPos: any;
+ view: EditorView;
+ node: Node;
+ getPos: () => number;
}
@observer
@@ -109,7 +110,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
};
outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
- onKeyDown = (e: any) => {
+ onKeyDown = (e: React.KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Tab' || e.key === 'Enter') {
e.preventDefault();
@@ -176,29 +177,31 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
export class DashDocView {
dom: HTMLSpanElement; // container for label and value
- root: any;
+ root: ReactDOM.Root;
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ constructor(node: Node, view: EditorView, getPos: () => number | undefined, tbox: FormattedTextBox) {
this.dom = document.createElement('span');
this.dom.style.position = 'relative';
this.dom.style.textIndent = '0';
this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
this.dom.style.height = node.attrs.height;
this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
- (this.dom.style as any).float = node.attrs.float;
- this.dom.onkeypress = function (e: any) {
+ this.dom.style.float = node.attrs.float;
+ this.dom.onkeypress = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onkeydown = function (e: any) {
+ this.dom.onkeydown = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onkeyup = function (e: any) {
+ this.dom.onkeyup = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onmousedown = function (e: any) {
+ this.dom.onmousedown = function (e: MouseEvent) {
e.stopPropagation();
};
+ const getPosition = () => getPos() ?? 0;
+
this.root = ReactDOM.createRoot(this.dom);
this.root.render(
<DashDocViewInternal
@@ -211,7 +214,7 @@ export class DashDocView {
tbox={tbox}
view={view}
node={node}
- getPos={getPos}
+ getPos={getPosition}
/>
);
}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 9903d0e8a..f0313fba4 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,6 +1,3 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
-/* eslint-disable jsx-a11y/control-has-associated-label */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
@@ -26,6 +23,8 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../OpenWhere';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
+import { Node } from 'prosemirror-model';
+import { EditorView } from 'prosemirror-view';
@observer
export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -34,7 +33,7 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
static toggleFieldHide: () => void = emptyFunction;
static toggleValueHide: () => void = emptyFunction;
- constructor(props: any) {
+ constructor(props: AntimodeMenuProps) {
super(props);
DashFieldViewMenu.Instance = this;
}
@@ -100,8 +99,8 @@ interface IDashFieldViewInternal {
height: number;
editable: boolean;
nodeSelected: () => boolean;
- node: any;
- getPos: any;
+ node: Node;
+ getPos: () => number;
unclickable: () => boolean;
}
@@ -274,7 +273,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
<select className="dashFieldView-select" tabIndex={-1} defaultValue={this._dashDoc && Field.toKeyValueString(this._dashDoc, this._fieldKey)} onChange={this.selectVal}>
<option value="-unset-">-unset-</option>
{this.values.map(val => (
- <option value={val.value}>{val.label}</option>
+ <option key={val.value} value={val.value}>
+ {val.label}
+ </option>
))}
</select>
)}
@@ -284,16 +285,17 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
- root: any;
- node: any;
+ root: ReactDOM.Root;
+ node: Node;
tbox: FormattedTextBox;
- getpos: any;
+ getpos: () => number | undefined;
@observable _nodeSelected = false;
NodeSelected = () => this._nodeSelected;
- unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some(m => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+ constructor(node: Node, view: EditorView, getPos: () => number | undefined, tbox: FormattedTextBox) {
makeObservable(this);
+ const getPosition = () => getPos() ?? 0;
this.node = node;
this.tbox = tbox;
this.getpos = getPos;
@@ -312,7 +314,7 @@ export class DashFieldView {
const editor = tbox.EditorView;
if (editor) {
const { state } = editor;
- for (let i = this.getpos() + 1; i < state.doc.content.size; i++) {
+ for (let i = getPosition() + 1; i < state.doc.content.size; i++) {
if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
return;
@@ -321,10 +323,10 @@ export class DashFieldView {
}
}
};
- this.dom.onkeyup = function (e: any) {
+ this.dom.onkeyup = function (e: KeyboardEvent) {
e.stopPropagation();
};
- this.dom.onmousedown = function (e: any) {
+ this.dom.onmousedown = function (e: MouseEvent) {
e.stopPropagation();
};
@@ -333,7 +335,7 @@ export class DashFieldView {
<DashFieldViewInternal
node={node}
unclickable={this.unclickable}
- getPos={getPos}
+ getPos={getPosition}
fieldKey={node.attrs.fieldKey}
docId={node.attrs.docId}
width={node.attrs.width}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index d9b1a2cf8..8bb4a0a26 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -3,15 +3,12 @@ import React, { Component, createRef } from 'react';
// Import JQuery, required for the functioning of the equation editor
import $ from 'jquery';
-
import './EquationEditor.scss';
-// @ts-ignore
-window.jQuery = $;
-
-// @ts-ignore
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+(window as any).jQuery = $;
require('mathquill/build/mathquill');
-
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).MathQuill = (window as any).MathQuill.getInterface(1);
type EquationEditorProps = {
@@ -36,17 +33,18 @@ type EquationEditorProps = {
* @extends {Component<EquationEditorProps>}
*/
class EquationEditor extends Component<EquationEditorProps> {
- element: any;
+ element: React.RefObject<HTMLSpanElement>;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
mathField: any;
ignoreEditEvents: number;
// Element needs to be in the class format and thus requires a constructor. The steps that are run
// in the constructor is to make sure that React can succesfully communicate with the equation
// editor.
- constructor(props: any) {
+ constructor(props: EquationEditorProps) {
super(props);
- this.element = createRef();
+ this.element = createRef<HTMLSpanElement>();
this.mathField = null;
// MathJax apparently fire 2 edit events on startup.
@@ -74,6 +72,7 @@ class EquationEditor extends Component<EquationEditorProps> {
autoOperatorNames,
};
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
this.mathField = (window as any).MathQuill.MathField(this.element.current, config);
this.mathField.latex(value || '');
}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 5167c8f2a..df1421a33 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,22 +1,23 @@
-/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
+import { Node } from 'prosemirror-model';
import { TextSelection } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
+import { DocData } from '../../../../fields/DocSymbols';
import { StrCast } from '../../../../fields/Types';
import './DashFieldView.scss';
import EquationEditor from './EquationEditor';
import { FormattedTextBox } from './FormattedTextBox';
-import { DocData } from '../../../../fields/DocSymbols';
interface IEquationViewInternal {
fieldKey: string;
tbox: FormattedTextBox;
width: number;
height: number;
- getPos: () => number;
+ getPos: () => number | undefined;
setEditor: (editor: EquationEditor | undefined) => void;
}
@@ -27,7 +28,7 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
_fieldKey: string;
_ref: React.RefObject<EquationEditor> = React.createRef();
- constructor(props: any) {
+ constructor(props: IEquationViewInternal) {
super(props);
this._fieldKey = props.fieldKey;
this._textBoxDoc = props.tbox.Document;
@@ -46,7 +47,7 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
className="equationView"
onKeyDown={e => {
if (e.key === 'Enter') {
- this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new TextSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos() + 1))));
+ this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new TextSelection(this.props.tbox.EditorView!.state.doc.resolve((this.props.getPos() ?? 0) + 1))));
this.props.tbox.EditorView!.focus();
e.preventDefault();
}
@@ -63,7 +64,7 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
<EquationEditor
ref={this._ref}
value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
- onChange={(str: any) => {
+ onChange={str => {
this._textBoxDoc[DocData][this._fieldKey] = str;
}}
autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
@@ -77,25 +78,27 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
export class EquationView {
dom: HTMLDivElement; // container for label and value
- root: any;
+ root: ReactDOM.Root;
tbox: FormattedTextBox;
- view: any;
- constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ view: EditorView;
+ _editor: EquationEditor | undefined;
+ getPos: () => number | undefined;
+ constructor(node: Node, view: EditorView, getPos: () => number | undefined, tbox: FormattedTextBox) {
this.tbox = tbox;
this.view = view;
+ this.getPos = getPos;
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
this.dom.style.position = 'relative';
this.dom.style.display = 'inline-block';
- this.dom.onmousedown = function (e: any) {
+ this.dom.onmousedown = (e: MouseEvent) => {
e.stopPropagation();
};
this.root = ReactDOM.createRoot(this.dom);
this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
}
- _editor: EquationEditor | undefined;
setEditor = (editor?: EquationEditor) => {
this._editor = editor;
};
@@ -106,6 +109,7 @@ export class EquationView {
this._editor?.mathField.focus();
}
selectNode() {
+ this.view.dispatch(this.view.state.tr.setSelection(new TextSelection(this.view.state.doc.resolve(this.getPos() ?? 0))));
this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
setTimeout(() => {
this._editor?.mathField.focus();
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index faef78469..73b20e6c2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,9 +1,8 @@
/* eslint-disable no-use-before-define */
-/* eslint-disable jsx-a11y/no-static-element-interactions */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { baseKeymap, selectAll } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
@@ -14,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
import { EditorView, NodeViewConstructor } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
@@ -27,7 +26,7 @@ import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
-import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
+import { gptAPICall, GPTCallType, gptImageLabel } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -65,8 +64,6 @@ import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
-import { URLField } from '../../../../fields/URLField';
-import { gptImageLabel } from '../../../apis/gpt/GPT';
// import * as applyDevTools from 'prosemirror-dev-tools';
export interface FormattedTextBoxProps extends FieldViewProps {
@@ -78,28 +75,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
- private static nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor };
+ public static MakeConfig(rules?: RichTextRules, props?: FormattedTextBoxProps) {
+ return {
+ schema,
+ plugins: [
+ inputRules(rules?.inpRules ?? { rules: [] }),
+ ...(props ? [FormattedTextBox.richTextMenuPlugin(props)] : []),
+ history(),
+ keymap(buildKeymap(schema, props ?? {})),
+ keymap(baseKeymap),
+ new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
+ new Plugin({ view: () => new FormattedTextBoxComment() }),
+ ],
+ };
+ }
/**
* Initialize the class with all the plugin node view components
* @param nodeViews prosemirror plugins that render a custom UI for specific node types
*/
- public static Init(nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor }) {
- FormattedTextBox.nodeViews = nodeViews;
- }
+ public static Init(nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor }) { FormattedTextBox._nodeViews = nodeViews; } // prettier-ignore
+
+ public static PasteOnLoad: ClipboardEvent | undefined;
+ public static DontSelectInitialText = false; // whether initial text should be selected or not
+ public static SelectOnLoadChar = '';
public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection
- static _globalHighlightsCache: string = '';
- static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
- static _highlightStyleSheet = addStyleSheet();
- static _bulletStyleSheet = addStyleSheet();
- static _userStyleSheet = addStyleSheet();
- static _hadSelection: boolean = false;
+
+ private static _nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor };
+ private static _globalHighlightsCache: string = '';
+ private static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
+ private static _highlightStyleSheet = addStyleSheet();
+ private static _bulletStyleSheet = addStyleSheet();
+ private static _userStyleSheet = addStyleSheet();
+
+ private _oldWheel: HTMLDivElement | null = null;
private _selectionHTML: string | undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
private _sidebarTagRef = React.createRef<React.Component>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: HTMLDivElement | null = null;
- private _editorView: Opt<EditorView>;
- public _applyingChange: string = '';
+ private _editorView: Opt<EditorView & { TextView?: FormattedTextBox | undefined }>;
private _inDrop = false;
private _finishingLink = false;
private _searchIndex = 0;
@@ -110,85 +124,37 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _recordingStart: number = 0;
private _ignoreScroll = false;
private _focusSpeed: Opt<number>;
- private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
private _break = true;
+
+ public _applyingChange: string = '';
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 noSidebar() {
- return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
- }
- @computed get layout_sidebarWidthPercent() {
- return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
- }
- @computed get sidebarColor() {
- return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4'));
- }
- @computed get layout_autoHeight() {
- return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight;
- }
- @computed get textHeight() {
- return NumCast(this.dataDoc[this.fieldKey + '_height']);
- }
- @computed get scrollHeight() {
- return NumCast(this.dataDoc[this.fieldKey + '_scrollHeight']);
- }
- @computed get sidebarHeight() {
- return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.SidebarKey + '_height']);
- }
- @computed get titleHeight() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
- }
- @computed get layout_autoHeightMargins() {
- return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
- }
- @computed get _recordingDictation() {
- return this.dataDoc?.mediaState === mediaState.Recording;
- }
+ @observable _showSidebar = false;
+
+ @computed get fontColor() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore
+ @computed get fontSize() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore
+ @computed get fontFamily() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) as string; } // prettier-ignore
+ @computed get fontWeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight) as string; } // prettier-ignore
+
set _recordingDictation(value) {
!this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
}
- @computed get config() {
- this._keymap = buildKeymap(schema, this._props);
- this._rules = new RichTextRules(this.Document, this);
- return {
- schema,
- plugins: [
- inputRules(this._rules.inpRules),
- this.richTextMenuPlugin(),
- history(),
- keymap(this._keymap),
- keymap(baseKeymap),
- new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
- new Plugin({
- view(/* editorView */) {
- return new FormattedTextBoxComment();
- },
- }),
- ],
- };
- }
-
- // State for GPT
- @observable
- private gptRes: string = '';
-
- // public makeAIFlashcards: () => void = unimplementedFunction;
- public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
-
- public static PasteOnLoad: ClipboardEvent | undefined;
- public static DontSelectInitialText = false; // whether initial text should be selected or not
- public static SelectOnLoadChar = '';
+ @computed get _recordingDictation() { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
+ @computed get SidebarShown() { return !!(this._showSidebar || this.layoutDoc._layout_showSidebar); } // prettier-ignore
+ @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
+ @computed get noSidebar() { return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar; } // prettier-ignore
+ @computed get layout_sidebarWidthPercent() { return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
+ @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4')); } // prettier-ignore
+ @computed get layout_autoHeight() { return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight; } // prettier-ignore
+ @computed get textHeight() { return NumCast(this.dataDoc[this.fieldKey + '_height']); } // prettier-ignore
+ @computed get scrollHeight() { return NumCast(this.dataDoc[this.fieldKey + '_scrollHeight']); } // prettier-ignore
+ @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.sidebarKey + '_height']); } // prettier-ignore
+ @computed get titleHeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) as number || 0; } // prettier-ignore
+ @computed get layout_autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins); } // prettier-ignore
+ @computed get config() { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this._props); } // prettier-ignore
+ @computed get sidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore
constructor(props: FormattedTextBoxProps) {
super(props);
@@ -196,6 +162,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._recordingStart = Date.now();
}
+ public get EditorView() { return this._editorView; } // prettier-ignore
+
+ // public makeAIFlashcards: () => void = unimplementedFunction;
+ public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
+
// 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.
// but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
@@ -207,9 +178,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (state && a1 && a2 && this._editorView) {
this.removeDocument(a1);
this.removeDocument(a2);
- let allFoundLinkAnchors: any[] = [];
- state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any /* , pos: number, parent: any */) => {
- const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
+ let allFoundLinkAnchors: { href: string; title: string; anchorId: string }[] = [];
+ state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: Node /* , pos: number, parent: any */) => {
+ const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: { href: string; title: string; anchorId: string }) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
return true;
});
@@ -257,7 +228,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const target = this._sidebarRef.current?.anchorMenuClick(anchor);
if (target) {
anchor.followLinkAudio = true;
- let stopFunc: any;
+ let stopFunc: () => void = emptyFunction;
const targetData = target[DocData];
targetData.mediaState = mediaState.Recording;
DictationManager.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
@@ -275,10 +246,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
};
- AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight');
- return undefined;
- }, 'highlght text');
+ AnchorMenu.Instance.Highlight = undoable((color: string) => this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'), 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
@@ -294,7 +262,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.DocumentView?.()!, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+ const docView = this.DocumentView?.();
+ docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
});
AnchorMenu.Instance.setSelectedText(window.getSelection()?.toString() ?? '');
@@ -347,7 +316,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
const accumTags = [] as string[];
- state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any /* , pos: number, parent: any */) => {
+ state.tr.doc.nodesBetween(0, state.doc.content.size, (node: Node /* , pos: number, parent: any */) => {
if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
accumTags.push(node.attrs.fieldKey);
}
@@ -413,8 +382,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
if (this._editorView && linkTime) {
const { state } = this._editorView;
- const { path } = state.selection.$from as any;
- if (linkAnchor && path[path.length - 3].type !== state.schema.nodes.code_block) {
+ const node = state.selection.$from.node();
+ if (linkAnchor && node.type !== state.schema.nodes.code_block) {
const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000;
this._break = false;
const { from } = state.selection;
@@ -479,7 +448,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
* function of a freeform view that is driven by the text box's text. The include directive will copy the code of the published
* document into the code being evaluated.
*/
- hyperlinkTerm = (trIn: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ hyperlinkTerm = (trIn: Transaction, target: Doc, newAutoLinks: Set<Doc>) => {
let tr = trIn;
const editorView = this._editorView;
if (editorView && !Doc.AreProtosEqual(target, this.Document)) {
@@ -496,7 +465,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
) {
const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
tr = tr.addMark(sel.from, sel.to, splitter);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number /* , parent: any */) => {
+ tr.doc.nodesBetween(sel.from, sel.to, (node: Node, 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 ??
@@ -649,15 +618,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (node.isBlock) {
// tslint:disable-next-line: prefer-for-of
- for (let i = 0; i < (context.content as any).content.length; i++) {
- const result = this.getNodeEndpoints((context.content as any).content[i], node);
+ for (let i = 0; i < context.content.childCount; i++) {
+ const result = this.getNodeEndpoints(context.content.child(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),
};
}
- offset += (context.content as any).content[i].nodeSize;
+ offset += context.content.child(i).nodeSize;
}
}
return null;
@@ -743,18 +712,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
};
- @observable _showSidebar = false;
- @computed get SidebarShown() {
- return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
- }
-
@action
toggleSidebar = (preview: boolean = false) => {
const defaultSidebar = 250;
const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
if (preview) this._showSidebar = true;
else {
- this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
+ this.layoutDoc[this.sidebarKey + '_freeform_scale_max'] = 1;
this.layoutDoc._layout_showSidebar =
(this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` : '0%') !== '0%';
}
@@ -822,10 +786,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._props.dontSelect?.()) return;
const cm = ContextMenu.Instance;
- let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
- while (target && !target.dataset?.targethrefs) target = target.parentElement;
+ let target: Element | HTMLElement | null = e.target as HTMLElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
+ while (target && (!(target instanceof HTMLElement) || !target.dataset?.targethrefs)) target = target.parentElement;
const editor = this._editorView;
- if (editor && target && !(e.nativeEvent as any).dash) {
+ if (editor && target && !(e.nativeEvent instanceof simMouseEvent ? e.nativeEvent.dash : false)) {
const hrefs = (target.dataset?.targethrefs as string)
?.trim()
.split(' ')
@@ -834,10 +798,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
.lastElement()
.replace(Doc.localServerPath(), '')
.split('?')[0];
- const deleteMarkups = undoBatch(() => {
+ const deleteMarkups = undoable(() => {
const { selection } = editor.state;
editor.dispatch(editor.state.tr.removeMark(selection.from, selection.to, editor.state.schema.marks.linkAnchor));
- });
+ }, 'delete markups');
e.persist();
anchorDoc &&
DocServer.GetRefField(anchorDoc).then(
@@ -861,21 +825,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const changeItems: ContextMenuProps[] = [];
changeItems.push({
description: 'plain',
- event: undoBatch(() => {
+ event: undoable(() => {
Doc.setNativeView(this.Document);
this.layoutDoc.layout_autoHeightMargins = undefined;
- }),
+ }, 'set plain view'),
icon: 'eye',
});
changeItems.push({
description: 'metadata',
- event: undoBatch(() => {
+ event: undoable(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
this.Document.layout_fieldKey = 'layout_meta';
setTimeout(() => {
this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
}, 50);
- }),
+ }, 'set metadata view'),
icon: 'eye',
});
const noteTypesDoc = Cast(Doc.UserDoc().template_notes, Doc, null);
@@ -883,11 +847,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const icon: IconProp = StrCast(note.icon) as IconProp;
changeItems.push({
description: StrCast(note.title),
- event: undoBatch(() => {
- this.layoutDoc.layout_autoHeightMargins = undefined;
- Doc.setNativeView(this.Document);
- DocUtils.makeCustomViewClicked(this.Document, Docs.Create.TreeDocument, StrCast(note.title), note);
- }),
+ event: undoable(
+ () => {
+ this.layoutDoc.layout_autoHeightMargins = undefined;
+ Doc.setNativeView(this.Document);
+ DocUtils.makeCustomViewClicked(this.Document, Docs.Create.TreeDocument, StrCast(note.title), note);
+ },
+ `set ${StrCast(note.title)} view}`
+ ),
icon: icon,
});
});
@@ -909,12 +876,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
})
);
const appearance = cm.findByDescription('Appearance...');
- const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
+ const appearanceItems = appearance?.subitems ?? [];
// appearanceItems.push({
// description: 'Find image tags',
// event: this.findImageTags,
// icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
// });
+
appearanceItems.push({
description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle',
event: () => {
@@ -968,7 +936,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
const options = cm.findByDescription('Options...');
- const optionItems = options && 'subitems' in options ? options.subitems : [];
+ const optionItems = options?.subitems ?? [];
optionItems.push({
description: `Toggle auto update from template`,
event: () => {
@@ -997,7 +965,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
const help = cm.findByDescription('Help...');
- const helpItems = help && 'subitems' in help ? help.subitems : [];
+ const helpItems = help?.subitems ?? [];
helpItems.push({ description: `show markdown options`, event: () => RTFMarkup.Instance.setOpen(true), icon: <BsMarkdownFill /> });
!help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
};
@@ -1067,7 +1035,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
askGPT = action(async () => {
try {
- GPTPopup.Instance.setSidebarId(this.SidebarKey);
+ GPTPopup.Instance.setSidebarId(this.sidebarKey);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
if (!res) {
@@ -1170,7 +1138,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor);
- tr.doc.nodesBetween(selection.from, selection.to, (node: any, pos: number /* , parent: any */) => {
+ tr.doc.nodesBetween(selection.from, selection.to, (node: Node, pos: number /* , parent: any */) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
const allAnchors = [{ href, title, anchorId: anchor[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
@@ -1194,7 +1162,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
getView = async (doc: Doc, options: FocusViewOptions) => {
- if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+ if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
if (!this.SidebarShown) {
this.toggleSidebar(false);
options.didMove = true;
@@ -1247,17 +1215,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._didScroll = false; // assume we don't need to scroll. if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
if (this._editorView && textAnchorId) {
- const editor = this._editorView;
- const ret = findAnchorFrag(editor.state.doc.content, editor);
+ const { state } = this._editorView;
+ const ret = findAnchorFrag(state.doc.content, this._editorView);
- const content = (ret.frag as any)?.content;
- if ((ret.frag.size || (content?.length && content[0].type === this._editorView.state.schema.nodes.dashDoc) || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
+ const firstChild = ret.frag.childCount ? ret.frag.child(0) : undefined;
+ if (ret.start >= 0 && (ret.frag.size || (firstChild && [state.schema.nodes.dashDoc, state.schema.nodes.audioTag].includes(firstChild.type)))) {
!options.instant && (this._focusSpeed = focusSpeed);
- let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
+ let selection = TextSelection.near(state.doc.resolve(ret.start)); // default to near the start
if (ret.frag.firstChild) {
- selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+ selection = TextSelection.between(state.doc.resolve(ret.start), state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
}
- editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+ this._editorView.dispatch(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(() => {
@@ -1333,9 +1301,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const protoData = DocCast(this.dataDoc.proto)?.[this.fieldKey];
const dataData = this.dataDoc[this.fieldKey];
const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey];
- const dataTime = dataData ? DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
- const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
- const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+ const dataTime = dataData ? (DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+ const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+ const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
@@ -1494,41 +1462,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
let el = elIn;
while (el && el !== document.body) {
if (getComputedStyle(el).display === 'none') return false;
- el = el.parentNode as any;
+ el = el.parentElement;
}
return true;
}
- richTextMenuPlugin() {
- const self = this;
+ static richTextMenuPlugin(props: FormattedTextBoxProps) {
return new Plugin({
- view(newView) {
- runInAction(() => {
- self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
- });
- return new RichTextMenuPlugin({ editorProps: this._props });
- },
+ view: action((newView: EditorView) => {
+ props?.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+ return new RichTextMenuPlugin({ editorProps: props });
+ }),
});
}
_didScroll = false;
_scrollStopper: undefined | (() => void);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
setupEditor(config: any, fieldKey: string) {
const curText = Cast(this.dataDoc[this.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.fieldKey]);
const rtfField = Cast((!curText && this.layoutDoc[this.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
if (this.ProseRef) {
- const self = this;
this._editorView?.destroy();
this._editorView = new EditorView(this.ProseRef, {
state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
handleScrollToSelection: editorView => {
const docPos = editorView.coordsAtPos(editorView.state.selection.to);
- const viewRect = self._ref.current!.getBoundingClientRect();
- const scrollRef = self._scrollRef;
+ const viewRect = this._ref.current!.getBoundingClientRect();
+ const scrollRef = this._scrollRef;
const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
- const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
+ const scrollPos = scrollRef.scrollTop + shift * this.ScreenToLocalBoxXf().Scale;
if (this._focusSpeed !== undefined) {
setTimeout(() => {
scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
@@ -1541,7 +1506,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
},
dispatchTransaction: this.dispatchTransaction,
- nodeViews: FormattedTextBox.nodeViews(this),
+ nodeViews: FormattedTextBox._nodeViews(this),
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
@@ -1559,7 +1524,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
}
}
- (this._editorView as any).TextView = this;
+ this._editorView.TextView = this;
}
const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, Doc.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
@@ -1584,7 +1549,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
} else if (!FormattedTextBox.DontSelectInitialText) {
const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
selectAll(this._editorView.state, (tx: Transaction) => {
- this._editorView?.dispatch(tx.deleteSelection().addStoredMark(mark));
+ this._editorView?.dispatch(tx.addStoredMark(mark));
});
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
} else {
@@ -1637,18 +1602,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
onPointerDown = (e: React.PointerEvent): void => {
- if ((e.nativeEvent as any).handledByInnerReactInstance) {
- return; // e.stopPropagation();
- }
- (e.nativeEvent as any).handledByInnerReactInstance = true;
-
if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitWidth doc is being viewed in different embedContainer (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
- if ((e.target as any).tagName === 'AUDIOTAG') {
+ const target = e.target as HTMLElement;
+ if (target.tagName === 'AUDIOTAG') {
e.preventDefault();
e.stopPropagation();
- const timecode = Number((e.target as any)?.dataset?.timecode);
- DocServer.GetRefField((e.target as any)?.dataset?.audioid || 0).then(anchor => {
+ const timecode = Number(target.dataset?.timecode);
+ DocServer.GetRefField(target.dataset?.audioid || '').then(anchor => {
if (anchor instanceof Doc) {
// const timecode = NumCast(anchor.timecodeToShow, 0);
const audiodoc = anchor.annotationOn as Doc;
@@ -1672,7 +1633,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
e.stopPropagation(); // if the text box's content is active, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
- (this.ProseRef?.children?.[0] as any).focus();
+ (this.ProseRef?.children?.[0] as HTMLElement).focus();
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
@@ -1680,7 +1641,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
};
onSelectEnd = () => {
- GPTPopup.Instance.setSidebarId(this.SidebarKey);
+ GPTPopup.Instance.setSidebarId(this.sidebarKey);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
document.removeEventListener('pointerup', this.onSelectEnd);
};
@@ -1688,10 +1649,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const state = this.EditorView?.state;
if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- let clickTarget = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
- for (let { target } = e as any; target && !target.dataset?.targethrefs; target = target.parentElement);
- while (clickTarget && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
- FormattedTextBoxComment.update(this, this.EditorView!, undefined, clickTarget?.dataset?.targethrefs, clickTarget?.dataset.linkdoc, clickTarget?.dataset.nopreview === 'true');
+ let clickTarget: HTMLElement | Element | null = e.target as HTMLElement; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let target: HTMLElement | Element | null = clickTarget as HTMLElement; target instanceof HTMLElement && !target.dataset?.targethrefs; target = target.parentElement);
+ while (clickTarget instanceof HTMLElement && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
+ const dataset = clickTarget instanceof HTMLElement ? clickTarget?.dataset : undefined;
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true');
}
};
@action
@@ -1715,27 +1677,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setFocus = (ipos?: number) => {
const pos = ipos ?? (this._editorView?.state.selection.$from.pos || 1);
setTimeout(() => this._editorView?.dispatch(this._editorView.state.tr.setSelection(TextSelection.near(this._editorView.state.doc.resolve(pos)))), 100);
- setTimeout(() => (this.ProseRef?.children?.[0] as any).focus(), 200);
+ setTimeout(() => (this.ProseRef?.children?.[0] as HTMLElement).focus(), 200);
};
@action
onFocused = (e: React.FocusEvent): void => {
// applyDevTools.applyDevTools(this._editorView);
- this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
e.stopPropagation();
};
onClick = (e: React.MouseEvent): void => {
if (!this._props.isContentActive()) return;
- if ((e.nativeEvent as any).handledByInnerReactInstance) {
- e.stopPropagation();
- return;
- }
- if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) {
+ const editorView = this._editorView;
+ const editorRoot = editorView?.root instanceof Document ? editorView.root : undefined;
+ if (editorView && (!this._forceUncollapse || editorRoot?.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) {
- this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
+ const pcords = editorView.posAtCoords({ left: e.clientX, top: e.clientY });
+ const node = pcords && 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 === editorView.state.schema.nodes.dashComment) {
+ this._editorView!.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, pcords.pos + 2)));
e.preventDefault();
}
if (!node && this.ProseRef) {
@@ -1743,19 +1702,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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
- this._editorView?.focus();
- this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
+ editorView.focus();
+ editorView.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, editorView.state.doc.content.size)));
}
- } 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)));
+ } else if (node && [editorView.state.schema.nodes.ordered_list, editorView.state.schema.nodes.listItem].includes(node.type) && node !== (editorView.state.selection as NodeSelection)?.node && pcords) {
+ editorView.dispatch(editorView.state.tr.setSelection(NodeSelection.create(editorView.state.doc, pcords.pos)));
}
}
- if (this._props.rootSelected?.()) {
+ if (editorView && this._props.rootSelected?.()) {
// if text box is selected, then it consumes all click events
- (e.nativeEvent as any).handledByInnerReactInstance = true;
- this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, e.shiftKey);
+ e.stopPropagation();
+ this.hitBulletTargets(e.clientX, e.clientY, !editorView.state.selection.empty || this._forceUncollapse, false, e.shiftKey);
}
- this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
+ this._forceUncollapse = !editorRoot?.getSelection()?.isCollapsed;
};
// 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, selectOrderedList: boolean = false) {
@@ -1771,9 +1730,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
let $olistPos = this._editorView?.state.doc.resolve(olistPos);
let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
if (olistNode?.type === this._editorView?.state.schema.nodes.list_item) {
- if ($olistPos && ($olistPos as any).path.length > 3) {
+ if ($olistPos && $olistPos.depth) {
olistNode = $olistPos.parent;
- $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
+ $olistPos = this._editorView?.state.doc.resolve($olistPos.start($olistPos.depth - 1));
}
}
const maxSize = this._editorView?.state.doc.content.size ?? 0;
@@ -1804,7 +1763,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@action
- onBlur = (e: any) => {
+ onBlur = (e: React.FocusEvent) => {
if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
const stordMarks = this._editorView?.state.storedMarks?.slice();
@@ -1822,7 +1781,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
- FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
// this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
@@ -1869,7 +1827,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
switch (e.key) {
case 'Escape':
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
- (document.activeElement as any).blur?.();
+ (document.activeElement as HTMLElement).blur?.();
DocumentView.DeselectAll();
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
return;
@@ -1936,14 +1894,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
- sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
+ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.sidebarKey) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
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);
+ sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
+ sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.sidebarKey);
setSidebarHeight = (height: number) => {
- this.dataDoc[this.SidebarKey + '_height'] = height;
+ this.dataDoc[this.sidebarKey + '_height'] = height;
};
sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
@@ -1973,9 +1931,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@computed get sidebarHandle() {
TraceMobx();
- const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
+ 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.layoutDoc, this._props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
+ const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : (this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.WidgetColor + (annotated ? ':annotated' : '')) as string);
return !annotated && (!this._props.isContentActive() || SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? null : (
<div
@@ -1992,6 +1950,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const ComponentTag: any = tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
@@ -2026,7 +1985,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
- viewField={this.SidebarKey}
+ viewField={this.sidebarKey}
isAnnotationOverlay={false}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
@@ -2041,14 +2000,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
fitContentsToBox={this.fitContentsToBox}
noSidebar
treeViewHideTitle
- fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
+ fieldKey={this.layoutDoc[this.sidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
/>
</div>
);
};
return (
<div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {renderComponent(StrCast(this.layoutDoc[this.SidebarKey + '_type_collection']))}
+ {renderComponent(StrCast(this.layoutDoc[this.sidebarKey + '_type_collection']))}
</div>
);
}
@@ -2119,19 +2078,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
}
};
- _oldWheel: any;
- @computed get fontColor() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor);
- }
- @computed get fontSize() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
- }
- @computed get fontFamily() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
- }
- @computed get fontWeight() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight);
- }
+
render() {
TraceMobx();
const scale = this._props.NativeDimScaling?.() || 1;
@@ -2225,6 +2172,7 @@ Docs.Prototypes.TemplateMap.set(DocumentType.RTF, {
_layout_nativeDimEditable: true,
_layout_reflowVertical: true,
_layout_reflowHorizontal: true,
+ _layout_noSidebar: true,
defaultDoubleClick: 'ignore',
systemIcon: 'BsFileEarmarkTextFill',
},
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 01c46edeb..6c0eac103 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,4 +1,4 @@
-import { Mark, ResolvedPos } from 'prosemirror-model';
+import { Mark, Node, ResolvedPos } from 'prosemirror-model';
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { ClientUtils } from '../../../../ClientUtils';
@@ -61,8 +61,8 @@ export class FormattedTextBoxComment {
tooltip.style.display = 'none';
tooltip.appendChild(tooltipText);
tooltip.onpointerdown = (e: PointerEvent) => {
- const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
- false && startUserMarkRegion !== undefined && textBox?.adoptAnnotation(startUserMarkRegion, endUserMarkRegion, userMark);
+ // const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
+ // startUserMarkRegion !== undefined && textBox?.adoptAnnotation(startUserMarkRegion, endUserMarkRegion, userMark);
e.stopPropagation();
e.preventDefault();
};
@@ -73,7 +73,7 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.textBox = undefined;
FormattedTextBoxComment.tooltip.style.display = 'none';
}
- public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) {
+ public static saveMarkRegion(textBox: FormattedTextBox, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
FormattedTextBoxComment.startUserMarkRegion = start;
FormattedTextBoxComment.endUserMarkRegion = end;
@@ -87,7 +87,7 @@ export class FormattedTextBoxComment {
const start = view.coordsAtPos(state.selection.from - nbef);
const end = view.coordsAtPos(state.selection.from - nbef);
// The box in which the tooltip is positioned, to use as base
- const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
+ const box = document.getElementsByClassName('mainView-container')[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';
@@ -118,8 +118,8 @@ export class FormattedTextBoxComment {
const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
const noselection = state.selection.$from === state.selection.$to;
- let child: any = null;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any /* , pos: number, parent: any */) => {
+ let child: Node | undefined;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node: Node /* , pos: number, parent: any */) => {
!child && node.marks.length && (child = node);
});
const mark = child && findOtherUserMark(child.marks);
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index 8799964b3..d41938698 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -1,18 +1,18 @@
-import { Node, DOMOutputSpec } from 'prosemirror-model';
+import { Node, DOMOutputSpec, AttributeSpec, TagParseRule } from 'prosemirror-model';
import clamp from '../../../util/clamp';
import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
// import type { NodeSpec } from './Types';
type NodeSpec = {
- attrs?: { [key: string]: any };
+ attrs?: { [key: string]: AttributeSpec };
content?: string;
draggable?: boolean;
group?: string;
inline?: boolean;
name?: string;
- parseDOM?: Array<any>;
- toDOM?: (node: any) => DOMOutputSpec;
+ parseDOM?: Array<TagParseRule>;
+ toDOM?: (node: Node) => DOMOutputSpec;
};
// This assumes that every 36pt maps to one indent level.
@@ -30,7 +30,7 @@ function convertMarginLeftToIndentValue(marginLeft: string): number {
return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL);
}
-function getAttrs(dom: HTMLElement): Object {
+export function getAttrs(dom: HTMLElement): object {
const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;
let align = dom.getAttribute('align') || textAlign || '';
@@ -50,9 +50,31 @@ function getAttrs(dom: HTMLElement): Object {
return { align, indent, lineSpacing, paddingTop, paddingBottom, id };
}
-function toDOM(node: Node): DOMOutputSpec {
+export function getHeadingAttrs(dom: HTMLElement): { align?: string; indent?: number; lineSpacing?: string; paddingTop?: string; paddingBottom?: string; id: string; level?: number } {
+ const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;
+
+ let align = dom.getAttribute('align') || textAlign || '';
+ align = ALIGN_PATTERN.test(align) ? align : '';
+
+ let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10);
+
+ if (!indent && marginLeft) {
+ indent = convertMarginLeftToIndentValue(marginLeft);
+ }
+
+ indent = indent || MIN_INDENT_LEVEL;
+
+ const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : undefined;
+
+ const level = Number(dom.nodeName.substring(1)) || 1;
+
+ const id = dom.getAttribute('id') || '';
+ return { align, indent, lineSpacing, paddingTop, paddingBottom, id, level };
+}
+
+export function toDOM(node: Node): DOMOutputSpec {
const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs;
- const attrs: { [key: string]: any } | null = {};
+ const attrs: { [key: string]: unknown } | null = {};
let style = '';
if (align && align !== 'left') {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index a612f3c65..738f6d699 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,11 +1,11 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { lift, wrapIn } from 'prosemirror-commands';
+import { lift, toggleMark, wrapIn } from 'prosemirror-commands';
import { Mark, MarkType } from 'prosemirror-model';
import { wrapInList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
+import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
@@ -17,13 +17,11 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocumentView } from '../DocumentView';
import { EquationBox } from '../EquationBox';
import { FieldViewProps } from '../FieldView';
-import { FormattedTextBox } from './FormattedTextBox';
+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> {
// eslint-disable-next-line no-use-before-define
@@ -35,8 +33,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
private _linkToRef = React.createRef<HTMLInputElement>();
layoutDoc: Doc | undefined;
- @observable public view?: EditorView = undefined;
- public editorProps: FieldViewProps | undefined;
+ @observable public view?: EditorView & { TextView?: FormattedTextBox } = undefined;
+ public editorProps: FieldViewProps | AntimodeMenuProps | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
@@ -114,17 +112,17 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
_disposer: IReactionDisposer | undefined;
componentDidMount() {
- this._disposer = reaction(
- () => DocumentView.Selected().slice(),
- () => this.updateMenu(undefined, undefined, undefined, undefined)
- );
+ // this._disposer = reaction(
+ // () => DocumentView.Selected().slice(),
+ // () => this.updateMenu(undefined, undefined, undefined, undefined)
+ // );
}
componentWillUnmount() {
this._disposer?.();
}
@action
- public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any, layoutDoc: Doc | undefined) {
+ public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, layoutDoc: Doc | undefined) {
if (this._linkToRef.current?.getBoundingClientRect().width) {
return;
}
@@ -158,7 +156,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
}
- setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
+ setMark = (mark: Mark, state: EditorState, dispatch: (tr: Transaction) => void, dontToggle: boolean = false) => {
if (mark) {
const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
@@ -177,25 +175,26 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
- this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
+ // this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
}
};
// finds font sizes and families in selection
- getActiveAlignment() {
+ getActiveAlignment = () => {
if (this.view && this.TextView?._props.rootSelected?.()) {
- const { path } = this.view.state.selection.$from as any;
- for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
- if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
- return path[i].attrs.align || 'left';
+ const from = this.view.state.selection.$from;
+ for (let i = from.depth; i >= 0; i--) {
+ const node = from.node(i);
+ if (node.type === this.view.state.schema.nodes.paragraph || node.type === this.view.state.schema.nodes.heading) {
+ return node.attrs.align || 'left';
}
}
}
return 'left';
- }
+ };
// finds font sizes and families in selection
- getActiveListStyle() {
+ getActiveListStyle = () => {
const state = this.view?.state;
if (state) {
const pos = state.selection.$anchor;
@@ -207,7 +206,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
return '';
- }
+ };
// finds font sizes and families in selection
getActiveFontStylesOnSelection() {
@@ -321,7 +320,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.view) {
const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor);
this.setMark(mark, this.view.state, this.view.dispatch, false);
- this.TextView.autoLink();
+ this.TextView?.autoLink();
this.view.focus();
}
};
@@ -350,7 +349,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
};
setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
- if (this.view) {
+ if (this.TextView && this.view) {
const { text, paragraph } = this.view.state.schema.nodes;
const selNode = this.view.state.selection.$anchor.node();
if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
@@ -360,11 +359,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const attrs: { [key: string]: string } = {};
attrs[fontField] = value;
const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
- this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+ this.setMark(fmark, this.view.state, (tx: Transaction) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
} else {
Doc.UserDoc()[fontField] = value;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ // this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
}
};
@@ -383,17 +382,17 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
marks && tx2.setStoredMarks([...marks]);
this.view.dispatch(tx2);
} else
- !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+ !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema, newMapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
this.view!.dispatch(tx3);
});
this.view.focus();
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ // this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
- insertSummarizer(state: EditorState, dispatch: any) {
+ insertSummarizer(state: EditorState, dispatch: (tr: Transaction) => void) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
const { tr } = state;
@@ -407,7 +406,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
vcenterToggle = () => {
this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
};
- align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
+ align = (view: EditorView, dispatch: (tr: Transaction) => void, alignment: 'left' | 'right' | 'center') => {
if (this.TextView?._props.rootSelected?.()) {
let { tr } = view.state;
view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {
@@ -423,7 +422,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- paragraphSetup(state: EditorState, dispatch: any, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
+ paragraphSetup(state: EditorState, dispatch: (tr: Transaction) => void, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
let { tr } = state;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
@@ -439,9 +438,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertBlockquote(state: EditorState, dispatch: any) {
- const { path } = state.selection.$from as any;
- if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
+ insertBlockquote(state: EditorState, dispatch: (tr: Transaction) => void) {
+ const node = state.selection.$from.depth ? state.selection.$from.node(state.selection.$from.depth - 1) : undefined;
+ if (node?.type === schema.nodes.blockquote) {
lift(state, dispatch);
} else {
wrapIn(schema.nodes.blockquote)(state, dispatch);
@@ -449,7 +448,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertHorizontalRule(state: EditorState, dispatch: any) {
+ insertHorizontalRule(state: EditorState, dispatch: (tr: Transaction) => void) {
dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView());
return true;
}
@@ -497,7 +496,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
get TextView() {
- return (this.view as any)?.TextView as FormattedTextBox;
+ return this.view?.TextView;
}
get TextViewFieldKey() {
return this.TextView?._props.fieldKey;
@@ -512,19 +511,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
createLinkButton() {
- const self = this;
-
- function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
- self.TextView?.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change');
- }
+ const onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.TextView?.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => this.setCurrentLink(e.target.value), 'link change');
+ };
const link = this.currentLink ? this.currentLink : '';
const button = (
<Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
{
- // eslint-disable-next-line jsx-a11y/control-has-associated-label
<button type="button" className="antimodeMenu-button color-preview-button">
<FontAwesomeIcon icon="link" size="lg" />
</button>
@@ -589,7 +585,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
+ this.TextView?.makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
};
@undoBatch
@@ -597,12 +593,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.view) {
const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
if (linkAnchor) {
- const allAnchors = linkAnchor.attrs.allAnchors.slice();
- this.TextView.RemoveAnchorFromSelection(allAnchors);
+ const allAnchors = (linkAnchor.attrs.allAnchors as { href: string; title: string; linkId: string; targetId: string }[]).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) => {
+ .filter(aref => aref?.href.indexOf(Doc.localServerPath()) === 0)
+ .forEach(aref => {
const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
anchorId && DocServer.GetRefField(anchorId).then(linkDoc => Doc.DeleteLink?.(linkDoc as Doc));
});
@@ -629,7 +625,7 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
@observable private showDropdown: boolean = false;
private ref: HTMLDivElement | null = null;
- constructor(props: any) {
+ constructor(props: ButtonDropdownProps) {
super(props);
makeObservable(this);
}
@@ -683,7 +679,6 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
<>
{this._props.button}
{
- // eslint-disable-next-line jsx-a11y/control-has-associated-label
<button type="button" className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</button>
@@ -697,12 +692,12 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
}
interface RichTextMenuPluginProps {
- editorProps: any;
+ editorProps: FormattedTextBoxProps;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
// eslint-disable-next-line react/no-unused-class-component-methods
- update(view: EditorView, lastState: EditorState | undefined) {
- RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
+ update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) {
+ RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.layoutDoc);
}
render() {
return null;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index bf11dfe62..e0d6c7c05 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,4 +1,5 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
+import { NodeType } from 'prosemirror-model';
import { NodeSelection, TextSelection } from 'prosemirror-state';
import { ClientUtils } from '../../../../ClientUtils';
import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
@@ -6,7 +7,7 @@ import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { NumCast, StrCast } from '../../../../fields/Types';
-import { Utils } from '../../../../Utils';
+import { emptyFunction, Utils } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { DocUtils } from '../../../documents/DocUtils';
@@ -35,13 +36,7 @@ export class RichTextRules {
wrappingInputRule(/%>$/, schema.nodes.blockquote),
// 1. create numerical ordered list
- wrappingInputRule(
- /^1\.\s$/,
- schema.nodes.ordered_list,
- () => ({ 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
- ),
+ wrappingInputRule(/^1\.\s$/, schema.nodes.ordered_list, () => ({ mapStyle: 'decimal', bulletStyle: 1 }), emptyFunction, ((type: unknown) => ({ type, attrs: { mapStyle: 'decimal', bulletStyle: 1 } })) as unknown as null),
// A. create alphabetical ordered list
wrappingInputRule(
@@ -49,9 +44,8 @@ export class RichTextRules {
schema.nodes.ordered_list,
// match => {
() => ({ mapStyle: 'multi', bulletStyle: 1 }),
- // return ({ order: +match[1] })
- (match: any, node: any) => node.childCount + node.attrs.order === +match[1],
- ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
+ emptyFunction,
+ ((type: NodeType) => ({ type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as unknown as null
),
// * + - create bullet list
@@ -60,8 +54,8 @@ export class RichTextRules {
schema.nodes.ordered_list,
// match => {
() => ({ 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
+ emptyFunction,
+ ((type: NodeType) => ({ type: type, attrs: { mapStyle: 'bullet' } })) as unknown as null
),
// ``` create code block
@@ -93,7 +87,7 @@ export class RichTextRules {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
textDoc.inlineTextCount = numInlines + 1;
- const node = (state.doc.resolve(start) as any).nodeAfter;
+ const node = state.doc.resolve(start).nodeAfter;
const newNode = schema.nodes.dashComment.create({ docId: doc[Id], reflow: false });
const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: doc[Id], float: 'right' });
const sm = state.storedMarks || undefined;
@@ -137,7 +131,7 @@ export class RichTextRules {
textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
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 node = state.doc.resolve(start).nodeAfter;
const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id], reflow: true });
const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' });
const sm = state.storedMarks || undefined;
@@ -154,8 +148,8 @@ export class RichTextRules {
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(/(%d|d)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
- const pos = state.doc.resolve(start) as any;
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const pos = state.doc.resolve(start);
+ for (let depth = pos.depth; 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 });
@@ -169,8 +163,8 @@ export class RichTextRules {
// set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(/(%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 pos = state.doc.resolve(start);
+ for (let depth = pos.depth; 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 });
@@ -184,12 +178,12 @@ export class RichTextRules {
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(/(%q|q)$/, (state, match, start, end) => {
if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
- const pos = state.doc.resolve(start) as any;
+ const pos = state.doc.resolve(start);
if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
const { node } = state.selection;
return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
}
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ for (let depth = pos.depth; 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 });
@@ -202,9 +196,9 @@ export class RichTextRules {
// center justify text
new InputRule(/%\^/, (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
+ const resolved = state.doc.resolve(start);
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);
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.start() - 1, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
}
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
@@ -214,9 +208,9 @@ export class RichTextRules {
// left justify text
new InputRule(/%\[/, (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
+ const resolved = state.doc.resolve(start);
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);
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.start() - 1, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
}
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
@@ -226,9 +220,9 @@ export class RichTextRules {
// right justify text
new InputRule(/%\]/, (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
+ const resolved = state.doc.resolve(start);
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);
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.start() - 1, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
}
const node = resolved.nodeAfter;
const sm = state.storedMarks || undefined;
@@ -402,7 +396,7 @@ export class RichTextRules {
}),
// create an inline view of a tag stored under the '#' field
- new InputRule(/#([a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => {
+ new InputRule(/#(@?[a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
// this.Document[DocData]['#' + tag] = '#' + tag;
@@ -410,6 +404,7 @@ export class RichTextRules {
if (!tags.includes(tag)) {
tags.push(tag);
this.Document[DocData].tags = new List<string>(tags);
+ this.Document[DocData].showTags = true;
}
const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag });
return state.tr
@@ -426,9 +421,9 @@ export class RichTextRules {
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;
+ const node = state.doc.resolve(start).nodeAfter;
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+ if (node?.marks.findIndex(m => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
return node
? state.tr
.removeMark(start, end, schema.marks.user_mark)
@@ -438,7 +433,7 @@ export class RichTextRules {
}),
new InputRule(/%\(/, (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
+ const node = state.doc.resolve(start).nodeAfter;
const sm = state.storedMarks?.slice() || [];
const mark = state.schema.marks.summarizeInclusive.create();
@@ -447,7 +442,7 @@ export class RichTextRules {
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;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]);
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...(node?.marks ?? []), ...sm]);
}),
new InputRule(/%\)/, (state, match, start, end) => state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create())),
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 6e1f325cf..ba8e4faed 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -34,14 +34,14 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'a[href]',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
title: dom.getAttribute('title'),
};
},
},
],
- toDOM(node: any) {
+ toDOM: node => {
const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /* 'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
@@ -53,7 +53,7 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'div',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
noAutoLink: dom.getAttribute('data-noAutoLink'),
};
@@ -80,7 +80,7 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'a[href]',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
title: dom.getAttribute('title'),
noPreview: dom.getAttribute('noPreview'),
@@ -88,7 +88,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
},
],
- toDOM(node: any) {
+ toDOM: node => {
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
@@ -117,7 +117,7 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'span',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' };
},
},
@@ -131,7 +131,7 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'span',
- getAttrs(dom: any) {
+ getAttrs: dom => {
const cstyle = getComputedStyle(dom);
if (cstyle.font) {
if (cstyle.font.indexOf('Times New Roman') !== -1) return { fontFamily: 'Times New Roman' };
@@ -154,7 +154,7 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'span',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return { color: dom.getAttribute('color') };
},
},
@@ -170,12 +170,12 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'span',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return { fontHighlight: dom.getAttribute('background-color') };
},
},
],
- toDOM(node: any) {
+ toDOM: node => {
return node.attrs.fontHighlight ? ['span', { style: 'background-color:' + node.attrs.fontHighlight }] : ['span', { style: 'background-color: transparent' }];
},
},
@@ -224,7 +224,7 @@ export const marks: { [index: string]: MarkSpec } = {
attrs: {
bulletType: { default: 'decimal' },
},
- toDOM(node: any) {
+ toDOM: node => {
return [
'span',
{
@@ -238,11 +238,11 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'span',
- getAttrs: (p: any) => {
+ getAttrs: p => {
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 (p.parentElement?.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement?.outerHTML.indexOf('text-decoration-style: solid') !== -1) {
return null;
}
}
@@ -266,11 +266,11 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'span',
- getAttrs: (p: any) => {
+ getAttrs: p => {
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 (p.parentElement?.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement?.outerHTML.indexOf('text-decoration-style: dotted') !== -1) {
return null;
}
}
@@ -292,10 +292,10 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [
{
tag: 'span',
- getAttrs: (p: any) => {
+ getAttrs: p => {
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;
}
}
@@ -317,7 +317,7 @@ export const marks: { [index: string]: MarkSpec } = {
selected: { default: false },
},
parseDOM: [{ style: 'background: yellow' }],
- toDOM(node: any) {
+ toDOM: node => {
return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }];
},
},
@@ -330,7 +330,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
excludes: 'user_mark',
group: 'inline',
- toDOM(node: any) {
+ toDOM: node => {
const uid = node.attrs.userid.replace(/\./g, '').replace(/@/g, '');
const min = Math.round(node.attrs.modified / 60);
const hr = Math.round(min / 60);
@@ -348,7 +348,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
group: 'inline',
inclusive: false,
- toDOM(node: any) {
+ toDOM: node => {
const uid = node.attrs.userid.replace('.', '').replace('@', '');
return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0];
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 5bf942218..02ded3103 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,6 +1,6 @@
import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
import { listItem, orderedList } from 'prosemirror-schema-list';
-import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
+import { ParagraphNodeSpec, toParagraphDOM, getHeadingAttrs } from './ParagraphNodeSpec';
import { DocServer } from '../../../DocServer';
import { Doc, Field, FieldType } from '../../../../fields/Doc';
import { schema } from './schema_rts';
@@ -53,7 +53,7 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [
{
tag: 'audiotag',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
timeCode: dom.getAttribute('data-timecode'),
audioId: dom.getAttribute('data-audioid'),
@@ -123,24 +123,57 @@ export const nodes: { [index: string]: NodeSpec } = {
level: { default: 1 },
},
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 } },
+ {
+ tag: 'h1',
+ attrs: { level: 1 },
+ getAttrs(dom) {
+ return getHeadingAttrs(dom);
+ },
+ },
+ {
+ tag: 'h2',
+ attrs: { level: 2 },
+ getAttrs(dom) {
+ return getHeadingAttrs(dom);
+ },
+ },
+ {
+ tag: 'h3',
+ attrs: { level: 3 },
+ getAttrs(dom) {
+ return getHeadingAttrs(dom);
+ },
+ },
+ {
+ tag: 'h4',
+ attrs: { level: 4 },
+ getAttrs(dom) {
+ return getHeadingAttrs(dom);
+ },
+ },
+ {
+ tag: 'h5',
+ attrs: { level: 5 },
+ getAttrs(dom) {
+ return getHeadingAttrs(dom);
+ },
+ },
+ {
+ tag: 'h6',
+ attrs: { level: 6 },
+ getAttrs(dom) {
+ return getHeadingAttrs(dom);
+ },
+ },
],
toDOM(node) {
- const dom = toParagraphDOM(node) as any;
- dom[0] = `h${node.attrs.level || 1}`;
+ const dom = toParagraphDOM(node);
+ if (dom instanceof Array) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (dom as any)[0] = `h${node.attrs.level || 1}`; // [0] is readonly so cast away to any
+ }
return dom;
},
- getAttrs(dom: any) {
- const attrs = getParagraphNodeAttrs(dom) as any;
- const level = Number(dom.nodeName.substring(1)) || 1;
- attrs.level = level;
- return attrs;
- },
},
// :: NodeSpec A code listing. Disallows marks or non-text inline
@@ -221,7 +254,7 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [
{
tag: 'img[src]',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
src: dom.getAttribute('src'),
title: dom.getAttribute('title'),
@@ -300,7 +333,7 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [
{
tag: 'video[src]',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
src: dom.getAttribute('src'),
title: dom.getAttribute('title'),
@@ -341,33 +374,31 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [
{
tag: 'ul',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
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.fontSize,
+ fontFamily: dom.style.fontFamily,
+ indent: dom.style.marginLeft,
};
},
},
{
style: 'list-style-type=disc',
- getAttrs() {
- return { mapStyle: 'bullet' };
- },
+ getAttrs: () => ({ mapStyle: 'bullet' }),
},
{
tag: 'ol',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return {
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.fontSize,
+ fontFamily: dom.style.fontFamily,
+ indent: dom.style.marginLeft,
};
},
},
@@ -416,7 +447,7 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [
{
tag: 'li',
- getAttrs(dom: any) {
+ getAttrs: dom => {
return { mapStyle: dom.getAttribute('data-mapStyle'), bulletStyle: dom.getAttribute('data-bulletStyle') };
},
},