aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/FormattedTextBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx138
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;