diff options
| author | ljungster <parkerljung@gmail.com> | 2022-08-09 11:52:07 -0500 | 
|---|---|---|
| committer | ljungster <parkerljung@gmail.com> | 2022-08-09 11:52:07 -0500 | 
| commit | da3cb00f809a482a9fdf732f6a656fbc467cce27 (patch) | |
| tree | 9eb1fd278bc71d080d71bbfb7e3aec482d35f439 /src/client/views/nodes/formattedText/FormattedTextBox.tsx | |
| parent | 1638527259a072dfc2ab286bd27bbb1751e8434e (diff) | |
| parent | 26670c8b9eb6e2fd981c3a0997bff5556b60504b (diff) | |
Merge branch 'parker' of https://github.com/brown-dash/Dash-Web into parker
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 1305 | 
1 files changed, 769 insertions, 536 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 6192b6829..a018f51c2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,89 +1,91 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core';  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { isEqual } from "lodash"; -import { action, computed, IReactionDisposer, reaction, runInAction, observable, trace } from "mobx"; -import { observer } from "mobx-react"; -import { baseKeymap, selectAll } from "prosemirror-commands"; -import { history } from "prosemirror-history"; +import { isEqual } from 'lodash'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { baseKeymap, selectAll } from 'prosemirror-commands'; +import { history } from 'prosemirror-history';  import { inputRules } from 'prosemirror-inputrules'; -import { keymap } from "prosemirror-keymap"; -import { Fragment, Mark, Node, Slice } from "prosemirror-model"; -import { ReplaceStep } from 'prosemirror-transform'; -import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; +import { keymap } from 'prosemirror-keymap'; +import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; +import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view';  import { DateField } from '../../../../fields/DateField'; -import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc"; +import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';  import { Id } from '../../../../fields/FieldSymbols';  import { InkTool } from '../../../../fields/InkField';  import { PrefetchProxy } from '../../../../fields/Proxy'; -import { RichTextField } from "../../../../fields/RichTextField"; +import { RichTextField } from '../../../../fields/RichTextField';  import { RichTextUtils } from '../../../../fields/RichTextUtils'; -import { Cast, DateCast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { ComputedField } from '../../../../fields/ScriptField'; +import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';  import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';  import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; -import { DocServer } from "../../../DocServer"; +import { DocServer } from '../../../DocServer';  import { Docs, DocUtils } from '../../../documents/Documents';  import { DocumentType } from '../../../documents/DocumentTypes'; -import { CurrentUserUtils } from '../../../util/CurrentUserUtils';  import { DictationManager } from '../../../util/DictationManager';  import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager } from "../../../util/DragManager"; -import { makeTemplate } from '../../../util/DropConverter'; -import { SelectionManager } from "../../../util/SelectionManager"; +import { DragManager } from '../../../util/DragManager'; +import { MakeTemplate } from '../../../util/DropConverter'; +import { LinkManager } from '../../../util/LinkManager'; +import { SelectionManager } from '../../../util/SelectionManager';  import { SnappingManager } from '../../../util/SnappingManager'; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; +import { undoBatch, UndoManager } from '../../../util/UndoManager';  import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';  import { CollectionStackingView } from '../../collections/CollectionStackingView';  import { ContextMenu } from '../../ContextMenu';  import { ContextMenuProps } from '../../ContextMenuItem'; -import { ViewBoxAnnotatableComponent } from "../../DocComponent"; +import { ViewBoxAnnotatableComponent } from '../../DocComponent';  import { DocumentButtonBar } from '../../DocumentButtonBar'; +import { Colors } from '../../global/globalEnums';  import { LightboxView } from '../../LightboxView';  import { AnchorMenu } from '../../pdf/AnchorMenu'; +import { SidebarAnnos } from '../../SidebarAnnos';  import { StyleProp } from '../../StyleProvider'; -import { AudioBox } from '../AudioBox'; -import { FieldView, FieldViewProps } from "../FieldView"; +import { FieldView, FieldViewProps } from '../FieldView';  import { LinkDocPreview } from '../LinkDocPreview'; -import { DashDocCommentView } from "./DashDocCommentView"; -import { DashDocView } from "./DashDocView"; -import { DashFieldView } from "./DashFieldView"; -import { EquationView } from "./EquationView"; -import { FootnoteView } from "./FootnoteView"; -import "./FormattedTextBox.scss"; +import { DashDocCommentView } from './DashDocCommentView'; +import { DashDocView } from './DashDocView'; +import { DashFieldView } from './DashFieldView'; +import { EquationView } from './EquationView'; +import { FootnoteView } from './FootnoteView'; +import './FormattedTextBox.scss';  import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment'; -import { OrderedListView } from "./OrderedListView"; -import { buildKeymap, updateBullets } from "./ProsemirrorExampleTransfer"; -import { removeMarkWithAttrs } from "./prosemirrorPatches"; +import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer'; +import { removeMarkWithAttrs } from './prosemirrorPatches';  import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; -import { RichTextRules } from "./RichTextRules"; -import { schema } from "./schema_rts"; -import { SummaryView } from "./SummaryView"; -import applyDevTools = require("prosemirror-dev-tools"); -import React = require("react"); -import { SidebarAnnos } from '../../SidebarAnnos'; -import { Colors } from '../../global/globalEnums'; -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -const translateGoogleApi = require("translate-google-api"); +import { RichTextRules } from './RichTextRules'; +import { schema } from './schema_rts'; +import { SummaryView } from './SummaryView'; +import applyDevTools = require('prosemirror-dev-tools'); +import React = require('react'); +import { text } from 'body-parser'; +import { CollectionTreeView } from '../../collections/CollectionTreeView'; +const translateGoogleApi = require('translate-google-api');  export interface FormattedTextBoxProps { -    makeLink?: () => Opt<Doc>;  // bcz: hack: notifies the text document when the container has made a link.  allows the text doc to react and setup a hyeprlink for any selected text -    xPadding?: number;   // used to override document's settings for xMargin --- see CollectionCarouselView +    makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link.  allows the text doc to react and setup a hyeprlink for any selected text +    xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView      yPadding?: number;      noSidebar?: boolean;      dontScale?: boolean;      dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)  } -export const GoogleRef = "googleDocId"; +export const GoogleRef = 'googleDocId';  type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;  @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() { -    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } +export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() { +    public static LayoutString(fieldStr: string) { +        return FieldView.LayoutString(FormattedTextBox, fieldStr); +    }      public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);      public static Instance: FormattedTextBox;      public static LiveTextUndo: UndoManager.Batch | undefined; -    static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; +    static _globalHighlights: 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(); @@ -93,7 +95,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      private _ref: React.RefObject<HTMLDivElement> = React.createRef();      private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();      private _editorView: Opt<EditorView>; -    private _applyingChange: string = ""; +    private _applyingChange: string = '';      private _searchIndex = 0;      private _lastTimedMark: Mark | undefined = undefined;      private _cachedLinks: Doc[] = []; @@ -102,7 +104,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      private _dropDisposer?: DragManager.DragDropDisposer;      private _recordingStart: number = 0;      private _ignoreScroll = false; -    private _lastText = ""; +    private _lastText = '';      private _focusSpeed: Opt<number>;      private _keymap: any = undefined;      private _rules: RichTextRules | undefined; @@ -113,21 +115,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      private _downY = 0;      private _break = true;      public ProseRef?: HTMLDivElement; -    public get EditorView() { return this._editorView; } -    public get SidebarKey() { return this.fieldKey + "-sidebar"; } -    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } - -    @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } -    @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } -    @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; } -    @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); } -    @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); } -    @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); } -    @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } -    @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); } -    @computed get _recording() { return this.dataDoc?.mediaState === "recording"; } +    public get EditorView() { +        return this._editorView; +    } +    public get SidebarKey() { +        return this.fieldKey + '-sidebar'; +    } +    @computed get allSidebarDocs() { +        return DocListCast(this.dataDoc[this.SidebarKey]); +    } + +    @computed get noSidebar() { +        return this.props.docViewPath?.()[this.props.docViewPath().length - 2]?.rootDoc.type === DocumentType.RTF || this.props.noSidebar || this.Document._noSidebar; +    } +    @computed get sidebarWidthPercent() { +        return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); +    } +    @computed get sidebarColor() { +        return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4')); +    } +    @computed get autoHeight() { +        return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; +    } +    @computed get textHeight() { +        return NumCast(this.rootDoc[this.fieldKey + '-height']); +    } +    @computed get scrollHeight() { +        return NumCast(this.rootDoc[this.fieldKey + '-scrollHeight']); +    } +    @computed get sidebarHeight() { +        return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '-height']); +    } +    @computed get titleHeight() { +        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; +    } +    @computed get autoHeightMargins() { +        return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); +    } +    @computed get _recording() { +        return this.dataDoc?.mediaState === 'recording'; +    }      set _recording(value) { -        !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined); +        !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined);      }      @computed get config() {          this._keymap = buildKeymap(schema, this.props); @@ -140,28 +169,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  history(),                  keymap(this._keymap),                  keymap(baseKeymap), -                new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }), -                new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) -            ] +                new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }), +                new Plugin({ +                    view(editorView) { +                        return new FormattedTextBoxComment(editorView); +                    }, +                }), +            ],          };      }      public static PasteOnLoad: ClipboardEvent | undefined; -    public static SelectOnLoad = ""; +    public static SelectOnLoad = '';      public static DontSelectInitialText = false; // whether initial text should be selected or not -    public static SelectOnLoadChar = ""; -    public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } +    public static SelectOnLoadChar = ''; +    public static IsFragment(html: string) { +        return html.indexOf('data-pm-slice') !== -1; +    }      public static GetHref(html: string): string {          const parser = new DOMParser();          const 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) { +        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 ""; +        return '';      }      public static GetDocFromUrl(url: string) { -        return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid +        return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid      }      constructor(props: any) { @@ -172,7 +206,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      }      // removes all hyperlink anchors for the removed linkDoc -    // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.  +    // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.      // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.      public RemoveLinkFromDoc(linkDoc?: Doc) {          this.unhighlightSearchTerms(); @@ -195,9 +229,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              }          }      } -    // removes all the specified link references from the selection.  +    // removes all the specified link references from the selection.      // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references. -    public RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) { +    public RemoveAnchorFromSelection(allAnchors: { href: string; title: string; linkId: string; targetId: string }[]) {          const state = this._editorView?.state;          if (state && this._editorView) {              this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors })); @@ -205,11 +239,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          }      } -    getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection"); +    getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection');      @action      setupAnchorMenu = () => { -        AnchorMenu.Instance.Status = "marquee"; +        AnchorMenu.Instance.Status = 'marquee';          AnchorMenu.Instance.OnClick = (e: PointerEvent) => {              !this.layoutDoc.showSidebar && this.toggleSidebar(); @@ -220,15 +254,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              return undefined;          });          AnchorMenu.Instance.onMakeAnchor = this.getAnchor; +        AnchorMenu.Instance.StartCropDrag = unimplementedFunction;          /** -         * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.   +         * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.           * It also initiates a Drag/Drop interaction to place the text annotation.           */          AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {              e.preventDefault();              e.stopPropagation();              const targetCreator = (annotationOn?: Doc) => { -                const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); +                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);                  FormattedTextBox.SelectOnLoad = target[Id];                  return target;              }; @@ -237,56 +272,56 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          });          const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);          this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); -    } +    };      dispatchTransaction = (tx: Transaction) => {          if (this._editorView) {              const state = this._editorView.state.apply(tx);              this._editorView.updateState(state); -            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 * 1000))); -            const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); -            const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined;               // the actual text in the text box -            const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype +            const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); +            const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box +            const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype              const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template              const json = JSON.stringify(state.toJSON());              const effectiveAcl = GetEffectiveAcl(this.dataDoc); -            const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ? -                json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); +            const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); -            if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || effectiveAcl === AclSelfEdit) { +            if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {                  const accumTags = [] as string[];                  state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => { -                    if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) { +                    if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {                          accumTags.push(node.attrs.fieldKey);                      }                  }); -                const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#")); +                const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith('#'));                  const added = accumTags.filter(tag => !curTags.includes(tag));                  const removed = curTags.filter(tag => !accumTags.includes(tag)); -                removed.forEach(r => this.dataDoc[r] = undefined); -                added.forEach(a => this.dataDoc[a] = a); +                removed.forEach(r => (this.dataDoc[r] = undefined)); +                added.forEach(a => (this.dataDoc[a] = a));                  let unchanged = true;                  if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {                      this._applyingChange = this.fieldKey; -                    (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); -                    if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) +                    curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text && (this.dataDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()))); +                    if ((!curTemp && !curProto) || curText || json.includes('dash')) { +                        // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)                          if (removeSelection(json) !== removeSelection(curLayout?.Data)) {                              this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); -                            this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited +                            this.dataDoc[this.props.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited                              ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });                              unchanged = false;                          } -                    } else { // if we've deleted all the text in a note driven by a template, then restore the template data +                    } else { +                        // if we've deleted all the text in a note driven by a template, then restore the template data                          this.dataDoc[this.props.fieldKey] = undefined;                          this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); -                        this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have +                        this.dataDoc[this.props.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have +                        ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });                          unchanged = false;                      } -                    this._applyingChange = ""; +                    this._applyingChange = '';                      if (!unchanged) {                          this.updateTitle();                          this.tryUpdateScrollHeight(); @@ -304,16 +339,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  AnchorMenu.Instance.fadeOut(true);              }          } -    } +    }; -    // for inserting timestamps  +    // for inserting timestamps      insertTime = () => {          let linkTime;          let linkAnchor;          let link;          DocListCast(this.dataDoc.links).forEach((l, i) => { -            const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; -            if (anchor && (anchor.annotationOn as Doc).mediaState === "recording") { +            const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; +            if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {                  linkTime = NumCast(anchor._timecodeToShow /* audioStart */);                  linkAnchor = anchor;                  link = l; @@ -344,40 +379,85 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));              }          } -    } +    }; + +    autoLink = () => { +        const newAutoLinks = new Set<Doc>(); +        const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords); +        if (this._editorView?.state.doc.textContent) { +            const f = this._editorView.state.selection.from; +            const t = this._editorView.state.selection.to; +            var tr = this._editorView.state.tr as any; +            const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; +            tr = tr.removeMark(0, tr.doc.content.size, autoAnch); +            DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); +            tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); +            this._editorView?.dispatch(tr); +        } +        oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); +    };      updateTitle = () => { -        if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !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.dataDoc["title-custom"] && -            (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { +        const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text); +        if ( +            !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing +            (title.startsWith('-') || title.startsWith('@')) && +            this._editorView && +            !this.dataDoc['title-custom'] && +            (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text') +        ) {              let node = this._editorView.state.doc; -            while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; +            while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;              const str = node.textContent; -            this.dataDoc.title = "-" + str.substr(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); +            const prefix = str.startsWith('@') ? '' : '-'; + +            const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title)); +            if (!(cfield instanceof ComputedField)) { +                this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : ''); +                if (str.startsWith('@') && str.length > 1) { +                    Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc); +                } +            }          } -    } +    }; -    // needs a better API for taking in a set of words with target documents instead of just one target -    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; -            const flattened1: TextSelection[] = []; -            res1.map(r => r.map(h => flattened1.push(h))); +    // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' +    hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => { +        const editorView = this._editorView; +        if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { +            const autoLinkTerm = StrCast(target.title).replace(/^@/, ''); +            const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); +            var alink: Doc | undefined;              flattened1.forEach((flat, i) => { -                const flattened: TextSelection[] = []; -                const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); -                res.map(r => r.map(h => flattened.push(h))); +                const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm);                  this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; -                const anchor = Docs.Create.TextanchorDocument(); -                const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; -                const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }]; -                const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location }); -                tr = tr.addMark(flattened[i].from, flattened[i].to, link); +                const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); +                const sel = flattened[i]; +                tr = tr.addMark(sel.from, sel.to, splitter); +                tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { +                    if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { +                        alink = +                            alink ?? +                            (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || +                                DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!); +                        newAutoLinks.add(alink); +                        const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; +                        allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); +                        const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' }); +                        tr = tr.addMark(pos, pos + node.nodeSize, link); +                    } +                }); +                tr = tr.removeMark(sel.from, sel.to, splitter);              }); -            this._editorView.dispatch(tr);          } -    } +        return tr; +    }; +    @action +    search = (searchString: string, bwd?: boolean, clear: boolean = false) => { +        if (clear) this.unhighlightSearchTerms(); +        else this.highlightSearchTerms([searchString], bwd!); +        return true; +    };      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); @@ -391,21 +471,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              if (backward === true) {                  if (this._searchIndex > 1) {                      this._searchIndex += -2; -                } -                else if (this._searchIndex === 1) { +                } else if (this._searchIndex === 1) {                      this._searchIndex = length - 1; -                } -                else if (this._searchIndex === 0 && length !== 1) { +                } else if (this._searchIndex === 0 && length !== 1) {                      this._searchIndex = length - 2;                  } -              }              const lastSel = Math.min(flattened.length - 1, this._searchIndex); -            flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); +            flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)));              flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());          } -    } +    };      unhighlightSearchTerms = () => {          if (window.screen.width < 600) null; @@ -414,20 +491,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });              const end = this._editorView.state.doc.nodeSize - 2;              this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); -          }          if (FormattedTextBox.PasteOnLoad) { -            const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin"); -            const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfRegion"); +            const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin'); +            const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion');              FormattedTextBox.PasteOnLoad = undefined;              setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10);          } -    } +    };      adoptAnnotation = (start: number, end: number, mark: Mark) => {          const view = this._editorView!;          const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });          view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); -    } +    };      protected createDropTarget = (ele: HTMLDivElement) => {          this._dropDisposer?.();          this.ProseRef = ele; @@ -435,8 +511,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              this.setupEditor(this.config, this.props.fieldKey);              this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc);          } -        // if (this.autoHeight) this.tryUpdateScrollHeight();  -    } +        // if (this.autoHeight) this.tryUpdateScrollHeight(); +    };      @undoBatch      @action @@ -454,22 +530,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  // embed document when dragg marked as embed              } else if (de.embedKey) {                  const target = dragData.droppedDocuments[0]; -                target._fitToBox = true; +                target._fitContentsToBox = true;                  const node = schema.nodes.dashDoc.create({                      width: target[WidthSym](),                      height: target[HeightSym](), -                    title: "dashDoc", +                    title: 'dashDoc',                      docid: target[Id], -                    float: "unset" +                    float: 'unset',                  });                  const view = this._editorView!;                  view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));                  e.stopPropagation();              } // otherwise, fall through to outer collection to handle drop          } -    } +    }; -    getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { +    getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null {          let offset = 0;          if (context === node) return { from: offset, to: offset + node.nodeSize }; @@ -480,8 +556,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  const result = this.getNodeEndpoints((context.content as any).content[i], node);                  if (result) {                      return { -                        from: result.from + offset + (context.type.name === "doc" ? 0 : 1), -                        to: result.to + offset + (context.type.name === "doc" ? 0 : 1) +                        from: result.from + offset + (context.type.name === 'doc' ? 0 : 1), +                        to: result.to + offset + (context.type.name === 'doc' ? 0 : 1),                      };                  }                  offset += (context.content as any).content[i].nodeSize; @@ -497,9 +573,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          let ret: TextSelection[] = [];          if (node.isTextblock) { -            let index = 0, foundAt; +            let index = 0, +                foundAt;              const ep = this.getNodeEndpoints(pm.state.doc, node); -            const regexp = new RegExp(find.replace("*", ""), "i"); +            const regexp = new RegExp(find.replace('*', ''), 'i');              if (regexp) {                  while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {                      const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); @@ -508,165 +585,247 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  }              }          } else { -            node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); +            node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));          }          return ret;      }      updateHighlights = () => { +        const highlights = FormattedTextBox._globalHighlights;          clearStyleSheetRules(FormattedTextBox._userStyleSheet); -        if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, ""); +        if (highlights.indexOf('Audio Tags') === -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, ''); +        } +        if (highlights.indexOf('Text from Others') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });          } -        if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); +        if (highlights.indexOf('My Text') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });          } -        if (FormattedTextBox._highlights.indexOf("My Text") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); +        if (highlights.indexOf('Todo Items') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'todo', { outline: 'black solid 1px' });          } -        if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" }); +        if (highlights.indexOf('Important Items') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'important', { 'font-size': 'larger' });          } -        if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" }); +        if (highlights.indexOf('Bold Text') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, ''); +            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');          } -        if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" }); +        if (highlights.indexOf('Disagree Items') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'disagree', { 'text-decoration': 'line-through' });          } -        if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" }); +        if (highlights.indexOf('Ignore Items') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'ignore', { 'font-size': '1' });          } -        if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); +        if (highlights.indexOf('By Recent Minute') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });              const min = Math.round(Date.now() / 1000 / 60); -            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); +            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));              setTimeout(this.updateHighlights);          } -        if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); +        if (highlights.indexOf('By Recent Hour') !== -1) { +            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });              const hr = Math.round(Date.now() / 1000 / 60 / 60); -            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); +            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));          } -    } +    };      @observable _showSidebar = false; -    @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } +    @computed get SidebarShown() { +        return this._showSidebar || this.layoutDoc._showSidebar ? true : false; +    }      @action      toggleSidebar = (preview: boolean = false) => { -        const prevWidth = this.sidebarWidth(); +        const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));          if (preview) this._showSidebar = true; -        else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%"; +        else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; -        this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); -    } +        this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth); +    };      sidebarDown = (e: React.PointerEvent) => { -        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); -    } +        const batch = UndoManager.StartBatch('sidebar'); +        setupMoveUpEvents( +            this, +            e, +            this.sidebarMove, +            (e, movement, isClick) => !isClick && batch.end(), +            () => { +                this.toggleSidebar(); +                batch.end(); +            }, +            true +        ); +    };      sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { -        const bounds = this._ref.current!.getBoundingClientRect(); -        this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; -        this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; +        const localDelta = this.props +            .ScreenToLocalTransform() +            .scale(this.props.NativeDimScaling?.() || 1) +            .transformDirection(delta[0], delta[1]); +        const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100; +        const width = this.layoutDoc[WidthSym]() + localDelta[0]; +        this.layoutDoc._sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%'; +        this.layoutDoc.width = width; +        this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%';          e.preventDefault();          return false; -    } +    }; + +    @undoBatch +    deleteAnnotation = (anchor: Doc) => { +        LinkManager.Instance.deleteLink(DocListCast(anchor.links)[0]); +        // const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]); +        // this.props.dataDoc[this.props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion)); +        // AnchorMenu.Instance.fadeOut(true); +        this.props.select(false); +    }; + +    @undoBatch +    pinToPres = (anchor: Doc) => this.props.pinToPres(anchor); + +    @undoBatch +    makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin); + +    isPushpin = (anchor: Doc) => BoolCast(anchor.isPushpin);      specificContextMenu = (e: React.MouseEvent): void => {          const cm = ContextMenu.Instance; +        const editor = this._editorView!; +        const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); +        let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> +        while (target && !target.dataset?.targethrefs) target = target.parentElement; +        if (target) { +            const hrefs = (target.dataset?.targethrefs as string) +                ?.trim() +                .split(' ') +                .filter(h => h); +            const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0]; +            e.persist(); +            anchorDoc && +                DocServer.GetRefField(anchorDoc).then( +                    action(anchor => { +                        AnchorMenu.Instance.Status = 'annotation'; +                        AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc); +                        AnchorMenu.Instance.Pinned = false; +                        AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc); +                        AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc); +                        AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc); +                        AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); +                    }) +                ); +            e.stopPropagation(); +            return; +        } +          const changeItems: ContextMenuProps[] = [];          changeItems.push({ -            description: "plain", event: undoBatch(() => { +            description: 'plain', +            event: undoBatch(() => {                  Doc.setNativeView(this.rootDoc);                  this.layoutDoc.autoHeightMargins = undefined; -            }), icon: "eye" +            }), +            icon: 'eye',          });          changeItems.push({ -            description: "metadata", event: undoBatch(() => { +            description: 'metadata', +            event: undoBatch(() => {                  this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout; -                this.rootDoc.layoutKey = "layout_meta"; -                setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50); -            }), icon: "eye" +                this.rootDoc.layoutKey = 'layout_meta'; +                setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50), 50); +            }), +            icon: 'eye',          }); -        const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); +        const noteTypesDoc = Cast(Doc.UserDoc()['template-notes'], Doc, null);          DocListCast(noteTypesDoc?.data).forEach(note => {              const icon: IconProp = StrCast(note.icon) as IconProp;              changeItems.push({ -                description: StrCast(note.title), event: undoBatch(() => { +                description: StrCast(note.title), +                event: undoBatch(() => {                      this.layoutDoc.autoHeightMargins = undefined;                      Doc.setNativeView(this.rootDoc);                      DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); -                }), icon: icon +                }), +                icon: icon,              });          }); -        !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });          const highlighting: ContextMenuProps[] = []; -        const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others"]; -        const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; -        (Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => +        const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text']; +        const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour']; +        (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>              highlighting.push({ -                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { +                description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option, +                event: () => {                      e.stopPropagation(); -                    if (FormattedTextBox._highlights.indexOf(option) === -1) { -                        FormattedTextBox._highlights.push(option); +                    if (FormattedTextBox._globalHighlights.indexOf(option) === -1) { +                        FormattedTextBox._globalHighlights.push(option);                      } else { -                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); +                        FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);                      } +                    runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join('')));                      this.updateHighlights(); -                }, icon: "expand-arrows-alt" -            })); +                }, +                icon: 'expand-arrows-alt', +            }) +        );          const uicontrols: ContextMenuProps[] = []; -        !Doc.UserDoc().noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, 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" }); -        !Doc.UserDoc().noviceMode && uicontrols.push({ -            description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => -                proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" -        }); -        cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); +        !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, 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' }); +        !Doc.noviceMode && +            uicontrols.push({ +                description: 'Broadcast Message', +                event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), +                icon: 'expand-arrows-alt', +            }); +        cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' }); -        const appearance = cm.findByDescription("Appearance..."); -        const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; -        appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); +        const appearance = cm.findByDescription('Appearance...'); +        const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; +        appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });          // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); -        !Doc.UserDoc().noviceMode && appearanceItems.push({ -            description: "Make Default Layout", event: () => { -                if (!this.layoutDoc.isTemplateDoc) { -                    const title = StrCast(this.rootDoc.title); -                    this.rootDoc.title = "text"; -                    this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); -                } else if (!this.rootDoc.isTemplateDoc) { -                    const title = StrCast(this.rootDoc.title); -                    this.rootDoc.title = "text"; -                    this.rootDoc.layout = this.layoutDoc.layout as string; -                    this.rootDoc.title = this.layoutDoc.isTemplateForField as string; -                    this.rootDoc.isTemplateDoc = false; -                    this.rootDoc.isTemplateForField = ""; -                    this.rootDoc.layoutKey = "layout"; -                    this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); -                    setTimeout(() => { -                        this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height -                        this.rootDoc._width = this.layoutDoc._width || 300;  // are stored on the template, since we're getting rid of the old template -                        this.rootDoc._height = this.layoutDoc._height || 200;  // we need to copy them over to the root.  This should probably apply to all '_' fields -                        this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); -                        this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null); -                    }, 10); -                } -                Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc); -                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); -            }, icon: "eye" -        }); -        cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); +        !Doc.noviceMode && +            appearanceItems.push({ +                description: 'Make Default Layout', +                event: () => { +                    if (!this.layoutDoc.isTemplateDoc) { +                        const title = StrCast(this.rootDoc.title); +                        this.rootDoc.title = 'text'; +                        MakeTemplate(this.rootDoc, true, title); +                    } else if (!this.rootDoc.isTemplateDoc) { +                        const title = StrCast(this.rootDoc.title); +                        this.rootDoc.title = 'text'; +                        this.rootDoc.layout = this.layoutDoc.layout as string; +                        this.rootDoc.title = this.layoutDoc.isTemplateForField as string; +                        this.rootDoc.isTemplateDoc = false; +                        this.rootDoc.isTemplateForField = ''; +                        this.rootDoc.layoutKey = 'layout'; +                        MakeTemplate(this.rootDoc, true, title); +                        setTimeout(() => { +                            this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height +                            this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template +                            this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root.  This should probably apply to all '_' fields +                            this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null); +                            this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, 'string', null); +                        }, 10); +                    } +                    Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc); +                    Doc.AddDocToList(Cast(Doc.UserDoc()['template-notes'], Doc, null), 'data', this.rootDoc); +                }, +                icon: 'eye', +            }); +        cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); -        const options = cm.findByDescription("Options..."); -        const optionItems = options && "subitems" in options ? options.subitems : []; -        optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); -        optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); -        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); +        const options = cm.findByDescription('Options...'); +        const optionItems = options && 'subitems' in options ? options.subitems : []; +        optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' }); +        optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); +        !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });          this._downX = this._downY = Number.NaN; -    } +    };      breakupDictation = () => {          if (this._editorView && this._recording) { @@ -675,12 +834,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              const state = this._editorView.state;              const to = state.selection.to;              const updated = TextSelection.create(state.doc, to, to); -            this._editorView.dispatch(state.tr.setSelection(updated).insertText("\n", to)); +            this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));              if (this._recording) {                  this.recordDictation();              }          } -    } +    };      recordDictation = () => {          DictationManager.Controls.listen({              interimHandler: this.setDictationContent, @@ -690,26 +849,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  DictationManager.Controls.stop();              }          }); -    } +    };      stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);      setDictationContent = (value: string) => {          if (this._editorView && this._recordingStart) {              if (this._break) { -                const textanchor = Docs.Create.TextanchorDocument({ title: "dictation anchor" }); +                const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });                  this.addDocument(textanchor);                  const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement();                  link && (Doc.GetProto(link).isDictation = true);                  if (!link) return;                  const audioanchor = Cast(link.anchor2, Doc, null);                  if (!audioanchor) return; -                audioanchor.backgroundColor = "tan"; +                audioanchor.backgroundColor = 'tan';                  const audiotag = this._editorView.state.schema.nodes.audiotag.create({                      timeCode: NumCast(audioanchor._timecodeToShow),                      audioId: audioanchor[Id], -                    textId: textanchor[Id] +                    textId: textanchor[Id],                  }); -                Doc.GetProto(textanchor).title = "dictation:" + audiotag.attrs.timeCode; +                Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;                  const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);                  const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));                  this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size))); @@ -719,8 +878,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              const tr = this._editorView.state.tr.insertText(value);              this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());          } -    } +    }; +    // TODO: nda -- Look at how link anchors are added      makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {          const state = this._editorView?.state;          if (state) { @@ -728,7 +888,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });              let tr = state.tr.addMark(sel.from, sel.to, splitter);              if (sel.from !== sel.to) { -                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true }); +                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });                  const href = targetHref ?? Doc.localServerPath(anchor);                  if (anchor !== anchorDoc) this.addDocument(anchor);                  tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { @@ -739,7 +899,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                          tr = tr.addMark(pos, pos + node.nodeSize, link);                      }                  }); -                this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true;  // need to allow permissions for adding links to readonly/augment only documents +                this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents                  this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));                  this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;                  return anchor; @@ -750,8 +910,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      }      scrollFocus = (textAnchor: Doc, smooth: boolean) => { -        if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) { +        let didToggle = false; +        if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {              this.toggleSidebar(!smooth); +            didToggle = true;          }          const textAnchorId = textAnchor[Id];          const findAnchorFrag = (frag: Fragment, editor: EditorView) => { @@ -780,10 +942,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              }              const marks = [...node.marks];              const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor); -            return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined; +            return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;          };          let start = 0; +        this._didScroll = false; // assume we don't need to scroll.  if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below          if (this._editorView && textAnchorId) {              const editor = this._editorView;              const ret = findAnchorFrag(editor.state.doc.content, editor); @@ -797,57 +960,72 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  }                  editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());                  const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId; -                addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow", transform: "scale(3)", "transform-origin": "left bottom" }); -                setTimeout(() => this._focusSpeed = undefined, this._focusSpeed); +                addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' }); +                setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);                  setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));              }          } -        return this._focusSpeed; -    } +        return this._didScroll ? this._focusSpeed : didToggle ? 1 : undefined; // if we actually scrolled, then return some focusSpeed +    }; +    getScrollHeight = () => this.scrollHeight;      // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc.      // Since we also monitor all component height changes, this will update the document's height.      resetNativeHeight = (scrollHeight: number) => {          const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); -        this.rootDoc[this.fieldKey + "-height"] = scrollHeight; +        this.rootDoc[this.fieldKey + '-height'] = scrollHeight;          if (nh) this.layoutDoc._nativeHeight = scrollHeight; -    } +    }; +    @computed get contentScaling() { +        return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.NativeDimScaling?.() || 1 : 1; +    }      componentDidMount() {          !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.          this._cachedLinks = DocListCast(this.Document.links);          this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation); -        this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight()); -        this._disposers.scrollHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), +        this._disposers.autoHeight = reaction( +            () => this.autoHeight, +            autoHeight => autoHeight && this.tryUpdateScrollHeight() +        ); +        this._disposers.scrollHeight = reaction( +            () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),              ({ width, scrollHeight, autoHeight }) => {                  width && autoHeight && this.resetNativeHeight(scrollHeight); -            }, { fireImmediately: true } +            }, +            { fireImmediately: true }          ); -        this._disposers.componentHeights = reaction(  // set the document height when one of the component heights changes and autoHeight is on +        this._disposers.componentHeights = reaction( +            // set the document height when one of the component heights changes and autoHeight is on              () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),              ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { -                autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight)); -            }, { fireImmediately: true }); -        this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks +                const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); +                if (autoHeight && newHeight && newHeight !== this.rootDoc.height) { +                    this.props.setHeight?.(newHeight); +                } +            }, +            { fireImmediately: true } +        ); +        this._disposers.links = reaction( +            () => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks              newLinks => {                  this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));                  this._cachedLinks = newLinks; -            }); +            } +        );          this._disposers.buttonBar = reaction(              () => DocumentButtonBar.Instance,              instance => {                  if (instance) {                      this.pullFromGoogleDoc(this.checkState); -                    this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true); +                    this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true));                  }              }          );          this._disposers.editorState = reaction(              () => { -                const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : -                    this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ? -                        this.dataDoc : this.layoutDoc; +                const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : this.dataDoc?.[this.props.fieldKey + '-noTemplate'] || !this.layoutDoc[this.props.fieldKey] ? this.dataDoc : this.layoutDoc;                  return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) };              },              incomingValue => { @@ -862,7 +1040,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                          selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));                      }                  } -            }, +            }          );          this._disposers.pullDoc = reaction(              () => this.props.Document[Pulls], @@ -883,33 +1061,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              }          ); -        this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc), -            search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(), -            { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }); +        this._disposers.search = reaction( +            () => Doc.IsSearchMatch(this.rootDoc), +            search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()), +            { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false } +        ); -        this._disposers.selected = reaction(() => this.props.isSelected(), +        this._disposers.selected = reaction( +            () => this.props.isSelected(),              action(selected => { +                this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : '';                  if (RichTextMenu.Instance?.view === this._editorView && !selected) {                      RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);                  }                  if (this._editorView && selected) {                      RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); +                    this.autoLink();                  } -            }), { fireImmediately: true }); +            }), +            { fireImmediately: true } +        );          if (!this.props.dontRegisterView) { -            this._disposers.record = reaction(() => this._recording, +            this._disposers.record = reaction( +                () => this._recording,                  () => {                      this.stopDictation(true);                      if (this._recording) {                          this.recordDictation();                      } -                }, +                }              );              if (this._recording) setTimeout(this.recordDictation);          } -        var quickScroll: string | undefined = ""; -        this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), +        var quickScroll: string | undefined = ''; +        this._disposers.scroll = reaction( +            () => NumCast(this.layoutDoc._scrollTop),              pos => {                  if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {                      const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); @@ -922,17 +1109,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                          this._scrollRef.current.scrollTo({ top: pos });                      }                  } -            }, { fireImmediately: true } +            }, +            { fireImmediately: true }          );          quickScroll = undefined; -        setTimeout(this.tryUpdateScrollHeight, 10); +        this.tryUpdateScrollHeight();      }      pushToGoogleDoc = async () => {          this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {              const modes = GoogleApiClientUtils.Docs.WriteMode;              let mode = modes.Replace; -            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string"); +            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], 'string');              if (!reference) {                  mode = modes.Insert;                  reference = { title: StrCast(this.dataDoc.title) }; @@ -942,7 +1130,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                      const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);                      const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });                      response && (this.dataDoc[GoogleRef] = response.documentId); -                    const pushSuccess = response !== undefined && !("errors" in response); +                    const pushSuccess = response !== undefined && !('errors' in response);                      dataDoc.googleDocUnchanged = pushSuccess;                      DocumentButtonBar.Instance.startPushOutcome(pushSuccess);                  } @@ -951,15 +1139,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  if (exportState && reference) {                      const content: GoogleApiClientUtils.Docs.Content = {                          text: exportState.text, -                        requests: [] +                        requests: [],                      };                      GoogleApiClientUtils.Docs.write({ reference, content, mode });                  }              }; -            UndoManager.AddEvent({ undo, redo, prop: "" }); +            UndoManager.AddEvent({ undo, redo, prop: '' });              redo();          }); -    } +    };      pullFromGoogleDoc = async (handler: PullHandler) => {          const dataDoc = this.dataDoc; @@ -969,7 +1157,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);          }          exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); -    } +    };      updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {          let pullSuccess = false; @@ -984,13 +1172,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  }              }, 0);              dataDoc.title = exportState.title; -            this.dataDoc["title-custom"] = true; +            this.dataDoc['title-custom'] = true;              dataDoc.googleDocUnchanged = true;          } else {              delete dataDoc[GoogleRef];          }          DocumentButtonBar.Instance.startPullOutcome(pullSuccess); -    } +    };      checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {          if (exportState && this._editorView) { @@ -1000,56 +1188,61 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              dataDoc.googleDocUnchanged = unchanged;              DocumentButtonBar.Instance.setPullState(unchanged);          } -    } +    };      clipboardTextSerializer = (slice: Slice): string => { -        let text = "", separated = true; -        const from = 0, to = slice.content.size; -        slice.content.nodesBetween(from, to, (node, pos) => { -            if (node.isText) { -                text += node.text!.slice(Math.max(from, pos) - pos, to - pos); -                separated = false; -            } else if (!separated && node.isBlock) { -                text += "\n"; -                separated = true; -            } else if (node.type.name === "hard_break") { -                text += "\n"; -            } -        }, 0); +        let text = '', +            separated = true; +        const from = 0, +            to = slice.content.size; +        slice.content.nodesBetween( +            from, +            to, +            (node, pos) => { +                if (node.isText) { +                    text += node.text!.slice(Math.max(from, pos) - pos, to - pos); +                    separated = false; +                } else if (!separated && node.isBlock) { +                    text += '\n'; +                    separated = true; +                } else if (node.type.name === 'hard_break') { +                    text += '\n'; +                } +            }, +            0 +        );          return text; -    } +    };      handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {          const cbe = event as ClipboardEvent; -        const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin"); -        const pdfRegionId = cbe.clipboardData?.getData("dash/pdfRegion"); +        const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin'); +        const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion');          return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false; -    } +    };      addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => {          const view = this._editorView!;          if (pdfDocId && pdfRegionId) {              DocServer.GetRefField(pdfDocId).then(pdfDoc => {                  DocServer.GetRefField(pdfRegionId).then(pdfRegion => { -                    if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { +                    if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) {                          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 +                            const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations                              if (targetAnnotations) targetAnnotations.push(pdfRegion); -                            else Doc.AddDocToList(pdfDoc[DataSym], targetField + "-annotations", pdfRegion); +                            else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion);                          }); -                        const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted"); +                        const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted');                          if (link) {                              const linkId = link[Id]; -                            const quote = view.state.schema.nodes.blockquote.create(); -                            quote.content = addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); +                            const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) });                              const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0);                              if (slice) { -                                view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); +                                view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));                              } else {                                  selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView())); -                              }                          }                      } @@ -1059,31 +1252,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          }          return false; -          function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {              const nodes: Node[] = [];              frag.forEach(node => nodes.push(marker(node)));              return Fragment.fromArray(nodes);          } -          function addLinkMark(node: Node, title: string, linkId: string) {              if (!node.isText) {                  const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));                  return node.copy(content);              }              const marks = [...node.marks]; -            const linkIndex = marks.findIndex(mark => mark.type.name === "link"); +            const linkIndex = marks.findIndex(mark => mark.type.name === 'link');              const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }]; -            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true }); +            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true });              marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);              return node.mark(marks);          } -    } +    };      isActiveTab(el: Element | null | undefined) {          while (el && el !== document.body) { -            if (getComputedStyle(el).display === "none") return false; +            if (getComputedStyle(el).display === 'none') return false;              el = el.parentNode as any;          }          return true; @@ -1095,9 +1286,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              view(newView) {                  runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));                  return new RichTextMenuPlugin({ editorProps: this.props }); -            } +            },          });      } +    _didScroll = false;      setupEditor(config: any, fieldKey: string) {          const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.props.fieldKey]);          const rtfField = Cast((!curText && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField); @@ -1106,13 +1298,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              this._editorView?.destroy();              this._editorView = new EditorView(this.ProseRef, {                  state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), -                handleScrollToSelection: (editorView) => { +                handleScrollToSelection: editorView => {                      const docPos = editorView.coordsAtPos(editorView.state.selection.to);                      const viewRect = self._ref.current!.getBoundingClientRect();                      const scrollRef = self._scrollRef.current;                      const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;                      const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined; -                    if ((topOff || botOff) && scrollRef) { +                    if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {                          const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);                          const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;                          if (this._focusSpeed !== undefined) { @@ -1120,18 +1312,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                          } else {                              scrollRef.scrollTo({ top: scrollPos });                          } +                        this._didScroll = true;                      }                      return true;                  },                  dispatchTransaction: this.dispatchTransaction,                  nodeViews: { -                    dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, -                    dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, -                    dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, -                    equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, -                    summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, -                    ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, -                    footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); } +                    dashComment(node: any, view: any, getPos: any) { +                        return new DashDocCommentView(node, view, getPos); +                    }, +                    dashDoc(node: any, view: any, getPos: any) { +                        return new DashDocView(node, view, getPos, self); +                    }, +                    dashField(node: any, view: any, getPos: any) { +                        return new DashFieldView(node, view, getPos, self); +                    }, +                    equation(node: any, view: any, getPos: any) { +                        return new EquationView(node, view, getPos, self); +                    }, +                    summary(node: any, view: any, getPos: any) { +                        return new SummaryView(node, view, getPos); +                    }, +                    //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, +                    footnote(node: any, view: any, getPos: any) { +                        return new FootnoteView(node, view, getPos); +                    },                  },                  clipboardTextSerializer: this.clipboardTextSerializer,                  handlePaste: this.handlePaste, @@ -1142,9 +1347,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  if (startupText) {                      dispatch(state.tr.insertText(startupText));                  } -                const textAlign = StrCast(this.dataDoc["text-align"], StrCast(Doc.UserDoc().textAlign, "left")); -                if (textAlign !== "left") { -                    selectAll(this._editorView.state, (tr) => { +                const textAlign = StrCast(this.dataDoc['text-align'], StrCast(Doc.UserDoc().textAlign, 'left')); +                if (textAlign !== 'left') { +                    selectAll(this._editorView.state, tr => {                          this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign })));                      });                  } @@ -1154,33 +1359,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));          if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { -            FormattedTextBox.SelectOnLoad = ""; +            const selLoadChar = FormattedTextBox.SelectOnLoadChar; +            FormattedTextBox.SelectOnLoad = '';              this.props.select(false); -            if (FormattedTextBox.SelectOnLoadChar && this._editorView) { +            if (selLoadChar && this._editorView) {                  const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;                  const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });                  const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];                  const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; -                const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks); +                const tr = this._editorView.state.tr +                    .setStoredMarks(storedMarks) +                    .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size) +                    .setStoredMarks(storedMarks);                  this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); -                FormattedTextBox.SelectOnLoadChar = ""; -            } else if (curText && !FormattedTextBox.DontSelectInitialText) { -                selectAll(this._editorView!.state, this._editorView?.dispatch); +            } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { +                selectAll(this._editorView.state, this._editorView?.dispatch);                  this.startUndoTypingBatch(); +            } else if (this._editorView) { +                this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));              }              FormattedTextBox.DontSelectInitialText = false;          }          selectOnLoad && 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.          if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) { -            this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), -            schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), -            ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), -            ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []), -            ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []), -            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), -            ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), -            ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])]; +            this._editorView.state.storedMarks = [ +                ...(this._editorView.state.storedMarks ?? []), +                schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), +                ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), +                ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), +                ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), +                ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), +                ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), +                ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), +            ];          }      } @@ -1191,12 +1403,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          this.unhighlightSearchTerms();          this._editorView?.destroy();          RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); -        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); +        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none');      }      onPointerDown = (e: React.PointerEvent): void => { +        if ((e.nativeEvent as any).handledByInnerReactInstance) { +            return e.stopPropagation(); +        } else (e.nativeEvent as any).handledByInnerReactInstance = true; + +        if (this.Document.forceActive) e.stopPropagation();          this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights.  so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view. -        if ((e.target as any).tagName === "AUDIOTAG") { +        if ((e.target as any).tagName === 'AUDIOTAG') {              e.preventDefault();              e.stopPropagation();              const timecode = Number((e.target as any)?.dataset?.timecode); @@ -1207,10 +1424,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                      const func = () => {                          const docView = DocumentManager.Instance.getDocumentView(audiodoc);                          if (!docView) { -                            this.props.addDocTab(audiodoc, "add:bottom"); +                            this.props.addDocTab(audiodoc, 'add:bottom');                              setTimeout(func); -                        } -                        else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that +                        } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that                      };                      func();                  } @@ -1226,50 +1442,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          this._downEvent = true;          FormattedTextBoxComment.textBox = this;          if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { -            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar +            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { +                // stop propagation if not in sidebar                  // bcz: Change. drag selecting requires that preventDefault is NOT called.  This used to happen in DocumentView,                  //      but that's changed, so this shouldn't be needed.                  //e.stopPropagation();  // if the text box is selected, then it consumes all down events -                document.addEventListener("pointerup", this.onSelectEnd); -                document.addEventListener("pointermove", this.onSelectMove); +                document.addEventListener('pointerup', this.onSelectEnd); +                document.addEventListener('pointermove', this.onSelectMove);              }          }          if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {              e.preventDefault();          } -    } +    };      onSelectMove = (e: PointerEvent) => e.stopPropagation();      onSelectEnd = (e: PointerEvent) => { -        document.removeEventListener("pointerup", this.onSelectEnd); -        document.removeEventListener("pointermove", this.onSelectMove); -    } +        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._downEvent) return;          this._downEvent = false; -        if ((e.nativeEvent as any).formattedHandled) { -            console.log("handled"); -        } -        if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) { +        if (this.props.isContentActive(true)) {              const editor = this._editorView!;              const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });              !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));              let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>              while (target && !target.dataset?.targethrefs) target = target.parentElement; -            FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs); +            FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc);          } -        (e.nativeEvent as any).formattedHandled = true; -        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { +        if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {              e.stopPropagation();          } -    } +    };      @action      onDoubleClick = (e: React.MouseEvent): void => {          FormattedTextBoxComment.textBox = this;          if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { -            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar -                e.stopPropagation();  // if the text box is selected, then it consumes all click events +            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { +                // stop propagation if not in sidebar +                e.stopPropagation(); // if the text box is selected, then it consumes all click events              }          }          if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -1277,31 +1491,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          }          FormattedTextBoxComment.Hide(); -        (e.nativeEvent as any).formattedHandled = true; -          if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {              e.stopPropagation();          } -    } +    };      setFocus = () => {          const pos = this._editorView?.state.selection.$from.pos || 1;          (this.ProseRef?.children?.[0] as any).focus();          setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); -    } +    };      @action      onFocused = (e: React.FocusEvent): void => {          //applyDevTools.applyDevTools(this._editorView);          FormattedTextBox.Focused = this; -        this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); -    } +        this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); +    };      @observable public static Focused: FormattedTextBox | undefined;      onClick = (e: React.MouseEvent): void => { +        if ((e.nativeEvent as any).handledByInnerReactInstance) { +            e.stopPropagation(); +            return; +        }          if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {              this._forceDownNode = undefined;              return;          } -        if (!this._forceUncollapse || (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. +        if (!this._forceUncollapse || (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) { @@ -1311,29 +1528,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              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                  const boundsRect = lastNode?.getBoundingClientRect(); -                if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && -                    e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document +                if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && e.clientY > boundsRect.bottom) { +                    // if we clicked below the last prosemirror div, then set the selection to be the end of the document                      this._editorView?.focus();                      this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));                  } -            } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) && -                node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) { +            } else if (node && [this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node.type) && node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {                  this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));              }          } -        if ((e.nativeEvent as any).formattedHandled) { -            e.stopPropagation(); -            return; -        } -        if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events -            (e.nativeEvent as any).formattedHandled = true; +        if (this.props.isSelected(true)) { +            // if text box is selected, then it consumes all click events +            (e.nativeEvent as any).handledByInnerReactInstance = true;              if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox.  Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.              // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)              this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);          }          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; @@ -1356,39 +1569,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);              if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {                  if (!highlightOnly) { -                    if (selectOrderedList || (!collapse && listNode.attrs.visibility)) { +                    if (selectOrderedList) {                          this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!))); -                    } else if (!listNode.attrs.visibility || downNode === listNode) { +                    } else {                          const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });                          this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos)));                      }                  } -                addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); +                addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' });              }          }      } -    onMouseUp = (e: React.MouseEvent): void => { -        e.stopPropagation(); - -        const view = this._editorView as any; -        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there -        // are nested prosemirrors.  We only want the lowest level prosemirror to be invoked. -        if (view.mouseDown) { -            const originalUpHandler = view.mouseDown.up; -            view.root.removeEventListener("mouseup", originalUpHandler); -            view.mouseDown.up = (e: MouseEvent) => { -                if (!(e as any).formattedHandled) { -                    originalUpHandler(e); -                    (e as any).formattedHandled = true; -                } else { -                    console.log("prehandled"); -                } -            }; -            view.root.addEventListener("mouseup", view.mouseDown.up); -        } -    }      startUndoTypingBatch() { -        !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); +        !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping'));      }      public endUndoTypingBatch() {          const wasUndoing = this._undoTyping; @@ -1399,40 +1592,53 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      @action      onBlur = (e: any) => { +        if (this.ProseRef?.children[0] !== e.nativeEvent.target) return; +        this.autoLink();          FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);          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();          FormattedTextBox.LiveTextUndo = undefined;          const state = this._editorView!.state; -        const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); -        if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) { +        const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); +        if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {              try { -                translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => { -                    setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => { -                        this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0]; -                    }), 1000); +                translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => { +                    setTimeout( +                        () => +                            translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => { +                                this.dataDoc[this.fieldKey + '-translation'] = result1 + '\r\n\r\n' + result[0]; +                            }), +                        1000 +                    );                  }); -            } catch (e: any) { console.log(e.message); } +            } catch (e: any) { +                console.log(e.message); +            }              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))) { -            return; +        if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc['title-custom']) { +            UndoManager.RunInBatch(() => { +                this.dataDoc['title-custom'] = true; +                this.dataDoc.showTitle = 'title'; +                const tr = this._editorView!.state.tr; +                this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection()); +            }, 'titler');          } +    }; + +    onKeyDown = (e: React.KeyboardEvent) => {          if (e.altKey) {              e.preventDefault();              return;          }          const state = this._editorView!.state; -        if (!state.selection.empty && e.key === "%") { +        if (!state.selection.empty && e.key === '%') {              this._rules!.EnteringStyle = true;              e.preventDefault();              e.stopPropagation(); @@ -1443,34 +1649,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              this._rules!.EnteringStyle = false;          }          e.stopPropagation(); -        for (var i = state.selection.from; i < state.selection.to; i++) { +        for (var i = state.selection.from; i <= state.selection.to; i++) {              const node = state.doc.resolve(i); -            if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark && -                mark.attrs.userid !== Doc.CurrentUserEmail) && -                [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) { +            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {                  e.preventDefault();              }          }          switch (e.key) { -            case "Escape": +            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) }))); +            case 'Enter': +                this.insertTime(); +            case 'Tab': +                e.preventDefault(); +                break; +            default: +                if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; +            case ' ': +                [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && +                    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 = (e: React.DragEvent) => {          this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));          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 = (e: React.UIEvent) => {          if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {              if (!this.props.dontSelectOnLoad) { @@ -1479,142 +1687,164 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  this._ignoreScroll = false;              }          } -    } +    };      tryUpdateScrollHeight = () => {          const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);          const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;          if (children) { -            const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); +            const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins);              const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); -            if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation -                const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; -                if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { +            if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { +                // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation +                const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight); +                if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {                      setScrollHeight();                  } else {                      setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...                  }              }          } -    } -    fitToBox = () => this.props.Document._fitToBox; -    sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); -    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { +    }; +    fitContentsToBox = () => this.props.Document._fitContentsToBox; +    sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); +    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {          if (!this.layoutDoc._showSidebar) this.toggleSidebar();          // console.log("printting allSideBarDocs");          // console.log(this.allSidebarDocs);          return this.addDocument(doc, sidebarKey); -    } +    };      sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);      sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); -    setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height; -    sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); -    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); +    setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '-height'] = height); +    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); +    sidebarScreenToLocal = () => +        this.props +            .ScreenToLocalTransform() +            .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0) +            .scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1));      @computed get audioHandle() { -        return <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} > -            <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" /> -        </div>; +        return ( +            <div className="formattedTextBox-dictation" onClick={action(e => (this._recording = !this._recording))}> +                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" /> +            </div> +        );      }      @computed get sidebarHandle() {          TraceMobx();          const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;          const color = !annotated ? Colors.WHITE : Colors.BLACK; -        const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")); +        const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : '')); -        return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) : -            <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} +        return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging()) ? null : ( +            <div +                className="formattedTextBox-sidebar-handle" +                onPointerDown={this.sidebarDown}                  style={{ -                    left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,                      backgroundColor: backgroundColor,                      color: color, -                    opacity: annotated ? 1 : undefined -                }} > -                <FontAwesomeIcon icon={"comment-alt"} /> -            </div>; +                    opacity: annotated ? 1 : undefined, +                }}> +                <FontAwesomeIcon icon={'comment-alt'} /> +            </div> +        );      }      @computed get sidebarCollection() {          const renderComponent = (tag: string) => { -            const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView; -            return ComponentTag === CollectionStackingView ? -                <SidebarAnnos ref={this._sidebarRef} +            const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'tree' ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; +            return ComponentTag === CollectionStackingView ? ( +                <SidebarAnnos +                    ref={this._sidebarRef}                      {...this.props}                      fieldKey={this.fieldKey}                      rootDoc={this.rootDoc}                      layoutDoc={this.layoutDoc}                      dataDoc={this.dataDoc} -                    // usePanelWidth={true} +                    ScreenToLocalTransform={this.sidebarScreenToLocal}                      nativeWidth={NumCast(this.layoutDoc._nativeWidth)} +                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}                      showSidebar={this.SidebarShown}                      PanelWidth={this.sidebarWidth}                      setHeight={this.setSidebarHeight}                      sidebarAddDocument={this.sidebarAddDocument}                      moveDocument={this.moveDocument}                      removeDocument={this.removeDocument} -                /> : -                <ComponentTag -                    {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} -                    NativeWidth={returnZero} -                    NativeHeight={returnZero} -                    PanelHeight={this.props.PanelHeight} -                    PanelWidth={this.sidebarWidth} -                    xPadding={0} -                    yPadding={0} -                    scaleField={this.SidebarKey + "-scale"} -                    isAnnotationOverlay={false} -                    select={emptyFunction} -                    scaling={this.sidebarContentScaling} -                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} -                    removeDocument={this.sidebarRemDocument} -                    moveDocument={this.sidebarMoveDocument} -                    addDocument={this.sidebarAddDocument} -                    CollectionView={undefined} -                    ScreenToLocalTransform={this.sidebarScreenToLocal} -                    renderDepth={this.props.renderDepth + 1} -                    setHeight={this.setSidebarHeight} -                    fitContentsToDoc={this.fitToBox} -                    noSidebar={true} -                    fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />; +                /> +            ) : ( +                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}> +                    //@ts-ignore +                    <ComponentTag +                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} +                        NativeWidth={returnZero} +                        NativeHeight={returnZero} +                        PanelHeight={this.props.PanelHeight} +                        PanelWidth={this.sidebarWidth} +                        xPadding={0} +                        yPadding={0} +                        scaleField={this.SidebarKey + '-scale'} +                        isAnnotationOverlay={false} +                        select={emptyFunction} +                        NativeDimScaling={this.sidebarContentScaling} +                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} +                        removeDocument={this.sidebarRemDocument} +                        moveDocument={this.sidebarMoveDocument} +                        addDocument={this.sidebarAddDocument} +                        CollectionView={undefined} +                        ScreenToLocalTransform={this.sidebarScreenToLocal} +                        renderDepth={this.props.renderDepth + 1} +                        setHeight={this.setSidebarHeight} +                        fitContentsToBox={this.fitContentsToBox} +                        noSidebar={true} +                        treeViewHideTitle={true} +                        fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-sidebar`} +                    /> +                </div> +            );          }; -        return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.SelectedTool !== InkTool.None ? "-inking" : "")} -            style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> -            {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} -        </div>; +        return ( +            <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> +                {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} +            </div> +        );      }      render() {          TraceMobx(); -        const selected = this.props.isSelected(); -        const active = this.props.isContentActive(); -        const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); -        const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; -        const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); +        const active = this.props.isContentActive() || this.props.isSelected(); +        const selected = active; +        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); +        const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : ''; +        const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);          if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);          const minimal = this.props.ignoreAutoHeight;          const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);          const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); -        const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0); -        const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : ""; -        const styleFromString = this.styleFromLayoutString(scale);   // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' > -        return (styleFromString?.height === "0px" ? (null) : -            <div className="formattedTextBox-cont" +        const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0; +        const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : ''; +        const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' > +        return styleFromString?.height === '0px' ? null : ( +            <div +                className="formattedTextBox-cont"                  onWheel={e => this.props.isContentActive() && e.stopPropagation()}                  style={{                      transform: this.props.dontScale ? undefined : `scale(${scale})`, -                    transformOrigin: this.props.dontScale ? undefined : "top left", +                    transformOrigin: this.props.dontScale ? undefined : 'top left',                      width: this.props.dontScale ? undefined : `${100 / scale}%`,                      height: this.props.dontScale ? undefined : `${100 / scale}%`,                      // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, -                    ...styleFromString +                    ...styleFromString,                  }}> -                <div className={`formattedTextBox-cont`} ref={this._ref} +                <div +                    className={`formattedTextBox-cont`} +                    ref={this._ref}                      style={{ -                        overflow: this.autoHeight ? "hidden" : undefined, -                        height: this.props.height || (this.autoHeight && this.props.renderDepth ? "max-content" : undefined), +                        overflow: this.autoHeight ? 'hidden' : undefined, +                        height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),                          background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),                          color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),                          fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), -                        fontWeight: Cast(this.layoutDoc._fontWeight, "string", null) as any, -                        fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), -                        pointerEvents: interactive ? undefined : "none", +                        fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any, +                        fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'), +                        pointerEvents: interactive ? undefined : 'none',                      }}                      onContextMenu={this.specificContextMenu}                      onKeyDown={this.onKeyDown} @@ -1624,32 +1854,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                      onBlur={this.onBlur}                      onPointerUp={this.onPointerUp}                      onPointerDown={this.onPointerDown} -                    onMouseUp={this.onMouseUp} -                    onDoubleClick={this.onDoubleClick} -                > -                    <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef} +                    onDoubleClick={this.onDoubleClick}> +                    <div +                        className={`formattedTextBox-outer${selected ? '-selected' : ''}`} +                        ref={this._scrollRef}                          style={{ -                            width: this.props.dontSelectOnLoad ? "100%" : `calc(100% - ${this.sidebarWidthPercent})`, -                            pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined, -                            overflow: this.layoutDoc._singleLine ? "hidden" : undefined, +                            width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`, +                            pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined, +                            overflow: this.layoutDoc._singleLine ? 'hidden' : undefined,                          }} -                        onScroll={this.onScroll} onDrop={this.ondrop} > -                        <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget} +                        onScroll={this.onScroll} +                        onDrop={this.ondrop}> +                        <div +                            className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`} +                            ref={this.createDropTarget}                              style={{                                  padding: StrCast(this.layoutDoc._textBoxPadding),                                  paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),                                  paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),                                  paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),                                  paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`), -                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? "none" : undefined) : undefined +                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,                              }}                          />                      </div> -                    {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection} -                    {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle} -                    {!this.layoutDoc._showAudio ? (null) : this.audioHandle} +                    {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} +                    {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} +                    {!this.layoutDoc._showAudio ? null : this.audioHandle}                  </div> -            </div > +            </div>          );      }  }  | 
