aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/RichTextMenu.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx281
1 files changed, 110 insertions, 171 deletions
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index cecf106a3..a612f3c65 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -3,30 +3,30 @@ import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { lift, wrapIn } from 'prosemirror-commands';
-import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
+import { Mark, MarkType } from 'prosemirror-model';
import { wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
-import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DocumentView } from '../DocumentView';
import { EquationBox } from '../EquationBox';
import { FieldViewProps } from '../FieldView';
import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
+
const { toggleMark } = require('prosemirror-commands');
@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
+ // eslint-disable-next-line no-use-before-define
static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
static get Instance() {
return RichTextMenu._instance?.menu;
@@ -115,8 +115,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
_disposer: IReactionDisposer | undefined;
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views.slice(),
- views => this.updateMenu(undefined, undefined, undefined, undefined)
+ () => DocumentView.Selected().slice(),
+ () => this.updateMenu(undefined, undefined, undefined, undefined)
);
}
componentWillUnmount() {
@@ -139,18 +139,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setActiveMarkButtons(this.getActiveMarksOnSelection());
const active = this.getActiveFontStylesOnSelection();
- const activeFamilies = active.activeFamilies;
- const activeSizes = active.activeSizes;
- const activeColors = active.activeColors;
- const activeHighlights = active.activeHighlights;
- const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
- const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
+ const { activeFamilies } = active;
+ const { activeSizes } = active;
+ const { activeColors } = active;
+ const { activeHighlights } = active;
+ const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc();
+ const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey);
+ const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));
this._activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
@@ -159,12 +160,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
- const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
- const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
- const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
- const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
- const fromRange = numberRange(state.selection.from).reverse();
- const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
+ const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
const hasMark = node.marks.some(m => m.type === mark.type);
@@ -172,25 +168,23 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
dispatch(updateBullets(markup, state.schema));
- } else {
- const state = this.view?.state;
- const tr = this.view?.state.tr;
- if (tr && state) {
- if (dontToggle) {
- tr.addMark(state.selection.from, state.selection.to, mark);
- dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
- } else {
- toggleMark(mark.type, mark.attrs)(state, dispatch);
- }
+ } else if (state) {
+ const { tr } = state;
+ if (dontToggle) {
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
+ } else {
+ toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
+ this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
}
};
// finds font sizes and families in selection
getActiveAlignment() {
if (this.view && this.TextView?._props.rootSelected?.()) {
- const path = (this.view.state.selection.$from as any).path;
+ const { path } = this.view.state.selection.$from as any;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
return path[i].attrs.align || 'left';
@@ -222,27 +216,28 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeColors = new Set<string>();
const activeHighlights = new Set<string>();
if (this.view && this.TextView?._props.rootSelected?.()) {
- const state = this.view.state;
+ const { state } = this.view;
const pos = this.view.state.selection.$from;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
marks.forEach(m => {
- m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family);
- m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color);
+ m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily);
+ m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
- m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
+ m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
});
- } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
- SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
+ } else if (DocumentView.Selected().some(dv => dv.ComponentView instanceof EquationBox)) {
+ DocumentView.Selected().forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
}
return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) };
}
@@ -254,26 +249,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return found;
}
- //finds all active marks on selection in given group
+ // finds all active marks on selection in given group
getActiveMarksOnSelection() {
if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
- const state = this.view.state;
- var marks: Mark[] = [...(state.storedMarks ?? [])];
+ const { state } = this.view;
+ let marks: Mark[] = [...(state.storedMarks ?? [])];
const pos = this.view.state.selection.$from;
if (state.storedMarks !== null) {
+ /* empty */
} else if (state.selection.empty) {
for (let i = 0; i <= pos.depth; i++) {
marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
}
} else {
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
});
}
const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
- return markGroup.filter(mark_type => {
- const mark = state.schema.mark(mark_type);
+ return markGroup.filter(markType => {
+ const mark = state.schema.mark(markType);
return mark.isInSet(marks);
});
}
@@ -291,7 +287,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this._superscriptActive = false;
activeMarks.forEach(mark => {
- // prettier-ignore
switch (mark.name) {
case 'noAutoLinkAnchor': this._noLinkActive = true; break;
case 'strong': this._boldActive = true; break;
@@ -300,7 +295,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
case 'strikethrough': this._strikethroughActive = true; break;
case 'subscript': this._subscriptActive = true; break;
case 'superscript': this._superscriptActive = true; break;
- }
+ default:
+ } // prettier-ignore
});
}
@@ -353,53 +349,25 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- setFontSize = (fontSize: string) => {
+ setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
if (this.view) {
- if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
- this.TextView.dataDoc[this.TextView.fieldKey + '_fontSize'] = fontSize;
- this.view.focus();
- } else {
- const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
- this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+ const { text, paragraph } = this.view.state.schema.nodes;
+ const selNode = this.view.state.selection.$anchor.node();
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
this.view.focus();
}
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
- } else Doc.UserDoc().fontSize = fontSize;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- };
-
- setFontFamily = (family: string) => {
- if (this.view) {
- const fmark = this.view.state.schema.marks.pFontFamily.create({ family });
+ const attrs: { [key: string]: string } = {};
+ attrs[fontField] = value;
+ const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
- } else Doc.UserDoc().fontFamily = family;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ } else {
+ Doc.UserDoc()[fontField] = value;
+ this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+ }
};
- setHighlight(color: string) {
- if (this.view) {
- const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color });
- this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
- this.view.focus();
- } else Doc.UserDoc()._fontHighlight = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- }
-
- setColor(color: string) {
- if (this.view) {
- const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
- this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
- this.view.focus();
- } else if (SelectionManager.Views.length) {
- SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
- } else Doc.UserDoc().fontColor = color;
- this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
- }
-
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
changeListType = (mapStyle: string) => {
@@ -407,7 +375,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const newMapStyle = active === mapStyle ? '' : mapStyle;
if (!this.view || newMapStyle === '') return;
- let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
+ const inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
if (inList) {
const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
@@ -428,7 +396,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
insertSummarizer(state: EditorState, dispatch: any) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
- const tr = state.tr;
+ const { tr } = state;
tr.addMark(state.selection.from, state.selection.to, mark);
const content = tr.selection.content();
const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
@@ -436,13 +404,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- vcenterToggle = (view: EditorView, dispatch: any) => {
+ vcenterToggle = () => {
this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
};
align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView?._props.rootSelected?.()) {
- var tr = view.state.tr;
- view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
+ let { tr } = view.state;
+ view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {
if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks);
return false;
@@ -455,56 +423,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- insetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
- outsetParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
- return false;
- }
- return true;
- });
- dispatch?.(tr);
- return true;
- }
-
- indentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- const heading = false;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
- if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
- return false;
- }
- return true;
- });
- !heading && dispatch?.(tr);
- return true;
- }
-
- hangingIndentParagraph(state: EditorState, dispatch: any) {
- var tr = state.tr;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ paragraphSetup(state: EditorState, dispatch: any, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
+ let { tr } = state;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
- const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
- const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
- tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+ const newValue = !value ?
+ (node.attrs[field] ? 0 : node.attrs[field] + 10) :
+ Math.max(0, value); // prettier-ignore
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, ...(field === 'inset' ? { inset: newValue } : { indent: newValue }) }, node.marks);
return false;
}
return true;
@@ -514,7 +440,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
insertBlockquote(state: EditorState, dispatch: any) {
- const path = (state.selection.$from as any).path;
+ const { path } = state.selection.$from as any;
if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
lift(state, dispatch);
} else {
@@ -547,13 +473,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- fillBrush(state: EditorState, dispatch: any) {
+ fillBrush() {
if (!this.view) return;
if (!Array.from(this.brushMarks.keys()).length) {
- const selected_marks = this.getMarksInSelection(this.view.state);
- if (selected_marks.size >= 0) {
- this.brushMarks = selected_marks;
+ const selectedMarks = this.getMarksInSelection(this.view.state);
+ if (selectedMarks.size >= 0) {
+ this.brushMarks = selectedMarks;
}
} else {
const { from, to, $from } = this.view.state.selection;
@@ -597,9 +523,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const button = (
<Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button">
- <FontAwesomeIcon icon="link" size="lg" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="antimodeMenu-button color-preview-button">
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
+ }
</Tooltip>
);
@@ -607,21 +536,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
<div className="dropdown link-menu">
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
- <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+ <button type="button" className="make-button" onPointerDown={() => this.makeLinkToURL(link)}>
Apply hyperlink
</button>
<div className="divider" />
- <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+ <button type="button" className="remove-button" onPointerDown={() => this.deleteLink()}>
Remove link
</button>
</div>
);
- return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+ // eslint-disable-next-line no-use-before-define
+ return <ButtonDropdown view={this.view} key="link button" button={button} dropdownContent={dropdownContent} openDropdownOnButton link />;
}
async getTextLinkTargetTitle() {
- if (!this.view) return;
+ if (!this.view) return undefined;
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === 'link');
@@ -633,15 +563,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
- const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc);
- const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc);
- const currentDoc = SelectionManager.Docs.lastElement();
- if (currentDoc && link_anchor_1 && link_anchor_2) {
- if (Doc.AreProtosEqual(currentDoc, link_anchor_1)) {
- return StrCast(link_anchor_2.title);
+ const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc);
+ const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc);
+ const currentDoc = DocumentView.Selected().lastElement().Document;
+ if (currentDoc && linkAnchor1 && linkAnchor2) {
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) {
+ return StrCast(linkAnchor2.title);
}
- if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) {
- return StrCast(link_anchor_1.title);
+ if (Doc.AreProtosEqual(currentDoc, linkAnchor2)) {
+ return StrCast(linkAnchor1.title);
}
}
}
@@ -653,11 +583,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return link.attrs.title;
}
}
+ return undefined;
}
// TODO: should check for valid URL
@undoBatch
- makeLinkToURL = (target: string, lcoation: string) => {
+ makeLinkToURL = (target: string) => {
((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
};
@@ -673,7 +604,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0)
.forEach((aref: any) => {
const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
- anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ anchorId && DocServer.GetRefField(anchorId).then(linkDoc => Doc.DeleteLink?.(linkDoc as Doc));
});
}
}
@@ -736,7 +667,11 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
render() {
return (
- <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
+ <div
+ className="button-dropdown-wrapper"
+ ref={node => {
+ this.ref = node;
+ }}>
{!this._props.pdf ? (
<div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
{this._props.button}
@@ -747,9 +682,12 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
) : (
<>
{this._props.button}
- <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
- <FontAwesomeIcon icon="caret-down" size="sm" />
- </button>
+ {
+ // eslint-disable-next-line jsx-a11y/control-has-associated-label
+ <button type="button" className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+ <FontAwesomeIcon icon="caret-down" size="sm" />
+ </button>
+ }
</>
)}
{this.showDropdown ? this._props.dropdownContent : null}
@@ -762,10 +700,11 @@ interface RichTextMenuPluginProps {
editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- render() {
- return null;
- }
+ // eslint-disable-next-line react/no-unused-class-component-methods
update(view: EditorView, lastState: EditorState | undefined) {
RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
}
+ render() {
+ return null;
+ }
}