diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 138 |
1 files changed, 62 insertions, 76 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 29f6e2bd8..89df7a98a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -15,7 +15,7 @@ import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, Wid import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; -import { Id, Copy } from '../../../../fields/FieldSymbols'; +import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; @@ -90,8 +90,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public static get DefaultLayout() { return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); } - public static CanAnnotate = true; public static Instance: FormattedTextBox; + static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + static _highlightStyleSheet: any = addStyleSheet(); + static _bulletStyleSheet: any = addStyleSheet(); + static _userStyleSheet: any = addStyleSheet(); + static _canAnnotate = true; + static _hadSelection: boolean = false; + public static LiveTextUndo: UndoManager.Batch | undefined; public ProseRef?: HTMLDivElement; public get EditorView() { return this._editorView; } public get SidebarKey() { return this.fieldKey + "-sidebar"; } @@ -111,6 +117,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _focusSpeed: Opt<number>; private _keymap: any = undefined; private _rules: RichTextRules | undefined; + private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle + private _forceDownNode: Node | undefined; + private _downEvent: any; + private _downX = 0; + private _downY = 0; + private _break = false; @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } @@ -121,10 +133,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } @computed get _recording() { return this.dataDoc?.audioState === "recording"; } set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } + @computed get config() { + this._keymap = buildKeymap(schema, this.props); + this._rules = new RichTextRules(this.props.Document, this); + return { + schema, + plugins: [ + inputRules(this._rules.inpRules), + this.richTextMenuPlugin(), + history(), + keymap(this._keymap), + keymap(baseKeymap), + new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }), + new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) + ] + }; + } public static FocusedBox: FormattedTextBox | undefined; - public static SelectOnLoad = ""; public static PasteOnLoad: ClipboardEvent | undefined; + public static SelectOnLoad = ""; public static SelectOnLoadChar = ""; public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } public static GetHref(html: string): string { @@ -314,7 +342,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } // needs a better API for taking in a set of words with target documents instead of just one target - public hyperlinkTerms = (terms: string[], target: Doc) => { + hyperlinkTerms = (terms: string[], target: Doc) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { const res1 = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); let tr = this._editorView.state.tr; @@ -334,7 +362,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr); } } - public highlightSearchTerms = (terms: string[], backward: boolean) => { + highlightSearchTerms = (terms: string[], backward: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); @@ -363,7 +391,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - public unhighlightSearchTerms = () => { + unhighlightSearchTerms = () => { if (window.screen.width < 600) null; else if (this._editorView && (this._editorView as any).docView) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); @@ -409,7 +437,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp target._fitToBox = true; const node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], + title: "dashDoc", + docid: target[Id], float: "right" }); const view = this._editorView!; @@ -463,7 +492,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return ret; } - static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; updateHighlights = () => { clearStyleSheetRules(FormattedTextBox._userStyleSheet); if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) { @@ -548,7 +576,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp })); const uicontrols: ContextMenuProps[] = []; - uicontrols.push({ description: `${FormattedTextBox.CanAnnotate ? "Hide" : "Show"} Annotation Bar`, event: () => FormattedTextBox.CanAnnotate = !FormattedTextBox.CanAnnotate, icon: "expand-arrows-alt" }); + uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Hide" : "Show"} Annotation Bar`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); @@ -637,26 +665,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - @computed get config() { - this._keymap = buildKeymap(schema, this.props); - this._rules = new RichTextRules(this.props.Document, this); - return { - schema, - plugins: [ - inputRules(this._rules.inpRules), - this.richTextMenuPlugin(), - history(), - keymap(this._keymap), - keymap(baseKeymap), - new Plugin({ - props: { - attributes: { class: "ProseMirror-example-setup-style" } - } - }), new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) - ] - }; - } - makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) { const state = this._editorView?.state; if (state) { @@ -761,7 +769,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp instance => { if (instance) { this.pullFromGoogleDoc(this.checkState); - this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true); + this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true); } } ); @@ -791,7 +799,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp () => { if (!DocumentButtonBar.hasPulledHack) { DocumentButtonBar.hasPulledHack = true; - this.pullFromGoogleDoc(this.dataDoc.unchanged ? this.checkState : this.updateState); + this.pullFromGoogleDoc(this.dataDoc.googleDocUnchanged ? this.checkState : this.updateState); } } ); @@ -866,7 +874,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); const pushSuccess = response !== undefined && !("errors" in response); - dataDoc.unchanged = pushSuccess; + dataDoc.googleDocUnchanged = pushSuccess; DocumentButtonBar.Instance.startPushOutcome(pushSuccess); } }; @@ -911,7 +919,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, 0); dataDoc.title = exportState.title; this.dataDoc["title-custom"] = true; - dataDoc.unchanged = true; + dataDoc.googleDocUnchanged = true; } else { delete dataDoc[GoogleRef]; } @@ -923,7 +931,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); const equalTitles = dataDoc.title === exportState.title; const unchanged = equalContent && equalTitles; - dataDoc.unchanged = unchanged; + dataDoc.googleDocUnchanged = unchanged; DocumentButtonBar.Instance.setPullState(unchanged); } } @@ -1100,11 +1108,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); } - _downEvent: any; - _downX = 0; - _downY = 0; - _break = false; - _collapsed = false; onPointerDown = (e: React.PointerEvent): void => { if ((e.target as any).tagName === "AUDIOTAG") { e.preventDefault(); @@ -1152,9 +1155,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp document.removeEventListener("pointerup", this.onSelectEnd); document.removeEventListener("pointermove", this.onSelectMove); } - onPointerUp = (e: React.PointerEvent): void => { - if (!this._editorView?.state.selection.empty && FormattedTextBox.CanAnnotate) this.setupAnchorMenu(); + if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; if (!(e.nativeEvent as any).formattedHandled && this.active(true)) { @@ -1171,7 +1173,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); } } - @action onDoubleClick = (e: React.MouseEvent): void => { FormattedTextBoxComment.textBox = this; @@ -1191,7 +1192,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); } } - @action onFocused = (e: React.FocusEvent): void => { FormattedTextBox.FocusedBox = this; @@ -1223,12 +1223,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); } } - - static _highlightStyleSheet: any = addStyleSheet(); - static _bulletStyleSheet: any = addStyleSheet(); - static _userStyleSheet: any = addStyleSheet(); - _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle - _forceDownNode: Node | undefined; onClick = (e: React.MouseEvent): void => { if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { this._forceDownNode = undefined; @@ -1264,7 +1258,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed; this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node; } - // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) { this._forceUncollapse = false; @@ -1314,24 +1307,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp view.root.addEventListener("mouseup", view.mouseDown.up); } } - - public startUndoTypingBatch() { + startUndoTypingBatch() { !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); } - public endUndoTypingBatch() { const wasUndoing = this._undoTyping; this._undoTyping?.end(); this._undoTyping = undefined; return wasUndoing; } - public static LiveTextUndo: UndoManager.Batch | undefined; - public static HadSelection: boolean = false; onBlur = (e: any) => { if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } - FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; + FormattedTextBox._hadSelection = window.getSelection()?.toString() !== ""; this.endUndoTypingBatch(); FormattedTextBox.LiveTextUndo?.end(); @@ -1350,7 +1339,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._lastText = curText; } } - onKeyDown = (e: React.KeyboardEvent) => { // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container) if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) { @@ -1372,29 +1360,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._rules!.EnteringStyle = false; } e.stopPropagation(); - if (e.key === "Escape") { - this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur?.(); - SelectionManager.DeselectAll(); - RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); - } else { - if (e.key === "Tab" || e.key === "Enter") { - if (e.key === "Enter") this.insertTime(); - e.preventDefault(); - } - if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) { - const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); - } - this.startUndoTypingBatch(); + switch (e.key) { + case "Escape": + this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); + (document.activeElement as any).blur?.(); + SelectionManager.DeselectAll(); + RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); + return; + case "Enter": this.insertTime(); + case "Tab": e.preventDefault(); break; + default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; + case " ": + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})) + .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } + this.startUndoTypingBatch(); } - - ondrop = (eve: React.DragEvent) => { + ondrop = (e: React.DragEvent) => { this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); - eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. + e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. } - onScroll = (ev: React.UIEvent) => { + onScroll = (e: React.UIEvent) => { if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { this._ignoreScroll = true; this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop; |