diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextMenu.tsx | 281 |
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; + } } |