diff options
author | Bob Zeleznik <zzzman@gmail.com> | 2019-06-12 23:33:21 -0400 |
---|---|---|
committer | Bob Zeleznik <zzzman@gmail.com> | 2019-06-12 23:33:21 -0400 |
commit | 37335c64be97502895d6fd80282daa8ef42a81a5 (patch) | |
tree | 2ef3cda0e5398c6da8cf1306150425df67e4cd6e /src/client/views/nodes/FormattedTextBox.tsx | |
parent | 27efc9b078b3301ebf73a9ba7dc881bd354e71d9 (diff) | |
parent | a638c12cde39a3ea5193a8038f72a55d706d9af8 (diff) |
Merge branch 'master' into text_box_ab
Diffstat (limited to 'src/client/views/nodes/FormattedTextBox.tsx')
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 153 |
1 files changed, 74 insertions, 79 deletions
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 3ba108a57..6a490109f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -8,10 +8,12 @@ import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc, Field, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; +import { Docs } from '../../documents/Documents'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorKeymap"; @@ -30,30 +32,19 @@ import "./FormattedTextBox.scss"; import React = require("react"); import { DocUtils } from '../../documents/Documents'; import { start } from 'repl'; +import { ContextMenuProps } from '../ContextMenuItem'; library.add(faEdit); library.add(faSmile); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // -// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name} -// -// In Code, the node's HTML is specified in the document's parameterized structure as: -// document.SetField(KeyStore.Layout, "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />"); -// and the node's binding to the specified document KEYNAME as: -// document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.<KEYNAME>])); -// The Jsx parser at run time will bind: -// 'fieldKey' property to the Key stored in LayoutKeys -// and 'doc' property to the document that is being rendered -// -// When rendered() by React, this extracts the TextController from the Document stored at the -// 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 interface FormattedTextBoxProps { isOverlay?: boolean; hideOnLeave?: boolean; + height?: string; + color?: string; } const richTextSchema = createSchema({ @@ -71,15 +62,37 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _ref: React.RefObject<HTMLDivElement>; private _proseRef: React.RefObject<HTMLDivElement>; private _editorView: Opt<EditorView>; - private _gotDown: boolean = false; - private _dropDisposer?: DragManager.DragDropDisposer; + private _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + private _applyingChange: boolean = false; + private _linkClicked = ""; private _reactionDisposer: Opt<IReactionDisposer>; - private _inputReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + @observable _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; public static InputBoxOverlayScroll: number = 0; + public static IsFragment(html: string) { + return html.indexOf("data-pm-slice") !== -1; + } + public static GetHref(html: string): string { + let parser = new DOMParser(); + let parsedHtml = parser.parseFromString(html, 'text/html'); + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && + (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; + } + return ""; + } + public static GetDocFromUrl(url: string) { + if (url.startsWith(document.location.origin)) { + let start = url.indexOf(window.location.origin); + let path = url.substr(start, url.length - start); + let docid = path.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + return docid; + } + return ""; + } constructor(props: FieldViewProps) { super(props); @@ -91,12 +104,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - _applyingChange: boolean = false; - - _lastState: any = undefined; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const state = this._lastState = this._editorView.state.apply(tx); + const state = this._editorView.state.apply(tx); this._editorView.updateState(state); this._applyingChange = true; Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON()))); @@ -112,25 +122,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - @undoBatch - @action - drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc = de.data.linkSourceDocument; - let destDoc = this.props.Document; - - DocUtils.MakeLink(sourceDoc, destDoc); - de.data.droppedDocuments.push(destDoc); - e.stopPropagation(); - } - } - componentDidMount() { - if (this._ref.current) { - this._dropDisposer = DragManager.MakeDropTarget(this._ref.current, { - handlers: { drop: this.drop.bind(this) } - }); - } const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -152,16 +144,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ] }; - if (this.props.isOverlay) { - this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay, - () => { - if (this._editorView) { - this._editorView.destroy(); - } - 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 { + if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { if (this.props.isSelected()) { @@ -171,20 +154,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } - this._reactionDisposer = reaction( () => { const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined; - return field ? field.Data : undefined; + return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; }, - field => field && this._editorView && !this._applyingChange && + field => this._editorView && !this._applyingChange && this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); - this.setupEditor(config, this.props.Document); + this.setupEditor(config, this.props.Document, this.props.fieldKey); } - private setupEditor(config: any, doc?: Doc) { - let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined; + private setupEditor(config: any, doc: Doc, fieldKey: string) { + let field = doc ? Cast(doc[fieldKey], RichTextField) : undefined; + let startup = StrCast(doc.documentText); + startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : ""; + if (!startup && !field && doc) { + startup = StrCast(doc[fieldKey]); + } if (this._proseRef.current) { this._editorView = new EditorView(this._proseRef.current, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), @@ -194,16 +181,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe star(node, view, getPos) { return new SummarizedView(node, view, getPos); } } }); - let text = StrCast(this.props.Document.documentText); - if (text.startsWith("@@@")) { - this.props.Document.proto!.documentText = undefined; - this._editorView.dispatch(this._editorView.state.tr.insertText(text.replace("@@@", ""))); + if (startup) { + Doc.GetProto(doc).documentText = undefined; + this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); } } if (this.props.selectOnLoad) { - this.props.select(false); - this._editorView!.focus(); + if (!this.props.isOverlay) this.props.select(false); + else this._editorView!.focus(); } } @@ -214,15 +200,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._reactionDisposer) { this._reactionDisposer(); } - if (this._inputReactionDisposer) { - this._inputReactionDisposer(); - } if (this._proxyReactionDisposer) { this._proxyReactionDisposer(); } - if (this._dropDisposer) { - this._dropDisposer(); - } } onPointerDown = (e: React.PointerEvent): void => { @@ -240,10 +220,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (href) { if (href.indexOf(DocServer.prepend("/doc/")) === 0) { - let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => { - (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab")) - }); + this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + } else { + let webDoc = Docs.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) }); + this.props.addDocument && this.props.addDocument(webDoc); + this._linkClicked = webDoc[Id]; } e.stopPropagation(); e.preventDefault(); @@ -251,7 +232,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - this._gotDown = true; e.preventDefault(); } } @@ -299,7 +279,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } - _toolTipTextMenu: TooltipTextMenu | undefined = undefined; tooltipLinkingMenuPlugin() { let myprops = this.props; return new Plugin({ @@ -333,10 +312,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } + if (this.props.isOverlay && this.props.Document.autoHeight) { + let xf = this._ref.current!.getBoundingClientRect(); + let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height); + let nh = NumCast(this.props.Document.nativeHeight, 0); + let dh = NumCast(this.props.Document.height, 0); + let sh = scrBounds.height; + this.props.Document.height = nh ? dh / nh * sh : sh; + this.props.Document.proto!.nativeHeight = nh ? sh : undefined; + } } - @observable - _entered = false; @action onPointerEnter = (e: React.PointerEvent) => { this._entered = true; @@ -345,6 +331,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerLeave = (e: React.PointerEvent) => { this._entered = false; } + + specificContextMenu = (e: React.MouseEvent): void => { + let subitems: ContextMenuProps[] = []; + subitems.push({ + description: BoolCast(this.props.Document.autoHeight, false) ? "Manual Height" : "Auto Height", + event: action(() => this.props.Document.autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt" + }); + ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems }); + } render() { let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : ""; @@ -352,20 +347,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return ( <div className={`formattedTextBox-cont-${style}`} ref={this._ref} style={{ + height: this.props.height ? this.props.height : undefined, background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined, opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1, - color: this.props.hideOnLeave ? "white" : "initial", + color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial", pointerEvents: interactive ? "all" : "none", }} - // onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} + onContextMenu={this.specificContextMenu} onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onMouseDown={this.onMouseDown} - onContextMenu={this.specificContextMenu} // tfs: do we need this event handler onWheel={this.onPointerWheel} onPointerEnter={this.onPointerEnter} |