diff options
Diffstat (limited to 'src/client/views/nodes/FormattedTextBox.tsx')
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 189 |
1 files changed, 129 insertions, 60 deletions
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index beca6cdc6..41ee24498 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,22 +1,27 @@ -import { action, IReactionDisposer, reaction } from "mobx"; +import { action, IReactionDisposer, reaction, trace, computed, _allowStateChangesInsideComputed } from "mobx"; import { baseKeymap } from "prosemirror-commands"; -import { history, redo, undo } from "prosemirror-history"; +import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { FieldWaiting, Opt } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { RichTextField } from "../../../fields/RichTextField"; +import { TextField } from "../../../fields/TextField"; +import { Document } from "../../../fields/Document"; +import buildKeymap from "../../util/ProsemirrorKeymap"; import { inpRules } from "../../util/RichTextRules"; import { schema } from "../../util/RichTextSchema"; +import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { ContextMenu } from "../../views/ContextMenu"; -import { Main } from "../Main"; +import { MainOverlayTextBox } from "../MainOverlayTextBox"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -const { buildMenuItems } = require("prosemirror-example-setup"); -const { menuBar } = require("prosemirror-menu"); +import { SelectionManager } from "../../util/SelectionManager"; +import { observer } from "mobx-react"; +import { InkingControl } from "../InkingControl"; // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -34,90 +39,108 @@ const { menuBar } = require("prosemirror-menu"); // specified Key and assigns it to an HTML input node. When changes are made to this node, // this will edit the document and assign the new value to that field. //] -export class FormattedTextBox extends React.Component<FieldViewProps> { + +export interface FormattedTextBoxOverlay { + isOverlay?: boolean; +} + +@observer +export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> { public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } private _ref: React.RefObject<HTMLDivElement>; + private _proseRef: React.RefObject<HTMLDivElement>; private _editorView: Opt<EditorView>; + private _gotDown: boolean = false; private _reactionDisposer: Opt<IReactionDisposer>; private _inputReactionDisposer: Opt<IReactionDisposer>; + private _proxyReactionDisposer: Opt<IReactionDisposer>; constructor(props: FieldViewProps) { super(props); this._ref = React.createRef(); + this._proseRef = React.createRef(); this.onChange = this.onChange.bind(this); } + _applyingChange: boolean = false; + + _lastState: any = undefined; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const state = this._editorView.state.apply(tx); + const state = this._lastState = this._editorView.state.apply(tx); this._editorView.updateState(state); - this.FieldDoc.SetDataOnPrototype( - this.FieldKey, + this._applyingChange = true; + this.props.Document.SetDataOnPrototype( + this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField ); - // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + this.props.Document.SetDataOnPrototype(KeyStore.DocumentText, state.doc.textBetween(0, state.doc.content.size, "\n\n"), TextField); + this._applyingChange = false; + if (this.props.Document.Title.startsWith("-") && this._editorView) { + let str = this._editorView.state.doc.textContent; + let titlestr = str.substr(0, Math.min(40, str.length)); + this.props.Document.SetText(KeyStore.Title, "-" + titlestr + (str.length > 40 ? "..." : "")); + }; } } - get FieldDoc() { return this.props.fieldKey === KeyStore.Archives ? Main.Instance._textDoc! : this.props.Document; } - get FieldKey() { return this.props.fieldKey === KeyStore.Archives ? KeyStore.Data : this.props.fieldKey; } - componentDidMount() { const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful - plugins: [ + plugins: this.props.isOverlay ? [ + this.tooltipTextMenuPlugin(), history(), - keymap({ "Mod-z": undo, "Mod-y": redo }), + keymap(buildKeymap(schema)), keymap(baseKeymap), - this.tooltipMenuPlugin() - ] + // this.tooltipLinkingMenuPlugin(), + new Plugin({ + props: { + attributes: { class: "ProseMirror-example-setup-style" } + } + }) + ] : [ + history(), + keymap(buildKeymap(schema)), + keymap(baseKeymap), + ] }; - if (this.props.fieldKey === KeyStore.Archives) { - this._inputReactionDisposer = reaction(() => Main.Instance._textDoc && Main.Instance._textDoc.Id, + if (this.props.isOverlay) { + this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc.Id, () => { if (this._editorView) { this._editorView.destroy(); } - - this.setupEditor(config); + this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox } ); + } else { + this._proxyReactionDisposer = reaction(() => this.props.isSelected(), + () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform)); } + this._reactionDisposer = reaction( () => { - const field = this.FieldDoc.GetT(this.FieldKey, RichTextField); + const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined; return field && field !== FieldWaiting ? field.Data : undefined; }, - field => { - if (field && this._editorView) { - this._editorView.updateState( - EditorState.fromJSON(config, JSON.parse(field)) - ); - } - } + field => field && this._editorView && !this._applyingChange && + this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); - this.setupEditor(config); + this.setupEditor(config, this.props.Document); } - private setupEditor(config: any) { - - let state: EditorState; - let field = this.FieldDoc.GetT(this.FieldKey, RichTextField); - if (field && field !== FieldWaiting && field.Data) { - state = EditorState.fromJSON(config, JSON.parse(field.Data)); - } else { - state = EditorState.create(config); - } + private setupEditor(config: any, doc?: Document) { + let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined; if (this._ref.current) { this._editorView = new EditorView(this._ref.current, { - state, + state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction }); } @@ -138,10 +161,9 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { if (this._inputReactionDisposer) { this._inputReactionDisposer(); } - } - - shouldComponentUpdate() { - return false; + if (this._proxyReactionDisposer) { + this._proxyReactionDisposer(); + } } @action @@ -150,24 +172,33 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { Document.SetOnPrototype(fieldKey, new RichTextField(e.target.value)); // doc.SetData(fieldKey, e.target.value, RichTextField); } + @action onPointerDown = (e: React.PointerEvent): void => { - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); + if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) + this._toolTipTextMenu.tooltip.style.opacity = "0"; + } + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { + this._gotDown = true; + e.preventDefault(); } } onPointerUp = (e: React.PointerEvent): void => { + if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) + this._toolTipTextMenu.tooltip.style.opacity = "1"; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } - if (this.props.fieldKey !== KeyStore.Archives) { - e.preventDefault(); - Main.Instance.SetTextDoc(this.props.Document, this._ref.current!); - } } onFocused = (e: React.FocusEvent): void => { - if (this.props.fieldKey !== KeyStore.Archives) { - Main.Instance.SetTextDoc(this.props.Document, this._ref.current!); + if (!this.props.isOverlay) { + MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform); + } else { + if (this._ref.current) { + this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll; + } } } @@ -175,6 +206,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { textCapability = (e: React.MouseEvent): void => { }; specificContextMenu = (e: React.MouseEvent): void => { + if (!this._gotDown) { + e.preventDefault(); + return; + } ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability @@ -195,35 +230,69 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { } onPointerWheel = (e: React.WheelEvent): void => { - e.stopPropagation(); + if (this.props.isSelected()) { + e.stopPropagation(); + } + } + + onClick = (e: React.MouseEvent): void => { + this._proseRef.current!.focus(); + } + + tooltipTextMenuPlugin() { + let myprops = this.props; + let self = this; + return new Plugin({ + view(_editorView) { + return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops); + } + }); } - tooltipMenuPlugin() { + _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + tooltipLinkingMenuPlugin() { + let myprops = this.props; return new Plugin({ view(_editorView) { - return new TooltipTextMenu(_editorView); + return new TooltipLinkingMenu(_editorView, myprops); } }); } - onKeyPress(e: React.KeyboardEvent) { + + @action + onKeyPress = (e: React.KeyboardEvent) => { + if (e.key == "Escape") { + SelectionManager.DeselectAll(); + } e.stopPropagation(); + if (e.key === "Tab") e.preventDefault(); // stop propagation doesn't seem to stop propagation of native keyboard events. // so we set a flag on the native event that marks that the event's been handled. - // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; + (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; } render() { + let style = this.props.isOverlay ? "-scroll" : "-hidden"; + let rounded = this.props.Document.GetNumber(KeyStore.BorderRounding, 0) < 0 ? "-rounded" : ""; + let color = this.props.Document.GetText(KeyStore.BackgroundColor, ""); + let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; return ( - <div - className="formattedTextBox-cont" + <div className={`formattedTextBox-cont${style}`} ref={this._ref} + style={{ + pointerEvents: interactive ? "all" : "none", + background: color, + }} onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} + onFocus={this.onFocused} + onClick={this.onClick} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu} // tfs: do we need this event handler onWheel={this.onPointerWheel} - ref={this._ref} - /> + > + <div className={`formattedTextBox-inner${rounded}`} ref={this._proseRef} /> + </div> ); } } |
