aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/RichTextMenu.tsx
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/RichTextMenu.tsx
parentc01828308714874589d1f60c33ca59df4c656c0c (diff)
parenta958577d4c27b276aa37484e3f895e196138b17c (diff)
Merge branch 'master' into alyssa-starter
Diffstat (limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx103
1 files changed, 49 insertions, 54 deletions
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;