diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextMenu.tsx | 361 |
1 files changed, 206 insertions, 155 deletions
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 98343a261..22ca76b2e 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,36 +1,35 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { lift, wrapIn } from "prosemirror-commands"; -import { Mark, MarkType, Node as ProsNode, ResolvedPos } from "prosemirror-model"; -import { wrapInList } from "prosemirror-schema-list"; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../../fields/Doc"; -import { Cast, StrCast } from "../../../../fields/Types"; -import { DocServer } from "../../../DocServer"; -import { LinkManager } from "../../../util/LinkManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; -import { FieldViewProps } from "../FieldView"; -import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox"; -import { updateBullets } from "./ProsemirrorExampleTransfer"; -import "./RichTextMenu.scss"; -import { schema } from "./schema_rts"; -const { toggleMark } = require("prosemirror-commands"); - +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { lift, wrapIn } from 'prosemirror-commands'; +import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model'; +import { wrapInList } from 'prosemirror-schema-list'; +import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view'; +import { Doc } from '../../../../fields/Doc'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { DocServer } from '../../../DocServer'; +import { LinkManager } from '../../../util/LinkManager'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { FieldViewProps } from '../FieldView'; +import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox'; +import { updateBullets } from './ProsemirrorExampleTransfer'; +import './RichTextMenu.scss'; +import { schema } from './schema_rts'; +const { toggleMark } = require('prosemirror-commands'); @observer -export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { +export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { @observable static Instance: RichTextMenu; public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef<HTMLInputElement>(); @observable public view?: EditorView; - public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined; public _brushMap: Map<string, Set<Mark>> = new Map(); @@ -43,21 +42,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { @observable private _subscriptActive: boolean = false; @observable private _superscriptActive: boolean = false; - @observable private _activeFontSize: string = "13px"; - @observable private _activeFontFamily: string = ""; - @observable private activeListType: string = ""; - @observable private _activeAlignment: string = "left"; + @observable private _activeFontSize: string = '13px'; + @observable private _activeFontFamily: string = ''; + @observable private activeListType: string = ''; + @observable private _activeAlignment: string = 'left'; @observable private brushMarks: Set<Mark> = new Set(); @observable private showBrushDropdown: boolean = false; - @observable private _activeFontColor: string = "black"; + @observable private _activeFontColor: string = 'black'; @observable private showColorDropdown: boolean = false; - @observable private activeHighlightColor: string = "transparent"; + @observable private activeHighlightColor: string = 'transparent'; @observable private showHighlightDropdown: boolean = false; - @observable private currentLink: string | undefined = ""; + @observable private currentLink: string | undefined = ''; @observable private showLinkDropdown: boolean = false; _reaction: IReactionDisposer | undefined; @@ -72,24 +71,44 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } componentDidMount() { - this._reaction = reaction(() => SelectionManager.Views(), - () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true)); + this._reaction = reaction( + () => SelectionManager.Views(), + () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true) + ); } componentWillUnmount() { this._reaction?.(); } - @computed get noAutoLink() { return this._noLinkActive; } - @computed get bold() { return this._boldActive; } - @computed get underline() { return this._underlineActive; } - @computed get italics() { return this._italicsActive; } - @computed get strikeThrough() { return this._strikethroughActive; } - @computed get fontColor() { return this._activeFontColor; } - @computed get fontFamily() { return this._activeFontFamily; } - @computed get fontSize() { return this._activeFontSize; } - @computed get textAlign() { return this._activeAlignment; } + @computed get noAutoLink() { + return this._noLinkActive; + } + @computed get bold() { + return this._boldActive; + } + @computed get underline() { + return this._underlineActive; + } + @computed get italics() { + return this._italicsActive; + } + @computed get strikeThrough() { + return this._strikethroughActive; + } + @computed get fontColor() { + return this._activeFontColor; + } + @computed get fontFamily() { + return this._activeFontFamily; + } + @computed get fontSize() { + return this._activeFontSize; + } + @computed get textAlign() { + return this._activeAlignment; + } - public delayHide = () => this._delayHide = true; + public delayHide = () => (this._delayHide = true); @action public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) { @@ -118,16 +137,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; - this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, "10px")) : activeSizes[0]; - this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "..."; - this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "..."; + this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; + this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...'; + this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle)); } - setMark = (mark: Mark, state: EditorState<any>, dispatch: any, dontToggle: boolean = false) => { + setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => { if (mark) { const node = (state.selection as NodeSelection).node; if (node?.type === schema.nodes.ordered_list) { @@ -140,7 +159,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } else if (dontToggle) { toggleMark(mark.type, mark.attrs)(state, (tx: any) => { const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { // hack -- should have just set the mark in the first place + if (!tx.doc.rangeHasMark(from, to, mark.type)) { + // hack -- should have just set the mark in the first place toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); } else dispatch(tx); }); @@ -148,7 +168,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { toggleMark(mark.type, mark.attrs)(state, dispatch); } } - } + }; // finds font sizes and families in selection getActiveAlignment() { @@ -156,11 +176,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { - return path[i].attrs.align || "left"; + return path[i].attrs.align || 'left'; } } } - return "left"; + return 'left'; } // finds font sizes and families in selection @@ -176,7 +196,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle; } } - return ""; + return ''; } // finds font sizes and families in selection @@ -190,7 +210,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { if (this.TextView.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; - const marks: Mark<any>[] = [...(state.storedMarks ?? [])]; + const marks: Mark[] = [...(state.storedMarks ?? [])]; if (state.selection.empty) { const ref_node = this.reference_node(pos); marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); @@ -209,10 +229,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return { activeFamilies, activeSizes, activeColors, activeHighlights }; } - getMarksInSelection(state: EditorState<any>) { + getMarksInSelection(state: EditorState) { const found = new Set<Mark>(); const { from, to } = state.selection as TextSelection; - state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m))); + state.doc.nodesBetween(from, to, node => node.marks.forEach(m => found.add(m))); return found; } @@ -234,14 +254,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } return false; }); - } - else { + } else { const pos = this.view.state.selection.$from; const ref_node: ProsNode | null = this.reference_node(pos); if (ref_node !== null && ref_node !== this.view.state.doc) { if (ref_node.isText) { - } - else { + } else { return []; } activeMarks = markGroup.filter(mark_type => { @@ -275,13 +293,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { activeMarks.forEach(mark => { switch (mark.name) { - case "noAutoLinkAnchor": this._noLinkActive = true; break; - case "strong": this._boldActive = true; break; - case "em": this._italicsActive = true; break; - case "underline": this._underlineActive = true; break; - case "strikethrough": this._strikethroughActive = true; break; - case "subscript": this._subscriptActive = true; break; - case "superscript": this._superscriptActive = true; break; + case 'noAutoLinkAnchor': + this._noLinkActive = true; + break; + case 'strong': + this._boldActive = true; + break; + case 'em': + this._italicsActive = true; + break; + case 'underline': + this._underlineActive = true; + break; + case 'strikethrough': + this._strikethroughActive = true; + break; + case 'subscript': + this._subscriptActive = true; + break; + case 'superscript': + this._superscriptActive = true; + break; } }); } @@ -293,14 +325,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.TextView.autoLink(); this.view.focus(); } - } + }; toggleBold = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } + }; toggleUnderline = () => { if (this.view) { @@ -308,7 +340,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } + }; toggleItalics = () => { if (this.view) { @@ -316,13 +348,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } - + }; setFontSize = (fontSize: string) => { if (this.view) { - if (this.view.state.selection.from === 1 && this.view.state.selection.empty && - (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { + if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { this.TextView.dataDoc.fontSize = fontSize; this.view.focus(); this.updateMenu(this.view, undefined, this.props); @@ -333,7 +363,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.updateMenu(this.view, undefined, this.props); } } - } + }; setFontFamily = (family: string) => { if (this.view) { @@ -342,7 +372,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.view.focus(); this.updateMenu(this.view, undefined, this.props); } - } + }; setHighlight(color: String, view: EditorView, dispatch: any) { const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color }); @@ -362,8 +392,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // TODO: remove doesn't work // remove all node type and apply the passed-in one to the selected text - changeListType = (nodeType: Node | undefined) => { - if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return; + changeListType = (mapStyle: string) => { + const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle(); + const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }); + if (!this.view || nodeType?.attrs.mapStyle === '') return; const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list; let inList: any = undefined; @@ -377,17 +409,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); - if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); + if ( + inList || + !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); - this.view!.dispatch(tx2); - })) { + this.view!.dispatch(tx2); + }) + ) { const tx2 = this.view.state.tr; if (nodeType && (inList || nextIsOL)) { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, - inList ? fromList + inList.nodeSize : this.view.state.selection.to); + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); this.view.dispatch(tx3); @@ -395,9 +429,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } this.view.focus(); this.updateMenu(this.view, undefined, this.props); - } + }; - insertSummarizer(state: EditorState<any>, dispatch: any) { + insertSummarizer(state: EditorState, dispatch: any) { if (state.selection.empty) return false; const mark = state.schema.marks.summarize.create(); const tr = state.tr; @@ -408,7 +442,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return true; } - align = (view: EditorView, dispatch: any, alignment: "left" | "right" | "center") => { + align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { if (this.TextView.props.isSelected(true)) { var tr = view.state.tr; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { @@ -422,9 +456,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { view.focus(); dispatch?.(tr); } - } + }; - insetParagraph(state: EditorState<any>, dispatch: any) { + insetParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -437,7 +471,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { dispatch?.(tr); return true; } - outsetParagraph(state: EditorState<any>, dispatch: any) { + outsetParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -451,7 +485,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return true; } - indentParagraph(state: EditorState<any>, dispatch: any) { + indentParagraph(state: EditorState, dispatch: any) { var tr = state.tr; const heading = false; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { @@ -467,7 +501,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return true; } - hangingIndentParagraph(state: EditorState<any>, dispatch: any) { + hangingIndentParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -482,7 +516,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return true; } - insertBlockquote(state: EditorState<any>, dispatch: any) { + insertBlockquote(state: EditorState, dispatch: any) { const path = (state.selection.$from as any).path; if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) { lift(state, dispatch); @@ -492,20 +526,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return true; } - insertHorizontalRule(state: EditorState<any>, dispatch: any) { + insertHorizontalRule(state: EditorState, dispatch: any) { dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView()); return true; } - @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } + @action toggleBrushDropdown() { + this.showBrushDropdown = !this.showBrushDropdown; + } // todo: add brushes to brushMap to save with a style name onBrushNameKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); - this._brushNameRef.current!.style.background = "lightGray"; + this._brushNameRef.current!.style.background = 'lightGray'; } - } + }; _brushNameRef = React.createRef<HTMLInputElement>(); @action @@ -514,7 +550,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } @action - fillBrush(state: EditorState<any>, dispatch: any) { + fillBrush(state: EditorState, dispatch: any) { if (!this.view) return; if (!Array.from(this.brushMarks.keys()).length) { @@ -522,68 +558,81 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { if (selected_marks.size >= 0) { this.brushMarks = selected_marks; } - } - else { + } else { const { from, to, $from } = this.view.state.selection; if (!this.view.state.selection.empty && $from && $from.nodeAfter) { if (to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { - this.setMark(mark, this.view!.state, this.view!.dispatch); - }); + Array.from(this.brushMarks) + .filter(m => m.type !== schema.marks.user_mark) + .forEach((mark: Mark) => { + this.setMark(mark, this.view!.state, this.view!.dispatch); + }); } } } } - get TextView() { return (this.view as any)?.TextView as FormattedTextBox; } - get TextViewFieldKey() { return this.TextView?.props.fieldKey; } - - - - @action setActiveHighlight(color: string) { this.activeHighlightColor = color; } + get TextView() { + return (this.view as any)?.TextView as FormattedTextBox; + } + get TextViewFieldKey() { + return this.TextView?.props.fieldKey; + } + @action setActiveHighlight(color: string) { + this.activeHighlightColor = color; + } - @action setCurrentLink(link: string) { this.currentLink = link; } + @action setCurrentLink(link: string) { + this.currentLink = link; + } createLinkButton() { const self = this; function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) { self.TextView?.endUndoTypingBatch(); - UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change"); + UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change'); } - const link = this.currentLink ? this.currentLink : ""; + const link = this.currentLink ? this.currentLink : ''; - const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom"> - <button className="antimodeMenu-button color-preview-button"> - <FontAwesomeIcon icon="link" size="lg" /> - </button> - </Tooltip>; + const button = ( + <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom"> + <button className="antimodeMenu-button color-preview-button"> + <FontAwesomeIcon icon="link" size="lg" /> + </button> + </Tooltip> + ); - const dropdownContent = + const dropdownContent = ( <div className="dropdown link-menu"> <p>Linked to:</p> <input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} /> - <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button> + <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}> + Apply hyperlink + </button> <div className="divider" /> - <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button> - </div>; + <button className="remove-button" onPointerDown={e => this.deleteLink()}> + Remove link + </button> + </div> + ); - return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />; + return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />; } async getTextLinkTargetTitle() { if (!this.view) return; const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); + const link = node && node.marks.find(m => m.type.name === 'link'); if (link) { const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined; if (href) { if (href.indexOf(Doc.localServerPath()) === 0) { - const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0]; + const linkclicked = href.replace(Doc.localServerPath(), '').split('?')[0]; if (linkclicked) { const linkDoc = await DocServer.GetRefField(linkclicked); if (linkDoc instanceof Doc) { @@ -612,8 +661,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // TODO: should check for valid URL @undoBatch makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target); - } + ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target); + }; @undoBatch @action @@ -624,13 +673,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const allAnchors = linkAnchor.attrs.allAnchors.slice(); this.TextView.RemoveAnchorFromSelection(allAnchors); // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected. - allAnchors.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0).forEach((aref: any) => { - const anchorId = aref.href.replace(Doc.localServerPath(), "").split("?")[0]; - anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); - }); + allAnchors + .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0) + .forEach((aref: any) => { + const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0]; + anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); + }); } } - } + }; linkExtend($start: ResolvedPos, href: string) { const mark = this.view!.state.schema.marks.linkAnchor; @@ -651,7 +702,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return { from: startPos, to: endPos }; } - reference_node(pos: ResolvedPos<any>): ProsNode | null { + reference_node(pos: ResolvedPos): ProsNode | null { if (!this.view) return null; let ref_node: ProsNode = this.view.state.doc; @@ -671,7 +722,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { ref_node = node; skip = true; } - }); } } @@ -755,21 +805,19 @@ interface ButtonDropdownProps { openDropdownOnButton?: boolean; link?: boolean; pdf?: boolean; - } @observer export class ButtonDropdown extends React.Component<ButtonDropdownProps> { - @observable private showDropdown: boolean = false; private ref: HTMLDivElement | null = null; componentDidMount() { - document.addEventListener("pointerdown", this.onBlur); + document.addEventListener('pointerdown', this.onBlur); } componentWillUnmount() { - document.removeEventListener("pointerdown", this.onBlur); + document.removeEventListener('pointerdown', this.onBlur); } @action @@ -785,7 +833,7 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> { e.preventDefault(); e.stopPropagation(); this.toggleDropdown(); - } + }; onBlur = (e: PointerEvent) => { setTimeout(() => { @@ -793,37 +841,40 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> { this.setShowDropdown(false); } }, 0); - } - + }; render() { return ( - <div className="button-dropdown-wrapper" ref={node => this.ref = node}> - {!this.props.pdf ? + <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}> + {!this.props.pdf ? ( <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.props.openDropdownOnButton ? this.onDropdownClick : undefined}> {this.props.button} - <div style={{ marginTop: "-8.5", position: "relative" }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}> + <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}> <FontAwesomeIcon icon="caret-down" size="sm" /> </div> </div> - : + ) : ( <> {this.props.button} <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}> <FontAwesomeIcon icon="caret-down" size="sm" /> </button> - </>} - {this.showDropdown ? this.props.dropdownContent : (null)} + </> + )} + {this.showDropdown ? this.props.dropdownContent : null} </div> ); } } - interface RichTextMenuPluginProps { editorProps: any; } export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> { - render() { return null; } - update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); } -}
\ No newline at end of file + render() { + return null; + } + update(view: EditorView, lastState: EditorState | undefined) { + RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); + } +} |