diff options
-rw-r--r-- | src/client/views/AntimodeMenu.tsx | 9 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 38 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextMenu.tsx | 61 |
3 files changed, 68 insertions, 40 deletions
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index 3e4d20fea..a4634103c 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -51,18 +51,15 @@ export default abstract class AntimodeMenu extends React.Component { if (this._opacity === 0.2) { this._transitionProperty = "opacity"; this._transitionDuration = "0.1s"; - this._transitionDelay = ""; - this._opacity = 0; - this._left = this._top = -300; } if (forceOut) { this._transitionProperty = ""; this._transitionDuration = ""; - this._transitionDelay = ""; - this._opacity = 0; - this._left = this._top = -300; } + this._transitionDelay = ""; + this._opacity = 0; + this._left = this._top = -300; } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2cb55e0fa..f4aa7919f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -86,6 +86,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; public ProseRef?: HTMLDivElement; + public get EditorView() { return this._editorView; } private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; @@ -93,7 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _searchIndex = 0; private _undoTyping?: UndoManager.Batch; private _disposers: { [name: string]: IReactionDisposer } = {}; - private dropDisposer?: DragManager.DragDropDisposer; + private _dropDisposer?: DragManager.DragDropDisposer; @computed get _recording() { return this.dataDoc.audioState === "recording"; } set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } @@ -291,8 +292,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } protected createDropTarget = (ele: HTMLDivElement) => { this.ProseRef = ele; - this.dropDisposer?.(); - ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc)); + this._dropDisposer?.(); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc)); } @undoBatch @@ -660,8 +661,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp incomingValue => { if (incomingValue !== undefined && this._editorView && !this._applyingChange) { const updatedState = JSON.parse(incomingValue); - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); + if (JSON.stringify(this._editorView!.state.toJSON()) !== JSON.stringify(updatedState)) { + this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + this.tryUpdateHeight(); + } } } ); @@ -1046,6 +1049,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action onFocused = (e: React.FocusEvent): void => { + console.log("FOUCSS") FormattedTextBox.FocusedBox = this; this.tryUpdateHeight(); @@ -1163,18 +1167,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); } - public static HadSelection: boolean = false; - onBlur = (e: any) => { - FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; - //DictationManager.Controls.stop(false); + public startUndoTypingBatch() { + this._undoTyping = UndoManager.StartBatch("undoTyping"); + } + + public endUndoTypingBatch() { + const wasUndoing = this._undoTyping; if (this._undoTyping) { this._undoTyping.end(); this._undoTyping = undefined; } + return wasUndoing; + } + public static HadSelection: boolean = false; + onBlur = (e: any) => { + console.log("BLURRR") + FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; + //DictationManager.Controls.stop(false); + this.endUndoTypingBatch(); this.doLinkOnDeselect(); // move the richtextmenu offscreen - if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300); + if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide(); } _lastTimedMark: Mark | undefined = undefined; @@ -1208,7 +1222,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); if (!this._undoTyping) { - this._undoTyping = UndoManager.StartBatch("undoTyping"); + this.startUndoTypingBatch(); } } @@ -1308,7 +1322,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onScroll={this.onscrolled} onDrop={this.ondrop} > <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget} style={{ - padding: this.layoutDoc._textBoxPadding ? this.layoutDoc._textBoxPadding : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`, + padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`, pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined }} /> diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index a06fcec9a..7c7a76e1b 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, observable } from "mobx"; +import { action, observable, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { lift, wrapIn } from "prosemirror-commands"; import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model"; @@ -23,6 +23,7 @@ import { updateBullets } from "./ProsemirrorExampleTransfer"; import "./RichTextMenu.scss"; import { schema } from "./schema_rts"; import { TraceMobx } from "../../../../fields/util"; +import { UndoManager } from "../../../util/UndoManager"; const { toggleMark } = require("prosemirror-commands"); library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); @@ -68,6 +69,8 @@ export default class RichTextMenu extends AntimodeMenu { @observable private currentLink: string | undefined = ""; @observable private showLinkDropdown: boolean = false; + _reaction: IReactionDisposer | undefined; + _delayHide = false; constructor(props: Readonly<{}>) { super(props); RichTextMenu.Instance = this; @@ -138,6 +141,16 @@ export default class RichTextMenu extends AntimodeMenu { ]; } + componentDidMount() { + this._reaction = reaction(() => SelectionManager.SelectedDocuments(), + () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true)); + } + componentWillUnmount() { + this._reaction?.(); + } + + public delayHide = () => { this._delayHide = true; } + @action changeView(view: EditorView) { this.view = view; @@ -147,16 +160,6 @@ export default class RichTextMenu extends AntimodeMenu { this.updateFromDash(view, lastState, this.editorProps); } - public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { - if (this.view) { - const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). - addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - return this.view.state.selection.$from.nodeAfter?.text || ""; - } - return ""; - } - @action public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { if (!view) { @@ -310,8 +313,11 @@ export default class RichTextMenu extends AntimodeMenu { function onClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && command && command(self.view.state, self.view.dispatch, self.view); - self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => { + self.view && command && command(self.view.state, self.view.dispatch, self.view); + self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); + }, "rich text menu command"); self.setActiveMarkButtons(self.getActiveMarksOnSelection()); } @@ -338,9 +344,10 @@ export default class RichTextMenu extends AntimodeMenu { function onChange(e: React.ChangeEvent<HTMLSelectElement>) { e.stopPropagation(); e.preventDefault(); + self.TextView.endUndoTypingBatch(); options.forEach(({ label, mark, command }) => { if (e.target.value === label) { - self.view && mark && command(mark, self.view); + UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown"); } }); } @@ -361,9 +368,10 @@ export default class RichTextMenu extends AntimodeMenu { const self = this; function onChange(val: string) { + self.TextView.endUndoTypingBatch(); options.forEach(({ label, node, command }) => { if (val === label) { - self.view && node && command(node); + UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown"); } }); } @@ -461,7 +469,8 @@ export default class RichTextMenu extends AntimodeMenu { function onBrushClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.fillBrush(self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.fillBrush(self.view.state, self.view.dispatch), "rt brush"); } let label = "Stored marks: "; @@ -528,19 +537,24 @@ export default class RichTextMenu extends AntimodeMenu { @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; } @action setActiveColor(color: string) { this.activeFontColor = color; } + get TextView() { return (this.view as any).TextView as FormattedTextBox } createColorButton() { const self = this; function onColorClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); + self.TextView.EditorView!.focus() } function changeColor(e: React.PointerEvent, color: string) { e.preventDefault(); e.stopPropagation(); self.setActiveColor(color); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); + self.TextView.EditorView!.focus() } const button = @@ -585,13 +599,15 @@ export default class RichTextMenu extends AntimodeMenu { function onHighlightClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highligher"); } function changeHighlight(e: React.PointerEvent, color: string) { e.preventDefault(); e.stopPropagation(); self.setActiveHighlight(color); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter"); } const button = @@ -631,7 +647,8 @@ export default class RichTextMenu extends AntimodeMenu { const self = this; function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) { - self.setCurrentLink(e.target.value); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change"); } const link = this.currentLink ? this.currentLink : ""; @@ -766,7 +783,7 @@ export default class RichTextMenu extends AntimodeMenu { return ref_node; } - @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; } + @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } @action |