aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/FormattedTextBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/FormattedTextBox.tsx')
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx241
1 files changed, 141 insertions, 100 deletions
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 213af43c6..41c94b923 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,24 +1,28 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace, _allowStateChangesInsideComputed } from "mobx";
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Node as ProsNode, Slice } from "prosemirror-model";
+import { Fragment, Mark, Node, Slice } from "prosemirror-model";
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCastAsync, Opt, WidthSym, HeightSym, DataSym, Field } from "../../../new_fields/Doc";
-import { Copy, Id } from '../../../new_fields/FieldSymbols';
+import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { Id } from '../../../new_fields/FieldSymbols';
+import { InkTool } from '../../../new_fields/InkField';
import { RichTextField } from "../../../new_fields/RichTextField";
import { RichTextUtils } from '../../../new_fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../new_fields/Schema";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils';
+import { TraceMobx } from '../../../new_fields/util';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils } from '../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
@@ -26,26 +30,23 @@ import { DocumentType } from '../../documents/DocumentTypes';
import { DictationManager } from '../../util/DictationManager';
import { DragManager } from "../../util/DragManager";
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
-import { inpRules } from "../../util/RichTextRules";
-import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView, DashFieldView } from "../../util/RichTextSchema";
+import RichTextMenu from '../../util/RichTextMenu';
+import { RichTextRules } from "../../util/RichTextRules";
+import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, ImageResizeView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema";
import { SelectionManager } from "../../util/SelectionManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocAnnotatableComponent, DocAnnotatableProps } from "../DocComponent";
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { DocAnnotatableComponent } from "../DocComponent";
import { DocumentButtonBar } from '../DocumentButtonBar';
import { InkingControl } from "../InkingControl";
+import { AudioBox } from './AudioBox';
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
import React = require("react");
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ContextMenu } from '../ContextMenu';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { AudioBox } from './AudioBox';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { InkTool } from '../../../new_fields/InkField';
-import { TraceMobx } from '../../../new_fields/util';
-import RichTextMenu from '../../util/RichTextMenu';
+import { PrefetchProxy } from '../../../new_fields/Proxy';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -88,12 +89,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
private _pullReactionDisposer: Opt<IReactionDisposer>;
private _pushReactionDisposer: Opt<IReactionDisposer>;
private _buttonBarReactionDisposer: Opt<IReactionDisposer>;
+ private _scrollDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
@observable private _entered = false;
public static FocusedBox: FormattedTextBox | undefined;
public static SelectOnLoad = "";
+ public static SelectOnLoadChar = "";
public static IsFragment(html: string) {
return html.indexOf("data-pm-slice") !== -1;
}
@@ -184,20 +187,28 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
const tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000)));
- this._applyingChange = true;
- if (!this.props.Document._textTemplate || Doc.GetProto(this.props.Document) === this.dataDoc) {
- this.dataDoc[this.props.fieldKey + "-lastModified"] && (this.dataDoc[this.props.fieldKey + "-backgroundColor"] = "lightGray");
+ const curText = state.doc.textBetween(0, state.doc.content.size, "\n\n");
+ const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
+ if (!this._applyingChange) {
+ this._applyingChange = true;
this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
- this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+ if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+ this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText);
+ this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+ this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data)));
+ this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+ }
+ this._applyingChange = false;
}
- this._applyingChange = false;
this.updateTitle();
this.tryUpdateHeight();
}
}
updateTitle = () => {
- if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
+ if ((this.props.Document.isTemplateForField === "data" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
const str = this._editorView.state.doc.textContent;
const titlestr = str.substr(0, Math.min(40, str.length));
this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
@@ -249,17 +260,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
e.stopPropagation();
}
- // apply as template when dragging with Meta
- } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.metaKey) {
- draggedDoc.isTemplateDoc = true;
- let newLayout = Doc.Layout(draggedDoc);
- if (typeof (draggedDoc.layout) === "string") {
- newLayout = Doc.MakeDelegate(draggedDoc);
- newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${this.props.fieldKey}'}`);
- }
- this.Document.layout_custom = newLayout;
- this.Document.layoutKey = "layout_custom";
- e.stopPropagation();
// embed document when dragging with a userDropAction or an embedDoc flag set
} else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
const target = de.complete.docDragData.droppedDocuments[0];
@@ -377,10 +377,18 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
+ public static get DefaultLayout(): Doc | string | undefined {
+ return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
+ }
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.toggleSidebar(); }, icon: "expand-arrows-alt" });
- funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
+ this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document.proto as Doc), icon: "eye" });
+ funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+ !this.props.Document.expandedTemplate && funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = true; Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); }, icon: "eye" });
+ funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
+ funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
+ funcs.push({ description: "Record Bullet", event: () => this.recordBullet(), icon: "expand-arrows-alt" });
+ funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
funcs.push({
description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
@@ -418,6 +426,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
DictationManager.Controls.stop(!abort);
}
+ @action
+ toggleMenubar = () => {
+ this.props.Document._chromeStatus = this.props.Document._chromeStatus == "disabled" ? "enabled" : "disabled";
+ }
+
recordBullet = async () => {
const completedCue = "end session";
const results = await DictationManager.Controls.listen({
@@ -464,13 +477,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
_keymap: any = undefined;
+ _rules: RichTextRules | undefined;
@computed get config() {
- this._keymap = buildKeymap(schema);
- (schema as any).Document = this.props.Document;
+ this._keymap = buildKeymap(schema, this.props);
+ this._rules = new RichTextRules(this.props.Document, this);
return {
schema,
plugins: [
- inputRules(inpRules),
+ inputRules(this._rules.inpRules),
this.richTextMenuPlugin(),
history(),
keymap(this._keymap),
@@ -498,11 +512,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this._reactionDisposer = reaction(
() => {
- const field = Cast(this.props.Document._textTemplate || this.dataDoc[this.props.fieldKey], RichTextField);
- return field ? field.Data : RichTextUtils.Initialize();
+ if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) {
+ return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
+ }
+ return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
},
incomingValue => {
- if (this._editorView && !this._applyingChange) {
+ if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
const updatedState = JSON.parse(incomingValue);
this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
this.tryUpdateHeight();
@@ -574,7 +590,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
if (ret.frag.size > 2 && ret.start >= 0) {
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
if (ret.frag.firstChild) {
- selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+ selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
}
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
@@ -588,6 +604,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
{ fireImmediately: true }
);
+ this._scrollDisposer = reaction(() => NumCast(this.props.Document.scrollPos),
+ pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
+ );
+
setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
}
@@ -700,7 +720,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
setTimeout(async () => {
const targetField = Doc.LayoutFieldKey(pdfDoc);
const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
- targetAnnotations ?.push(pdfRegion);
+ targetAnnotations?.push(pdfRegion);
});
const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
@@ -739,17 +759,19 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
private setupEditor(config: any, fieldKey: string) {
- const rtfField = Cast(this.props.Document._textTemplate || this.dataDoc[fieldKey], RichTextField);
+ const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
+ const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"];
+ const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
if (this.ProseRef) {
const self = this;
- this._editorView ?.destroy();
+ this._editorView?.destroy();
this._editorView = new EditorView(this.ProseRef, {
- state: rtfField ?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
+ state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
handleScrollToSelection: (editorView) => {
const ref = editorView.domAtPos(editorView.state.selection.from);
let refNode = ref.node as any;
while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
- const r1 = refNode ?.getBoundingClientRect();
+ const r1 = refNode?.getBoundingClientRect();
const r3 = self._ref.current!.getBoundingClientRect();
if (r1.top < r3.top || r1.top > r3.bottom) {
r1 && (self._scrollRef.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
@@ -769,17 +791,19 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- this._editorView.state.schema.Document = this.props.Document;
const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
if (startupText) {
this._editorView.dispatch(this._editorView.state.tr.insertText(startupText));
}
}
- const selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad;
+ const selectOnLoad = (Cast(this.props.Document.expandedTemplate, Doc, null) || this.props.Document)[Id] === FormattedTextBox.SelectOnLoad;
if (selectOnLoad) {
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
+ FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
+ FormattedTextBox.SelectOnLoadChar = "";
+
}
(selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
@@ -799,19 +823,24 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
componentWillUnmount() {
- this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer();
- this._reactionDisposer && this._reactionDisposer();
- this._proxyReactionDisposer && this._proxyReactionDisposer();
- this._pushReactionDisposer && this._pushReactionDisposer();
- this._pullReactionDisposer && this._pullReactionDisposer();
- this._heightReactionDisposer && this._heightReactionDisposer();
- this._searchReactionDisposer && this._searchReactionDisposer();
- this._buttonBarReactionDisposer && this._buttonBarReactionDisposer();
- this._editorView && this._editorView.destroy();
+ this._scrollDisposer?.();
+ this._scrollToRegionReactionDisposer?.();
+ this._reactionDisposer?.();
+ this._proxyReactionDisposer?.();
+ this._pushReactionDisposer?.();
+ this._pullReactionDisposer?.();
+ this._heightReactionDisposer?.();
+ this._searchReactionDisposer?.();
+ this._buttonBarReactionDisposer?.();
+ this._editorView?.destroy();
}
static _downEvent: any;
+ _downX = 0;
+ _downY = 0;
onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
this.doLinkOnDeselect();
FormattedTextBox._downEvent = true;
FormattedTextBoxComment.textBox = this;
@@ -848,15 +877,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.tryUpdateHeight();
// see if we need to preserve the insertion point
- const prosediv = this.ProseRef ?.children ?.[0] as any;
- const keeplocation = prosediv ?.keeplocation;
+ const prosediv = this.ProseRef?.children?.[0] as any;
+ const keeplocation = prosediv?.keeplocation;
prosediv && (prosediv.keeplocation = undefined);
- const pos = this._editorView ?.state.selection.$from.pos || 1;
- keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+ const pos = this._editorView?.state.selection.$from.pos || 1;
+ keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
// jump rich text menu to this textbox
const { current } = this._ref;
- if (current) {
+ if (current && this.props.Document._chromeStatus !== "disabled") {
const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50;
RichTextMenu.Instance.jumpTo(x, y);
@@ -876,13 +905,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
- if (pcords && node ?.type === this._editorView!.state.schema.nodes.dashComment) {
+ if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
e.preventDefault();
}
if (!node && this.ProseRef) {
const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
- if (e.clientY > lastNode ?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+ if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
}
}
@@ -910,7 +939,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
// if (linkClicked) {
// DocServer.GetRefField(linkClicked).then(async linkDoc => {
// (linkDoc instanceof Doc) &&
- // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false);
+ // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false);
// });
// }
// } else {
@@ -922,7 +951,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
// }
// }
- this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
+ if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) {
+ this.props.select(e.ctrlKey);
+ this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
+ }
if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500);
}
@@ -939,7 +971,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
for (let off = 1; off < 100; off++) {
const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
- if (node ?.type === schema.nodes.list_item) {
+ if (node?.type === schema.nodes.list_item) {
list_node = node;
break;
}
@@ -954,8 +986,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
} else if (Math.abs(pos.pos - pos.inside) < 2) {
if (!highlightOnly) {
- this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
- this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside)));
+ const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0;
+ this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset)));
}
addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
}
@@ -981,7 +1014,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
richTextMenuPlugin() {
- const self = FormattedTextBox;
return new Plugin({
view(newView) {
RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
@@ -990,7 +1022,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
});
}
+ public static HadSelection: boolean = false;
onBlur = (e: any) => {
+ FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
//DictationManager.Controls.stop(false);
if (this._undoTyping) {
this._undoTyping.end();
@@ -1010,18 +1044,18 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
const state = this._editorView!.state;
if (!state.selection.empty && e.key === "%") {
- state.schema.EnteringStyle = true;
+ this._rules!.EnteringStyle = true;
e.preventDefault();
e.stopPropagation();
return;
}
- if (state.selection.empty || !state.schema.EnteringStyle) {
- state.schema.EnteringStyle = false;
+ if (state.selection.empty || !this._rules!.EnteringStyle) {
+ this._rules!.EnteringStyle = false;
}
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 ?.();
+ (document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
}
e.stopPropagation();
@@ -1041,10 +1075,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
}
+ onscrolled = (ev: React.UIEvent) => {
+ this.props.Document.scrollPos = this._scrollRef.current!.scrollTop;
+ }
@action
tryUpdateHeight(limitHeight?: number) {
- let scrollHeight = this._ref.current ?.scrollHeight;
- if (!this.layoutDoc.animateToPos && this.layoutDoc._autoHeight && scrollHeight &&
+ let scrollHeight = this._ref.current?.scrollHeight;
+ if (this.layoutDoc._autoHeight && scrollHeight &&
getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
if (limitHeight && scrollHeight > limitHeight) {
scrollHeight = limitHeight;
@@ -1062,23 +1099,23 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
@computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
- @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
+ sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
+ sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
@computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
render() {
TraceMobx();
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
if (this.props.isSelected()) {
- // TODO: ftong --> update from dash in richtextmenu
- RichTextMenu.Instance.updateFromDash(this._editorView!, undefined, this.props);
+ this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
} else if (FormattedTextBoxComment.textBox === this) {
FormattedTextBoxComment.Hide();
}
return (
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
- height: this.layoutDoc._autoHeight ? "max-content" : undefined,
- background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
+ height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
+ background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"]),
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.hideOnLeave ? "white" : "inherit",
pointerEvents: interactive ? "none" : "all",
@@ -1098,16 +1135,20 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
onPointerEnter={action(() => this._entered = true)}
onPointerLeave={action(() => this._entered = false)}
>
- <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} ref={this._scrollRef}>
- <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
+ <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
+ <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+ style={{
+ padding: `${NumCast(this.Document._xMargin, 0)}px ${NumCast(this.Document._yMargin, 0)}px`,
+ pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+ }} />
</div>
- {this.props.Document._hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
+ {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
<div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
<div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
- PanelWidth={() => this.sidebarWidth}
+ PanelWidth={this.sidebarWidth}
annotationsKey={this.annotationKey}
isAnnotationOverlay={false}
focus={this.props.focus}
@@ -1118,24 +1159,24 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
whenActiveChanged={this.whenActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
- addDocument={(doc: Doc) => { doc._hideSidebar = true; return this.addDocument(doc); }}
+ addDocument={this.addDocument}
CollectionView={undefined}
- ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
renderDepth={this.props.renderDepth + 1}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- chromeCollapsed={true}>
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
</CollectionFreeFormView>
<div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
</div>}
- <div className="formattedTextBox-dictation"
- onClick={e => {
- this._recording ? this.stopDictation(true) : this.recordDictation();
- setTimeout(() => this._editorView!.focus(), 500);
- e.stopPropagation();
- }} >
- <FontAwesomeIcon className="formattedTExtBox-audioFont"
- style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
- </div>
+ {!this.props.Document._showAudio ? (null) :
+ <div className="formattedTextBox-dictation"
+ onClick={e => {
+ this._recording ? this.stopDictation(true) : this.recordDictation();
+ setTimeout(() => this._editorView!.focus(), 500);
+ e.stopPropagation();
+ }} >
+ <FontAwesomeIcon className="formattedTExtBox-audioFont"
+ style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
+ </div>}
</div>
);
}