From b1c83a54b8794c91bf2c218939891e87ad2a2776 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 2 Feb 2021 14:08:07 -0500
Subject: playing with translations in textbox sidebars.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 55 ++++++++++++++++------
 1 file changed, 40 insertions(+), 15 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d24ccd9ad..634165bab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,12 +67,14 @@ import { StyleProp } from '../../StyleProvider';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
 import { DocumentManager } from '../../../util/DocumentManager';
+var 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
     hideOnLeave?: boolean;  // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
     xMargin?: number;   // used to override document's settings for xMargin --- see CollectionCarouselView
     yMargin?: number;
+    noSidebar?: boolean;
     dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded
 }
 export const GoogleRef = "googleDocId";
@@ -94,7 +96,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: boolean = false;
+    private _applyingChange: string = "";
     private _searchIndex = 0;
     private _cachedLinks: Doc[] = [];
     private _undoTyping?: UndoManager.Batch;
@@ -246,6 +248,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom));
     }
 
+    _lastText = "";
     dispatchTransaction = (tx: Transaction) => {
         let timeStamp;
         clearTimeout(timeStamp);
@@ -296,8 +299,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             };
 
             if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
-                if (!this._applyingChange && removeSelection(json) !== removeSelection(curProto?.Data)) {
-                    this._applyingChange = 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)
                         if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
@@ -325,7 +328,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
                         unchanged = false;
                     }
-                    this._applyingChange = false;
+                    this._applyingChange = "";
                     if (!unchanged) {
                         this.updateTitle();
                         this.tryUpdateHeight();
@@ -931,16 +934,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             () => {
                 if (!this.dataDoc || !this.layoutDoc) return undefined;
                 if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey]) {
-                    return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
+                    return { data: Cast(this.dataDoc[this.props.fieldKey], RichTextField, null), str: StrCast(this.dataDoc[this.props.fieldKey]) };
                 }
-                return Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null)?.Data;
+                return { data: Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null), str: StrCast(this.layoutDoc[this.props.fieldKey]) };
             },
             incomingValue => {
-                if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
-                    const updatedState = JSON.parse(incomingValue);
-                    if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
-                        this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
-                        this.tryUpdateHeight();
+                if (this._editorView && this._applyingChange !== this.fieldKey) {
+                    if (incomingValue?.data) {
+                        const updatedState = JSON.parse(incomingValue.data.Data);
+                        if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
+                            this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+                            this.tryUpdateHeight();
+                        }
+                    } else if (incomingValue?.str) {
+                        selectAll(this._editorView!.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
                     }
                 }
             },
@@ -1541,6 +1548,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         FormattedTextBox.LiveTextUndo?.end();
         FormattedTextBox.LiveTextUndo = undefined;
+
+        const state = this._editorView!.state;
+        const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
+        if (!this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
+            try {
+                translateGoogleApi(curText, { from: "en", to: "es", }).then(result => {
+                    this.dataDoc[this.fieldKey + "-translation"] = result[0];
+                });
+            } catch (e) {
+                console.log(e.message);
+            }
+            this._lastText = curText;
+        }
     }
 
     _lastTimedMark: Mark | undefined = undefined;
@@ -1636,7 +1656,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @computed get sidebarHandle() {
         const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length;
-        return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
+        return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) :
             <div className="formattedTextBox-sidebar-handle"
                 style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }}
                 onPointerDown={this.sidebarDown}
@@ -1670,12 +1690,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             ScreenToLocalTransform: this.sidebarScreenToLocal,
             renderDepth: this.props.renderDepth + 1,
         };
-        return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
+        return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
             <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
                 style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
+                <FormattedTextBox
+                    {...collectionProps}
+                    noSidebar={true}
+                    fieldKey={`${this.fieldKey}-translation`}
+                />
+                {/* {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
                     <CollectionFreeFormView {...collectionProps} /> :
-                    <CollectionStackingView {...collectionProps} />}
+                    <CollectionStackingView {...collectionProps} />} */}
             </div>;
     }
 
-- 
cgit v1.2.3-70-g09d2


From 5666aa3b921024c0f7e6ebb48e0e8f50bb770e79 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 2 Feb 2021 23:00:17 -0500
Subject: fixed document decorations on lightbox.  made translations an option
 for text boxes.

---
 src/client/views/DocumentDecorations.scss          |  1 +
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/MainView.tsx                      |  6 +++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 27 ++++++++++------------
 4 files changed, 18 insertions(+), 18 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 22e120167..461ea0fef 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -4,6 +4,7 @@ $linkGap : 3px;
 
 .documentDecorations {
     position: absolute;
+    z-index: 20000;
 }
 
 .documentDecorations-container {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b8500c263..226eef658 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -604,7 +604,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2,
                 pointerEvents: KeyManager.Instance.ShiftPressed || this.Interacting ? "none" : "all",
-                zIndex: SelectionManager.Views().length > 1 ? 900 : 0,
+                display: SelectionManager.Views().length <= 1 ? "none" : undefined
             }} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
             {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 08d590fd4..8598a7303 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -59,7 +59,7 @@ import { AnchorMenu } from './pdf/AnchorMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { SearchBox } from './search/SearchBox';
-import { DefaultStyleProvider, StyleProp } from './StyleProvider';
+import { DefaultStyleProvider, StyleProp, DefaultLayerProvider } from './StyleProvider';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -596,6 +596,7 @@ export class MainView extends React.Component {
 
     lightboxWidth = () => window.innerWidth - Math.min(window.innerWidth / 4, 200) * 2;
     lightboxHeight = () => window.innerHeight - Math.min(window.innerHeight / 4, 100) * 2;
+    lightboxScreenToLocal = () => new Transform(-Math.min(window.innerWidth / 4, 200), -Math.min(window.innerHeight / 4, 100), 1);
     @computed get lightboxView() {
         return !this.LightboxDoc ? (null) :
             <div className="mainView-lightBoxFrame" onPointerDown={action(() => this.LightboxDoc = undefined)}  >
@@ -614,7 +615,8 @@ export class MainView extends React.Component {
                         rootSelected={returnTrue}
                         removeDocument={undefined}
                         styleProvider={DefaultStyleProvider}
-                        ScreenToLocalTransform={Transform.Identity}
+                        layerProvider={DefaultLayerProvider(this.LightboxDoc)}
+                        ScreenToLocalTransform={this.lightboxScreenToLocal}
                         PanelWidth={this.lightboxWidth}
                         PanelHeight={this.lightboxHeight}
                         focus={emptyFunction}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 634165bab..8b1e58ceb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,7 +67,7 @@ import { StyleProp } from '../../StyleProvider';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
 import { DocumentManager } from '../../../util/DocumentManager';
-var translateGoogleApi = require("translate-google-api")
+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
@@ -1551,14 +1551,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         const state = this._editorView!.state;
         const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
-        if (!this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
+        if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
             try {
-                translateGoogleApi(curText, { from: "en", to: "es", }).then(result => {
-                    this.dataDoc[this.fieldKey + "-translation"] = result[0];
+                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) {
-                console.log(e.message);
-            }
+            } catch (e) { console.log(e.message); }
             this._lastText = curText;
         }
     }
@@ -1693,14 +1693,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
             <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
                 style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                <FormattedTextBox
-                    {...collectionProps}
-                    noSidebar={true}
-                    fieldKey={`${this.fieldKey}-translation`}
-                />
-                {/* {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
-                    <CollectionFreeFormView {...collectionProps} /> :
-                    <CollectionStackingView {...collectionProps} />} */}
+                {this.layoutDoc.sidebarViewType === "translation" ?
+                    <FormattedTextBox {...collectionProps} noSidebar={true} fieldKey={`${this.fieldKey}-translation`} /> :
+                    this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
+                        <CollectionFreeFormView {...collectionProps} /> :
+                        <CollectionStackingView {...collectionProps} />}
             </div>;
     }
 
-- 
cgit v1.2.3-70-g09d2


From a774d7f55755703faa316401dd8724dd82a9ee98 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 3 Feb 2021 12:08:27 -0500
Subject: added linkRelationship to linkEditor.  fixed warnings.

---
 src/client/views/GestureOverlay.tsx                | 18 +----
 src/client/views/collections/CollectionMenu.tsx    | 11 ++-
 src/client/views/linking/LinkEditor.tsx            | 84 +++++++++++++++-------
 src/client/views/nodes/WebBox.tsx                  |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 5 files changed, 71 insertions(+), 47 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0cf958681..ae27e78d5 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -32,10 +32,6 @@ export class GestureOverlay extends Touchable {
     @observable public InkShape: string = "";
     @observable public SavedColor?: string;
     @observable public SavedWidth?: string;
-    @observable public SavedFill?: string;
-    @observable public SavedArrowStart: string = "none";
-    @observable public SavedArrowEnd: string = "none";
-    @observable public SavedDash: String = "0";
     @observable public Tool: ToolglassTools = ToolglassTools.None;
 
     @observable private _thumbX?: number;
@@ -653,15 +649,7 @@ export class GestureOverlay extends Touchable {
         } else {
             this._points = [];
         }
-        //get out of ink mode after each stroke=
-        if (CollectionFreeFormViewChrome.Instance && !CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
-            Doc.SetSelectedTool(InkTool.None);
-            CollectionFreeFormViewChrome.Instance._selectedPrimitive = CollectionFreeFormViewChrome.Instance._shapesPrimNum;
-            SetActiveArrowStart("none");
-            GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
-            SetActiveArrowEnd("none");
-            GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
-        }
+        CollectionFreeFormViewChrome.Instance.primCreated();
     }
 
     makePolygon = (shape: string, gesture: boolean) => {
@@ -969,13 +957,9 @@ Scripting.addGlobal(function setPen(width: any, color: any, fill: any, arrowStar
         SetActiveInkColor(color);
         GestureOverlay.Instance.SavedWidth = ActiveInkWidth();
         SetActiveInkWidth(width);
-        GestureOverlay.Instance.SavedFill = ActiveFillColor();
         SetActiveFillColor(fill);
-        GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
         SetActiveArrowStart(arrowStart);
-        GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
         SetActiveArrowStart(arrowEnd);
-        GestureOverlay.Instance.SavedDash = ActiveDash();
         SetActiveDash(dash);
     });
 });
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 4e2d81e61..7ba857449 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -25,7 +25,7 @@ import { undoBatch } from "../../util/UndoManager";
 import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
 import { EditableView } from "../EditableView";
 import { GestureOverlay } from "../GestureOverlay";
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
+import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { DocumentView } from "../nodes/DocumentView";
 import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
@@ -591,12 +591,19 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
     @observable _selectedPrimitive = this._shapePrims.length;
     @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
-
     @observable _colorBtn = false;
     @observable _widthBtn = false;
     @observable _fillBtn = false;
 
     @action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; }
+    @action primCreated() {
+        if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke=
+            Doc.SetSelectedTool(InkTool.None);
+            this._selectedPrimitive = this._shapePrims.length;
+            SetActiveArrowStart("none");
+            SetActiveArrowEnd("none");
+        }
+    }
 
     @action
     changeColor = (color: string, type: string) => {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 435b9d904..f74b422d3 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -4,7 +4,6 @@ import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../fields/Doc";
 import { DateCast, StrCast } from "../../../fields/Types";
-import { Utils } from "../../../Utils";
 import { LinkManager } from "../../util/LinkManager";
 import { undoBatch } from "../../util/UndoManager";
 import './LinkEditor.scss';
@@ -21,31 +20,41 @@ interface LinkEditorProps {
 export class LinkEditor extends React.Component<LinkEditorProps> {
 
     @observable description = StrCast(LinkManager.currentLink?.description);
+    @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
     @observable openDropdown: boolean = false;
     @observable showInfo: boolean = false;
     @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
     @observable private buttonColor: string = "";
-
+    @observable private relationshipButtonColor: string = "";
 
     //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
 
-    @undoBatch @action
+    @undoBatch
     deleteLink = (): void => {
         LinkManager.Instance.deleteLink(this.props.linkDoc);
         this.props.showLinks();
     }
 
-    @undoBatch @action
-    setDescripValue = (value: string) => {
+    @undoBatch
+    setRelationshipValue = action((value: string) => {
+        if (LinkManager.currentLink) {
+            LinkManager.currentLink.linkRelationship = value;
+            this.relationshipButtonColor = "rgb(62, 133, 55)";
+            setTimeout(action(() => this.relationshipButtonColor = ""), 750);
+            return true;
+        }
+    });
+
+    @undoBatch
+    setDescripValue = action((value: string) => {
         if (LinkManager.currentLink) {
             LinkManager.currentLink.description = value;
             this.buttonColor = "rgb(62, 133, 55)";
             setTimeout(action(() => this.buttonColor = ""), 750);
             return true;
         }
-    }
+    });
 
-    @action
     onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === "Enter") {
             this.setDescripValue(this.description);
@@ -53,22 +62,48 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
         }
     }
 
-    @action
-    onDown = () => {
-        this.setDescripValue(this.description);
+    onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+        if (e.key === "Enter") {
+            this.setRelationshipValue(this.relationship);
+            document.getElementById('input')?.blur();
+        }
     }
 
+    onDown = () => this.setDescripValue(this.description);
+    onRelationshipDown = () => this.setRelationshipValue(this.description);
+
     @action
-    handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.description = e.target.value;
-    }
+    handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
+    @action
+    handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; }
 
+    @computed
+    get editRelationship() {
+        return <div className="linkEditor-description">
+            <div className="linkEditor-description-label">Link Relationship:</div>
+            <div className="linkEditor-description-input">
+                <div className="linkEditor-description-editing">
+                    <input
+                        style={{ width: "100%" }}
+                        id="input"
+                        value={this.relationship}
+                        placeholder={"enter link label"}
+                        // color={"rgb(88, 88, 88)"}
+                        onKeyDown={this.onRelationshipKey}
+                        onChange={this.handleRelationshipChange}
+                    ></input>
+                </div>
+                <div className="linkEditor-description-add-button"
+                    style={{ background: this.relationshipButtonColor }}
+                    onPointerDown={this.onRelationshipDown}>Set</div>
+            </div>
+        </div>;
+    }
 
     @computed
     get editDescription() {
         return <div className="linkEditor-description">
-            <div className="linkEditor-description-label">
-                Link Label:</div>
+            <div className="linkEditor-description-label">Link Description:</div>
             <div className="linkEditor-description-input">
                 <div className="linkEditor-description-editing">
                     <input
@@ -84,13 +119,12 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                 <div className="linkEditor-description-add-button"
                     style={{ background: this.buttonColor }}
                     onPointerDown={this.onDown}>Set</div>
-            </div></div>;
+            </div>
+        </div>;
     }
 
     @action
-    changeDropdown = () => {
-        this.openDropdown = !this.openDropdown;
-    }
+    changeDropdown = () => { this.openDropdown = !this.openDropdown; }
 
     @undoBatch
     changeFollowBehavior = action((follow: string) => {
@@ -101,8 +135,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @computed
     get followingDropdown() {
         return <div className="linkEditor-followingDropdown">
-            <div className="linkEditor-followingDropdown-label">
-                Follow Behavior:</div>
+            <div className="linkEditor-followingDropdown-label">Follow Behavior:</div>
             <div className="linkEditor-followingDropdown-dropdown">
                 <div className="linkEditor-followingDropdown-header"
                     onPointerDown={this.changeDropdown}>
@@ -157,9 +190,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     }
 
     @action
-    changeInfo = () => {
-        this.showInfo = !this.showInfo;
-    }
+    changeInfo = () => { this.showInfo = !this.showInfo; }
 
     render() {
         const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
@@ -186,8 +217,9 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                         {DateCast(this.props.linkDoc.creationDate).toString()}</div> : null}</div>
                 </div> : null}
 
-                <div>{this.editDescription}</div>
-                <div>{this.followingDropdown}</div>
+                {this.editDescription}
+                {this.editRelationship}
+                {this.followingDropdown}
             </div>
 
         );
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 438395ab0..352abd1e7 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -256,6 +256,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         } catch (e) {
             console.log("WebBox URL error:" + this._url);
         }
+        return true;
     }
 
     editToggleBtn() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 8b1e58ceb..b3abcf90a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,7 +67,7 @@ import { StyleProp } from '../../StyleProvider';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
 import { DocumentManager } from '../../../util/DocumentManager';
-const translateGoogleApi = require("translate-google-api")
+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
@@ -947,7 +947,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             this.tryUpdateHeight();
                         }
                     } else if (incomingValue?.str) {
-                        selectAll(this._editorView!.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
+                        selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
                     }
                 }
             },
-- 
cgit v1.2.3-70-g09d2


From 0e5891eab7f53697b764b7e9da5163db0351a0a2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Feb 2021 19:25:48 -0500
Subject: overhaul of link anchors on text boxes to use actual Documents to
 represent selected text.  Also got rid of _scrollY and _scrollPreviewY so
 that all document regions can be focused on using focus() and the new
 scrollFocus() mechanisim

---
 src/client/documents/DocumentTypes.ts              |   4 +-
 src/client/documents/Documents.ts                  |   7 +
 src/client/util/DocumentManager.ts                 |   8 +-
 src/client/views/GestureOverlay.tsx                |  17 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  14 +-
 src/client/views/nodes/DocumentView.tsx            |  14 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  15 +-
 src/client/views/nodes/PDFBox.tsx                  |   5 +-
 src/client/views/nodes/PresBox.tsx                 |   2 +-
 src/client/views/nodes/WebBox.tsx                  | 184 ++++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 207 ++++++++++-----------
 .../formattedText/FormattedTextBoxComment.tsx      |  18 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  24 +--
 src/client/views/nodes/formattedText/marks_rts.ts  |  20 +-
 src/client/views/pdf/PDFViewer.tsx                 | 116 +++++-------
 src/fields/documentSchemas.ts                      |   7 +-
 src/fields/util.ts                                 |   4 +-
 20 files changed, 306 insertions(+), 372 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 37a148e55..080657fd8 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -38,5 +38,7 @@ export enum DocumentType {
 
     LINKDB = "linkdb",          // database of links  ??? why do we have this
     SCRIPTDB = "scriptdb",      // database of scripts
-    GROUPDB = "groupdb"         // database of groups
+    GROUPDB = "groupdb",        // database of groups
+
+    TEXTANCHOR = "textanchor"   // selection of text in a text box
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1a4aae17e..7d6db06d5 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -373,6 +373,9 @@ export namespace Docs {
             }],
             [DocumentType.GROUP, {
                 layout: { view: EmptyBox, dataField: defaultDataKey }
+            }],
+            [DocumentType.TEXTANCHOR, {
+                layout: { view: EmptyBox, dataField: defaultDataKey }
             }]
         ]);
 
@@ -785,6 +788,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", targetDropAction: "move", ...options });
         }
 
+        export function TextanchorDocument(options: DocumentOptions = {}) {
+            return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), document, { targetDropAction: "move", ...options });
+        }
+
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
         }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7af16ed6e..f5f6b6f67 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -149,7 +149,8 @@ export class DocumentManager {
         const highlight = () => {
             const finalDocView = getFirstDocView(targetDoc);
             if (finalDocView) {
-                finalDocView.layoutDoc.scrollToLinkID = linkDoc?.[Id];
+                const parent = targetDoc?.annotationOn as Doc;
+                if (parent) finalDocView.layoutDoc.scrollToAnchorID = targetDoc?.[Id];
                 Doc.linkFollowHighlight(finalDocView.props.Document);
             }
         };
@@ -159,7 +160,7 @@ export class DocumentManager {
             const first = getFirstDocView(annotatedDoc);
             if (first) {
                 annotatedDoc = first.props.Document;
-                first.props.focus(annotatedDoc, false);
+                first.focus(targetDoc, false);
             }
         }
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
@@ -190,7 +191,6 @@ export class DocumentManager {
                 highlight();
             } else {  // otherwise try to get a view of the context of the target
                 const targetDocContextView = getFirstDocView(targetDocContext);
-                targetDocContext._scrollY = targetDocContext._scrollPreviewY = NumCast(targetDocContext._scrollTop, 0);  // this will force PDFs to activate and load their annotations / allow scrolling
                 if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
                     targetDocContext._viewTransition = "transform 500ms";
                     targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
@@ -208,7 +208,7 @@ export class DocumentManager {
                             } else if (delay > 1500) {
                                 // we didn't find the target, so it must have moved out of the context.  Go back to just creating it.
                                 if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document);
-                                if (targetDoc.layout) {
+                                if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document
                                     Doc.SetInPlace(targetDoc, "annotationOn", undefined, false);
                                     createViewFunc(Doc.BrushDoc(targetDoc), finished); //  create a new view of the target
                                 }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 76cb0112e..9306cf9ae 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -516,7 +516,7 @@ export class GestureOverlay extends Touchable {
     }
 
     handleLineGesture = (): boolean => {
-        let actionPerformed = false;
+        const actionPerformed = false;
         const B = this.svgBounds;
 
         // get the two targets at the ends of the line
@@ -525,21 +525,6 @@ export class GestureOverlay extends Touchable {
         const target1 = document.elementFromPoint(ep1.X, ep1.Y);
         const target2 = document.elementFromPoint(ep2.X, ep2.Y);
 
-        // callback function to be called by each target
-        const callback = (doc: Doc) => {
-            if (!this._d1) {
-                this._d1 = doc;
-            }
-            // we don't want to create a link of both endpoints are the same document (doing so makes drawing an l very hard)
-            else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
-                // we don't want to create a link between ink strokes (doing so makes drawing a t very hard)
-                if (this._d1.type !== "ink" && doc.type !== "ink") {
-                    DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link", "");
-                    actionPerformed = true;
-                }
-            }
-        };
-
         const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
             {
                 bubbles: true,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 44aa41f85..bc3d05005 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -101,7 +101,7 @@ export class MainView extends React.Component {
         }
         new InkStrokeProperties();
         this._sidebarContent.proto = undefined;
-        DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
 
         DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3956b8c5b..058d21c92 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -12,7 +12,7 @@ import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { CollectionViewType } from './collections/CollectionView';
 import { MainView } from './MainView';
-import { DocumentViewProps } from "./nodes/DocumentView";
+import { DocumentViewProps, DocumentView } from "./nodes/DocumentView";
 import { FieldViewProps } from './nodes/FieldView';
 import "./StyleProvider.scss";
 import React = require("react");
@@ -111,6 +111,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
                 case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
                 case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
                 case DocumentType.LINK: return "transparent";
+                case DocumentType.WEB:
+                case DocumentType.PDF:
                 case DocumentType.VID: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
                 case DocumentType.COL:
                     if (StrCast(Doc.LayoutField(doc)).includes("SliderBox")) break;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e25a46a5d..a52522def 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -271,12 +271,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @undoBatch
     @action
-    internalPdfAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
+    internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
         const dragDoc = annoDragData.dropDocument!;
         const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)];
         dragDoc.x = xp - annoDragData.offset[0] + (NumCast(dragDoc.x) - dropPos[0]);
         dragDoc.y = yp - annoDragData.offset[1] + (NumCast(dragDoc.y) - dropPos[1]);
-        annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
         this.bringToFront(dragDoc);
         return true;
     }
@@ -303,7 +302,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
         if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
-        if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp);
+        if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp);
         if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
         return false;
     }
@@ -920,7 +919,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     scrollTo = Math.max(0, NumCast(doc.y) - 50);
                 }
                 if (curScroll !== scrollTo || this.props.Document._viewTransition) {
-                    this.props.Document._scrollPreviewY = this.props.Document._scrollY = scrollTo;
                     delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0;
                     !dontCenter && this.props.focus(this.props.Document);
                     afterFocus && setTimeout(() => afterFocus?.(delay ? true : false), delay);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 2cac2d0b8..defa4dbf0 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -58,8 +58,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                             // however, the dropped document isn't so accessible.  What we do is set the newly created link document on the documentView
                             // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
                             dropEv.linkDragData?.linkDropCallback?.(dropEv as { linkDocument: Doc }); // bcz: typescript can't figure out that this is valid even though we tested dropEv.linkDocument above
-                            runInAction(() => this.props.View.LinkBeingCreated = dropEv.linkDocument);
-                            setTimeout(action(() => this.props.View.LinkBeingCreated = undefined), 0);
                         }
                         linkDrag?.end();
                     },
@@ -118,7 +116,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     DocumentLinksButton.AnnotationId = undefined;
                 } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
                     const sourceDoc = DocumentLinksButton.StartLink;
-                    const targetDoc = this.props.View.props.Document;
+                    const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
                     const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "long drag");
 
                     LinkManager.currentLink = linkDoc;
@@ -163,15 +161,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
             startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
             const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag", undefined, undefined, true);
-            // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks ().  see note above in onLInkButtonMoved
-            if (endLinkView) {
-                endLinkView.LinkBeingCreated = linkDoc;
-                DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView.LinkBeingCreated = linkDoc);
-                setTimeout(action(() => {
-                    DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView.LinkBeingCreated = undefined);
-                    endLinkView.LinkBeingCreated = undefined;
-                }), 0);
-            }
+
             LinkManager.currentLink = linkDoc;
 
             if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2f56f5b00..463e59bd1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,12 +40,15 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { PresBox } from './PresBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
+import { LinkDocPreview } from "./LinkDocPreview";
+import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
 
 export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
 export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     getAnchor: () => Doc;
+    scrollFocus?: (doc: Doc, smooth: boolean) => void;
     back?: () => boolean;
     forward?: () => boolean;
     url?: () => string;
@@ -373,6 +376,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
+    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => {
+        this._componentView?.scrollFocus?.(doc, !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+        return this.props.focus(doc, willZoom, scale, afterFocus, dontCenter, focused);
+    }
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
@@ -710,7 +717,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
-    makeLink = () => this.props.DocumentView.LinkBeingCreated; // pass the link placeholde to child views so they can react to make a specialized anchor.  This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
     setContentView = (view: { getAnchor: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
     @observable contentsActive: () => boolean = returnFalse;
     @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive;
@@ -729,9 +735,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 contentsActive={this.setContentsActive}
                 parentActive={this.parentActive}
                 ScreenToLocalTransform={this.screenToLocal}
-                makeLink={this.makeLink}
                 rootSelected={this.rootSelected}
                 onClick={this.onClickFunc}
+                focus={this.focus}
                 layoutKey={this.finalLayoutKey} />
             {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
             {this.hideLinkButton ? (null) :
@@ -865,7 +871,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
     public ContentRef = React.createRef<HTMLDivElement>();
 
-    @observable LinkBeingCreated: Opt<Doc>;  // see DocumentLinksButton for explanation of how this works
     @observable public docView: DocumentViewInternal | undefined | null;
 
     get Document() { return this.props.Document; }
@@ -907,6 +912,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
     contentsActive = () => this.docView?.contentsActive();
+    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => {
+        return this.docView?.focus(doc, willZoom, scale, afterFocus, dontCenter, focused);
+    }
     getBounds = () => {
         if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 75ca6059e..8051568ff 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -26,6 +26,7 @@ export class LinkDocPreview extends React.Component<Props> {
     @observable public static LinkInfo: Opt<{ linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }>;
     @observable _targetDoc: Opt<Doc>;
     @observable _toolTipText = "";
+    _linkTarget: Opt<Doc>;
     _editRef = React.createRef<HTMLDivElement>();
 
     @action
@@ -58,17 +59,13 @@ export class LinkDocPreview extends React.Component<Props> {
                 runInAction(() => this._toolTipText = "external => " + this.props.href);
             }
         } else if (linkDoc && linkSrc) {
-            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), linkSrc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
-            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+            const anchor1 = linkDoc.anchor1 as Doc;
+            const anchor2 = linkDoc.anchor2 as Doc;
+            this._linkTarget = Doc.AreProtosEqual(anchor1, linkSrc) || Doc.AreProtosEqual(anchor1.annotationOn as Doc, linkSrc) ? anchor2 : anchor1;
+            const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
             runInAction(() => {
                 this._toolTipText = "";
                 LinkDocPreview.TargetDoc = this._targetDoc = target;
-                if (this._targetDoc) {
-                    this._targetDoc._scrollToPreviewLinkID = linkDoc?.[Id];
-                    if (anchor !== this._targetDoc && anchor) {
-                        this._targetDoc._scrollPreviewY = NumCast(anchor?.y);
-                    }
-                }
             });
         }
     }
@@ -89,7 +86,7 @@ export class LinkDocPreview extends React.Component<Props> {
                 </div>
             </div>
             :
-            <DocumentView
+            <DocumentView ref={r => this._linkTarget !== this._targetDoc && this._linkTarget && r?.focus(this._linkTarget)}
                 Document={this._targetDoc}
                 moveDocument={returnFalse}
                 rootSelected={returnFalse}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index ec9a75302..496caedaa 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -79,8 +79,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         }
     }
 
+    scrollFocus = (doc: Doc, smooth: boolean) => doc !== this.rootDoc && this._pdfViewer?.scrollFocus(doc, smooth);
+    getAnchor = () => this.rootDoc;
     componentWillUnmount() { this._selectReactionDisposer?.(); }
     componentDidMount() {
+        this.props.setContentView?.(this);
         this._selectReactionDisposer = reaction(() => this.props.isSelected(),
             () => {
                 document.removeEventListener("keydown", this.onKeyDown);
@@ -218,7 +221,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
     static pdfpromise = new Map<string, Pdfjs.PDFPromise<Pdfjs.PDFDocumentProxy>>();
     render() {
         TraceMobx();
-        if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0) || this.props.Document._scrollY !== undefined) {
+        if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0)) {
             this._displayPdfLive = true;
         }
         if (this._displayPdfLive) {
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index b6feace12..589a1c2ae 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -364,7 +364,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         bestTarget && runInAction(() => {
             if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
                 bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
-                bestTarget._scrollY = activeItem.presPinViewScroll;
+                bestTarget._scrollTop = activeItem.presPinViewScroll;
             } else if (bestTarget.type === DocumentType.COMPARISON) {
                 bestTarget._clipWidth = activeItem.presPinClipWidth;
             } else if (bestTarget.type === DocumentType.VID) {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 1dfe88f78..ee152ddb3 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import { Dictionary } from "typescript-collections";
 import * as WebRequest from 'web-request';
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, WidthSym, Opt } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
 import { HtmlField } from "../../../fields/HtmlField";
@@ -24,11 +24,11 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
+import { MarqueeAnnotator } from "../MarqueeAnnotator";
 import { Annotation } from "../pdf/Annotation";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./WebBox.scss";
 import React = require("react");
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
 const htmlToText = require("html-to-text");
 
 type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -44,15 +44,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
     private _iframeDragRef = React.createRef<HTMLDivElement>();
+    private _ignoreScroll = false;
+    private _initialScroll: Opt<number>;
     @observable private _marqueeing: number[] | undefined;
     @observable private _url: string = "hello";
     @observable private _pressX: number = 0;
     @observable private _pressY: number = 0;
     @observable private _iframe: HTMLIFrameElement | null = null;
     @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
-
-    get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
-    set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
     get scrollHeight() { return this.webpage?.scrollHeight || 1000; }
     get webpage() { return this._iframe?.contentDocument?.children[0]; }
 
@@ -65,9 +64,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this._annotationKey = this._annotationKey + "-" + this.urlHash(this._url);
     }
 
-    iframeLoaded = action((e: any) => {
+    iframeLoaded = (e: any) => {
         const iframe = this._iframe;
         if (iframe?.contentDocument) {
+            if (this._initialScroll !== undefined && this._outerRef.current && this.webpage) {
+                this.webpage.scrollTop = this._initialScroll;
+                this._outerRef.current.scrollTop = this._initialScroll;
+                this._initialScroll = undefined;
+            }
             iframe.setAttribute("enable-annotation", "true");
             iframe.contentDocument.addEventListener("click", undoBatch(action(e => {
                 let href = "";
@@ -76,81 +80,53 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 }
                 if (href) {
                     this.submitURL(href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin));
+                    if (this.webpage) {
+                        this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
+                        this.webpage.scrollLeft = 0;
+                    }
                 }
             })));
             iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false);
-            if (this.webpage) {
-                this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
-                this.webpage.scrollLeft = NumCast(this.layoutDoc._scrollLeft);
-            }
-        }
-        this._disposers.scrollReaction?.();
-        this._disposers.scrollReaction = reaction(() => ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }),
-            ({ scrollY, scrollX }) => {
-                const delay = this._outerRef.current ? 0 : 250; // wait for mainCont and try again to scroll
-                const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
-                const duration = durationStr ? Number(durationStr[1]) : 1000;
-                if (scrollY !== undefined) {
-                    this._forceSmoothScrollUpdate = true;
-                    setTimeout(() => this.layoutDoc._scrollY = undefined, duration);
-                    setTimeout(() => this.webpage && smoothScroll(duration, this.webpage as any as HTMLElement, Math.abs(scrollY || 0)), delay);
-                    setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollY || 0), () => this.layoutDoc._scrollTop = scrollY), delay);
-                }
-                if (scrollX !== undefined) {
-                    this._forceSmoothScrollUpdate = true;
-                    setTimeout(() => this.layoutDoc._scrollX = undefined, duration);
-                    setTimeout(() => this.webpage && smoothScroll(duration, this.webpage as any as HTMLElement, Math.abs(scrollX || 0)), delay);
-                    setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollX || 0), () => this.layoutDoc._scrollLeft = scrollX), delay);
-                }
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.scrollTop = reaction(() => this.layoutDoc._scrollTop,
-            scrollTop => {
-                const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
-                const duration = durationStr ? Number(durationStr[1]) : 1000;
-                if (scrollTop !== this._outerRef.current?.scrollTop && scrollTop !== undefined && this._forceSmoothScrollUpdate) {
-                    this.webpage!.scrollTop = scrollTop;
-                    this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollTop || 0), () => this._forceSmoothScrollUpdate = true);
-                } else this._forceSmoothScrollUpdate = true;
-            },
-            { fireImmediately: true }
-        );
-    });
-    _forceSmoothScrollUpdate = true;
-
-    updateScroll = (x: Opt<number>, y: Opt<number>) => {
-        if (y !== undefined) {
-            this._outerRef.current!.scrollTop = y;
-            this.layoutDoc._scrollY = undefined;
-        }
-        if (x !== undefined) {
-            this._outerRef.current!.scrollLeft = x;
-            this.layoutDoc.scrollX = undefined;
         }
     }
 
-    iframeWheel = (e: any) => {
-        if (this._forceSmoothScrollUpdate && e.target?.children) {
-            this.webpage && setTimeout(action(() => {
-                this.webpage!.scrollLeft = 0;
-                const scrollTop = this.webpage!.scrollTop;
-                const scrollLeft = this.webpage!.scrollLeft;
-                this._outerRef.current!.scrollTop = scrollTop;
-                this._outerRef.current!.scrollLeft = scrollLeft;
-                if (this.layoutDoc._scrollTop !== scrollTop) {
-                    this._forceSmoothScrollUpdate = false;
-                    this.layoutDoc._scrollTop = scrollTop;
-                }
-                if (this.layoutDoc._scrollLeft !== scrollLeft) {
-                    this._forceSmoothScrollUpdate = false;
-                    this.layoutDoc._scrollLeft = scrollLeft;
-                }
-            }));
+    @action
+    onWheelScroll = (scrollTop: number) => {
+        if (this.webpage && this._outerRef.current) {
+            this.webpage.scrollLeft = 0;
+            this._outerRef.current.scrollTop = scrollTop;
+            this._outerRef.current.scrollLeft = 0;
+            this._ignoreScroll = true;
+            if (this.layoutDoc._scrollTop !== scrollTop) {
+                this.layoutDoc._scrollTop = scrollTop;
+            }
+            this._ignoreScroll = false;
         }
     }
+    iframeWheel = (e: any) => this.webpage && e.target?.children && this.onWheelScroll(this.webpage.scrollTop);
+    onWheel = (e: React.WheelEvent) => {
+        this._outerRef.current && this.onWheelScroll(this._outerRef.current.scrollTop);
+        e.stopPropagation();
+    }
 
     getAnchor = () => this.rootDoc;
+    scrollFocus = (doc: Doc, smooth: boolean) => {
+        if (doc !== this.rootDoc && this.webpage && this._outerRef.current) {
+            this._initialScroll !== undefined && (this._initialScroll = NumCast(doc.y));
+            this._ignoreScroll = true;
+            if (smooth) {
+                smoothScroll(500, this.webpage as any as HTMLElement, NumCast(doc.y));
+                smoothScroll(500, this._outerRef.current, NumCast(doc.y));
+            } else {
+                this.webpage.scrollTop = NumCast(doc.y);
+                this._outerRef.current.scrollTop = NumCast(doc.y);
+            }
+            smooth && (this.layoutDoc._scrollTop = NumCast(doc.y));
+            this._ignoreScroll = false;
+        } else {
+            this._initialScroll = NumCast(doc.y);
+        }
+    }
 
     async componentDidMount() {
         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.
@@ -158,9 +134,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
         runInAction(() => this._url = urlField?.url.toString() || "");
 
-        this._disposers.scrollMove = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
-            () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop));
-
         this._disposers.selection = reaction(() => this.props.isSelected(),
             selected => {
                 if (!selected) {
@@ -190,6 +163,30 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 this.dataDoc.text = htmlToText.fromString(result.content);
             }
         }
+
+        var quickScroll: string | undefined = "";
+        this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
+            (scrollTop) => {
+                if (quickScroll !== undefined) {
+                    this._initialScroll = scrollTop;
+                }
+                else if (!this._ignoreScroll && this._outerRef.current && this.webpage) {
+                    const viewTrans = StrCast(this.Document._viewTransition);
+                    const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+                    const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+                    const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+                    if (duration) {
+                        smoothScroll(duration, this.webpage as any as HTMLElement, scrollTop);
+                        smoothScroll(duration, this._outerRef.current, scrollTop);
+                    } else {
+                        this.webpage.scrollTop = scrollTop;
+                        this._outerRef.current.scrollTop = scrollTop;
+                    }
+                }
+            },
+            { fireImmediately: true }
+        );
+        quickScroll = undefined;
     }
 
     componentWillUnmount() {
@@ -353,22 +350,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             }
         }, 1500);
     }
-
     onLongPressMove = (e: PointerEvent) => {
         // this._pressX = e.clientX;
         // this._pressY = e.clientY;
     }
-
     onLongPressUp = (e: PointerEvent) => {
-        if (this._longPressSecondsHack) {
-            clearTimeout(this._longPressSecondsHack);
-        }
-        if (this._iframeIndicatorRef.current) {
-            this._iframeIndicatorRef.current.classList.remove("active");
-        }
-        if (this._iframeDragRef.current) {
-            while (this._iframeDragRef.current.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
-        }
+        this._longPressSecondsHack && clearTimeout(this._longPressSecondsHack);
+        this._iframeIndicatorRef.current?.classList.remove("active");
+        while (this._iframeDragRef.current?.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
     }
 
     specificContextMenu = (e: React.MouseEvent): void => {
@@ -377,7 +366,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
         funcs.push({ description: (this.layoutDoc[this.fieldKey + "-contentWidth"] ? "Unfreeze" : "Freeze") + " Content Width", event: () => this.layoutDoc[this.fieldKey + "-contentWidth"] = this.layoutDoc[this.fieldKey + "-contentWidth"] ? undefined : Doc.NativeWidth(this.layoutDoc), icon: "snowflake" });
         cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-
     }
 
     @computed
@@ -389,7 +377,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         } else if (field instanceof WebField) {
             const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
             //    view = <iframe className="webBox-iframe" src={url} onLoad={e => { e.currentTarget.before((e.currentTarget.contentDocument?.body || e.currentTarget.contentDocument)?.children[0]!); e.currentTarget.remove(); }}
-
             view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
                 // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
                 // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
@@ -402,10 +389,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     @computed
     get content() {
-        const view = this.urlContent;
         const frozen = !this.props.isSelected() || DocumentDecorations.Instance?.Interacting;
         const scale = this.props.scaling?.() || 1;
-
         return (<>
             <div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
                 style={{
@@ -414,7 +399,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                     transform: `scale(${scale})`
                 }}
                 onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
-                {view}
+                {this.urlContent}
             </div>
             {!frozen ? (null) :
                 <div className="webBox-overlay" style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? undefined : "all" }}
@@ -429,7 +414,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     @computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); }
     @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
-
     @computed get annotationLayer() {
         TraceMobx();
         return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
@@ -452,7 +436,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
     panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
-    scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
+    scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     render() {
         const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
         const scale = this.props.scaling?.() || 1;
@@ -467,21 +451,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                         width: `${100 / scale}%`, height: `${100 / scale}%`, transform: `scale(${scale})`,
                         pointerEvents: this.layoutDoc.isAnnotating && !inactiveLayer ? "all" : "none"
                     }}
-                    onWheel={e => {
-                        const target = this._outerRef.current;
-                        if (this._forceSmoothScrollUpdate && target && this.webpage) {
-                            setTimeout(action(() => {
-                                target.scrollLeft = 0;
-                                const scrollTop = target.scrollTop;
-                                const scrollLeft = target.scrollLeft;
-                                this.webpage!.scrollTop = scrollTop;
-                                this.webpage!.scrollLeft = scrollLeft;
-                                if (this.layoutDoc._scrollTop !== scrollTop) this.layoutDoc._scrollTop = scrollTop;
-                                if (this.layoutDoc._scrollLeft !== scrollLeft) this.layoutDoc._scrollLeft = scrollLeft;
-                            }));
-                        }
-                        e.stopPropagation();
-                    }}
+                    onWheel={this.onWheel}
                     onPointerDown={this.onMarqueeDown}
                     onScroll={e => e.stopPropagation()}
                 >
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b3abcf90a..2409f36a0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -92,6 +92,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public static Instance: FormattedTextBox;
     public ProseRef?: HTMLDivElement;
     public get EditorView() { return this._editorView; }
+    public get SidebarKey() { return this.fieldKey + "-sidebar"; }
     private _boxRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -104,15 +105,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _recordingStart: number = 0;
     private _pause: boolean = false;
-    private _animatingScroll: number = 0; // hack to prevent scroll values from being written to document when scroll is animating
+    private _ignoreScroll = false;
 
     @computed get _recording() { return this.dataDoc?.audioState === "recording"; }
     set _recording(value) {
         this.dataDoc.audioState = value ? "recording" : undefined;
     }
 
-    @observable private _entered = false;
-
     public static FocusedBox: FormattedTextBox | undefined;
     public static SelectOnLoad = "";
     public static PasteOnLoad: ClipboardEvent | undefined;
@@ -164,29 +163,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     // 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();
         const state = this._editorView?.state;
-        if (state && linkDoc && this._editorView) {
-            var allLinks: any[] = [];
+        const a1 = linkDoc?.anchor1 as Doc;
+        const a2 = linkDoc?.anchor2 as Doc;
+        if (state && a1 && a2 && this._editorView) {
+            this.removeDocument(a1);
+            this.removeDocument(a2);
+            var allFoundLinkAnchors: any[] = [];
             state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
-                const foundMark = findLinkMark(node.marks);
-                const newHrefs = foundMark?.attrs.allLinks.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
-                allLinks = newHrefs.length ? newHrefs : allLinks;
+                const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
+                allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
                 return true;
             });
-            if (allLinks.length) {
-                this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allLinks }));
+            if (allFoundLinkAnchors.length) {
+                this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allAnchors: allFoundLinkAnchors }));
             }
         }
     }
-    // removes all the specified link referneces 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 RemoveLinkFromSelection(allLinks: { 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, { allLinks }));
+            this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors }));
         }
     }
 
+    getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection");
+
     linkOnDeselect: Map<string, string> = new Map();
 
     doLinkOnDeselect() {
@@ -234,11 +239,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
+                    const anchor = this.makeLinkAnchor(undefined, "add:right", undefined, "a link");
                     if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) {
-                        e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note");
+                        e.linkDocument = DocUtils.MakeLink({ doc: anchor }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note");
                         e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.rootDoc;
                     }
-                    e.linkDocument && e.annoDragData?.dropDocument && this.makeLinkToSelection(e.linkDocument[Id], "a link", "add:right", e.annoDragData.dropDocument[Id]);
                     e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument
                 }
             });
@@ -270,8 +275,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.linkOnDeselect.set(key, value);
 
                     const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-                    const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
-                    const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value });
+                    const allAnchors = [{ href: Utils.prepend("/doc/" + id), title: value, anchorId: id }];
+                    const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, location: "add:right", title: value });
                     const mval = this._editorView.state.schema.marks.metadataVal.create();
                     const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
                     tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -407,9 +412,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             res.map(r => r.map(h => flattened.push(h)));
             const lastSel = Math.min(flattened.length - 1, this._searchIndex);
             this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-            const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
-            const allLinks = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
-            const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, title: "a link", location });
+            const anchor = new Doc();
+            const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
+            const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }];
+            const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, title: "a link", location });
             this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
         }
     }
@@ -507,8 +513,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     linkDrop = (data: { linkDocument: Doc }) => {
         const anchor1Title = data.linkDocument.anchor1 instanceof Doc ? StrCast(data.linkDocument.anchor1.title) : "-untitled-";
-        const anchor1Id = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1[Id] : "";
-        this.makeLinkToSelection(data.linkDocument[Id], anchor1Title, "add:right", anchor1Id);
+        const anchor1 = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1 : undefined;
+        this.makeLinkAnchor(anchor1, "add:right", undefined, anchor1Title);
     }
 
     getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
@@ -825,37 +831,46 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         };
     }
 
-    makeLinkToSelection(linkId: string, title: string, location: string, targetId: string, targetHref?: string) {
+    makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
         const state = this._editorView?.state;
         if (state) {
-            const href = targetHref ?? Utils.prepend("/doc/" + linkId);
             const sel = state.selection;
             const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
             let tr = state.tr.addMark(sel.from, sel.to, splitter);
-            sel.from !== sel.to && 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.splitter.name)) {
-                    const allLinks = [{ href, title, targetId, linkId }];
-                    allLinks.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allLinks ?? []));
-                    const link = state.schema.marks.linkAnchor.create({ allLinks, title, location, linkId });
-                    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._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
-            this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+            if (sel.from !== sel.to) {
+                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument();
+                const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]);
+                if (anchor !== anchorDoc) this.addDocument(anchor);
+                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.splitter.name)) {
+                        const allAnchors = [{ href, title, anchorId: anchor[Id] }];
+                        allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
+                        const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location });
+                        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._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+                this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+                Doc.GetProto(anchor).title = this._editorView?.state.doc.textBetween(sel.from, sel.to);
+                return anchor;
+            }
+            return anchorDoc ?? this.rootDoc;
         }
+        return anchorDoc ?? this.rootDoc;
     }
 
     IsActive = () => {
         return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
     }
 
-    scrollToLinkId = (linkId: string) => {
-        const findLinkFrag = (frag: Fragment, editor: EditorView) => {
+    scrollFocus = (doc: Doc, smooth: boolean) => {
+        const anchorId = doc[Id];
+        const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
             const nodes: Node[] = [];
             let hadStart = start !== 0;
             frag.forEach((node, index) => {
-                const examinedNode = findLinkNode(node, editor);
+                const examinedNode = findAnchorNode(node, editor);
                 if (examinedNode?.node.textContent) {
                     nodes.push(examinedNode.node);
                     !hadStart && (start = index + examinedNode.start);
@@ -864,20 +879,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             });
             return { frag: Fragment.fromArray(nodes), start };
         };
-        const findLinkNode = (node: Node, editor: EditorView) => {
+        const findAnchorNode = (node: Node, editor: EditorView) => {
             if (!node.isText) {
-                const content = findLinkFrag(node.content, editor);
+                const content = findAnchorFrag(node.content, editor);
                 return { node: node.copy(content.frag), start: content.start };
             }
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
-            return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => linkId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
+            return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => anchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
         };
 
         let start = 0;
-        if (this._editorView && linkId) {
+        if (this._editorView && anchorId) {
             const editor = this._editorView;
-            const ret = findLinkFrag(editor.state.doc.content, editor);
+            const ret = findAnchorFrag(editor.state.doc.content, editor);
 
             if (ret.frag.size > 2 && ret.start >= 0) {
                 let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
@@ -885,16 +900,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
                 }
                 editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
-                const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-                setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
-                setTimeout(() => this.unhighlightSearchTerms(), 2000);
+                const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
+                addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" });
+                setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), 1500);
             }
-            Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
-            Doc.SetInPlace(this.layoutDoc, "_scrollToPreviewLinkID", undefined, false);
         }
     }
 
     componentDidMount() {
+        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.props.contentsActive?.(this.IsActive);
         this._cachedLinks = DocListCast(this.Document.links);
         this._disposers.sidebarheight = reaction(
@@ -916,27 +930,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }
         );
-        this._disposers.linkMaker = reaction(
-            () => this.props.makeLink?.(),
-            (linkDoc: Opt<Doc>) => {
-                if (linkDoc) {
-                    const a1 = Cast(linkDoc.anchor1, Doc, null);
-                    const a2 = Cast(linkDoc.anchor2, Doc, null);
-                    const otherAnchor = Doc.AreProtosEqual(a1, this.rootDoc) ? a2 : a1;
-                    const anchor2Title = StrCast(otherAnchor.title, "-untitled-");
-                    const anchor2Id = otherAnchor?.[Id] || "";
-                    this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id);
-                }
-            },
-            { fireImmediately: true }
-        );
         this._disposers.editorState = reaction(
             () => {
-                if (!this.dataDoc || !this.layoutDoc) return undefined;
-                if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey]) {
-                    return { data: Cast(this.dataDoc[this.props.fieldKey], RichTextField, null), str: StrCast(this.dataDoc[this.props.fieldKey]) };
-                }
-                return { data: Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null), str: StrCast(this.layoutDoc[this.props.fieldKey]) };
+                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 => {
                 if (this._editorView && this._applyingChange !== this.fieldKey) {
@@ -1013,38 +1012,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             );
         }
-        this._disposers.previewScrollToRegion = reaction(() => StrCast(this.layoutDoc._scrollToPreviewLinkID),
-            scrollToLinkID => this.props.renderDepth === -1 && scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true }
-        );
-        this._disposers.scrollToRegion = reaction(() => StrCast(this.layoutDoc.scrollToLinkID),
-            scrollToLinkID => scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true }
-        );
+        var quickScroll: string | undefined = "";
         this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
             pos => {
-                const durationMiliStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
-                const durationSecStr = StrCast(this.Document._viewTransition).match(/([0-9.]*)s/);
-                const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
-                if (duration) {
-                    this._animatingScroll++;
-                    this._scrollRef.current && smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
-                    setTimeout(() => this._animatingScroll--, duration);
-                } else {
-                    this._scrollRef.current?.scrollTo({ top: pos });
+                if (!this._ignoreScroll && this._scrollRef.current) {
+                    const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
+                    const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+                    const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+                    const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+                    if (duration) {
+                        smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
+                    } else {
+                        this._scrollRef.current.scrollTo({ top: pos });
+                    }
                 }
             }, { fireImmediately: true }
         );
-        this._disposers.scrollY = reaction(() => Cast(this.layoutDoc._scrollY, "number", null),
-            pos => {
-                this.Document._scrollY = undefined;
-                pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250);
-            }, { fireImmediately: true }
-        );
-        this._disposers.scrollPreviewY = reaction(() => Cast(this.layoutDoc.scrollPreviewY, "number", null),
-            pos => {
-                this.Document.scrollPreviewY = undefined;
-                pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250);
-            }, { fireImmediately: true }
-        );
+        quickScroll = undefined;
 
         setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight)));
     }
@@ -1228,8 +1212,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 handleScrollToSelection: (editorView) => {
                     const docPos = editorView.coordsAtPos(editorView.state.selection.from);
                     const viewRect = self._ref.current!.getBoundingClientRect();
-                    if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
-                        docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
+                    const scrollRef = self._scrollRef.current;
+                    if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) {
+                        const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale;
+                        if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
+                            scrollPos && smoothScroll(500, scrollRef, scrollPos);
+                        } else {
+                            scrollRef.scrollTo({ top: scrollPos });
+                        }
                     }
                     return true;
                 },
@@ -1423,6 +1413,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
+    static _highlightStyleSheet: any = addStyleSheet();
     static _bulletStyleSheet: any = addStyleSheet();
     static _userStyleSheet: any = addStyleSheet();
     _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason.  This flags the 2nd click as happening on a selection which allows bullet points to toggle
@@ -1609,9 +1600,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
         eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
     }
-    onscrolled = (ev: React.UIEvent) => {
-        if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && !this._animatingScroll) {
-            this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop;
+    onScroll = (ev: React.UIEvent) => {
+        if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this._scrollRef.current) {
+            this._ignoreScroll = true;
+            this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
+            this._ignoreScroll = false;
         }
     }
 
@@ -1655,7 +1648,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     @computed get sidebarHandle() {
-        const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length;
+        const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
         return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) :
             <div className="formattedTextBox-sidebar-handle"
                 style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }}
@@ -1675,9 +1668,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             xMargin: 0,
             yMargin: 0,
             chromeStatus: "enabled",
-            scaleField: this.annotationKey + "-scale",
+            scaleField: this.SidebarKey + "-scale",
             isAnnotationOverlay: true,
-            fieldKey: this.annotationKey,
+            fieldKey: this.SidebarKey,
             fitContentsToDoc: fitToBox,
             select: emptyFunction,
             active: this.annotationsActive,
@@ -1750,14 +1743,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     onPointerDown={this.onPointerDown}
                     onMouseUp={this.onMouseUp}
                     onWheel={this.onPointerWheel}
-                    onPointerEnter={action(() => this._entered = true)}
-                    onPointerLeave={action(e => {
-                        this._entered = false;
-                        const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
-                        for (let child: any = target; child; child = child?.parentElement) {
-                            child === this._ref.current! && (this._entered = true);
-                        }
-                    })}
                     onDoubleClick={this.onDoubleClick}
                 >
                     <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
@@ -1766,7 +1751,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
                             overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
                         }}
-                        onScroll={this.onscrolled} onDrop={this.ondrop} >
+                        onScroll={this.onScroll} onDrop={this.ondrop} >
                         <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
                             style={{
                                 padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${padding}px`,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 9508f8034..05f0fefd8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -227,7 +227,7 @@ export class FormattedTextBoxComment {
             state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             child = child || (nbef && state.selection.$from.nodeBefore);
             const mark = child ? findLinkMark(child.marks) : undefined;
-            const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href;
+            const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allAnchors.find((item: { href: string }) => item.href)?.href;
             if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
                 try {
                     ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
@@ -250,18 +250,16 @@ export class FormattedTextBoxComment {
                     FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
                 }
                 if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                    const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     FormattedTextBoxComment.tooltipText.textContent = "target not found...";
                     (FormattedTextBoxComment.tooltipText as any).href = "";
-                    docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
-                        if (linkDoc instanceof Doc) {
+                    anchorDoc && DocServer.GetRefField(anchorDoc).then(async anchor => {
+                        if (anchor instanceof Doc) {
+                            const linkDoc = DocListCast(anchor.links)[0];
                             (FormattedTextBoxComment.tooltipText as any).href = href;
                             FormattedTextBoxComment.linkDoc = linkDoc;
-                            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
-                            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
-                            if (anchor !== target && anchor && target) {
-                                target._scrollPreviewY = NumCast(anchor?.y);
-                            }
+                            const targetanchor = LinkManager.getOppositeAnchor(linkDoc, anchor);
+                            const target = targetanchor?.annotationOn ? await DocCastAsync(targetanchor.annotationOn) : targetanchor;
                             if (target?.author) {
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
 
@@ -295,7 +293,7 @@ export class FormattedTextBoxComment {
                                         </div>
                                     </div>
                                     <div className="FormattedTextBoxComment-preview-wrapper">
-                                        <DocumentView
+                                        <DocumentView ref={(r) => targetanchor && target !== targetanchor && r?.focus(targetanchor)}
                                             Document={target}
                                             moveDocument={returnFalse}
                                             rootSelected={returnFalse}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index dc630af74..b041c4fc9 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -806,7 +806,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
                 <p>Linked to:</p>
                 <input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
                 <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
-                <div className="divider"></div>
+                <div className="divider" />
                 <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
             </div>;
 
@@ -819,7 +819,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         const node = this.view.state.selection.$from.nodeAfter;
         const link = node && node.marks.find(m => m.type.name === "link");
         if (link) {
-            const href = link.attrs.allLinks.length > 0 ? link.attrs.allLinks[0].href : undefined;
+            const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
             if (href) {
                 if (href.indexOf(Utils.prepend("/doc/")) === 0) {
                     const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -851,21 +851,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     // TODO: should check for valid URL
     @undoBatch
     makeLinkToURL = (target: string, lcoation: string) => {
-        ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target);
+        ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", "", target);
     }
 
     @undoBatch
     @action
     deleteLink = () => {
         if (this.view) {
-            const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
-            if (link) {
-                const allLinks = link.attrs.allLinks.slice();
-                this.TextView.RemoveLinkFromSelection(link.attrs.allLinks);
+            const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
+            if (linkAnchor) {
+                const allAnchors = linkAnchor.attrs.allAnchors.slice();
+                this.TextView.RemoveAnchorFromSelection(allAnchors);
                 // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
-                allLinks.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
-                    const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+                allAnchors.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
+                    const anchorId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
                 });
             }
         }
@@ -877,8 +877,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         let startIndex = $start.index();
         let endIndex = $start.indexAfter();
 
-        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) startIndex--;
-        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) endIndex++;
+        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--;
+        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++;
 
         let startPos = $start.start();
         let endPos = startPos;
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ea239e4d3..aceba0387 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -22,7 +22,7 @@ export const marks: { [index: string]: MarkSpec } = {
     // element.
     linkAnchor: {
         attrs: {
-            allLinks: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
+            allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
             showPreview: { default: true },
             location: { default: null },
             title: { default: null },
@@ -31,17 +31,23 @@ export const marks: { [index: string]: MarkSpec } = {
         inclusive: false,
         parseDOM: [{
             tag: "a[href]", getAttrs(dom: any) {
-                return { allLinks: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.dataset.targetids }], location: dom.getAttribute("location"), };
+                return {
+                    allAnchors: [{ href: dom.getAttribute("shref"), title: dom.getAttribute("title"), anchorId: dom.getAttribute("class") }],
+                    location: dom.getAttribute("location"),
+                };
             }
         }],
         toDOM(node: any) {
-            const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
-            const targethrefs = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.href, "");
-            const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
+            const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, "");
+            const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, "");
             return node.attrs.docref && node.attrs.title ?
-                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
+                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", {
+                    ...node.attrs,
+                    class: "prosemirror-attribution",
+                    href: node.attrs.allAnchors[0].href,
+                }, node.attrs.title], ["br"]] :
                 //node.attrs.allLinks.length === 1 ?
-                ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, "data-targethrefs": targethrefs, title: `${node.attrs.title}`, href: node.attrs.allLinks[0]?.href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0];
+                ["a", { ...node.attrs, class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, href: node.attrs.allAnchors[0]?.href, style: `text-decoration: "underline"` }, 0];
             // ["div", { class: "prosemirror-anchor" },
             //     ["span", { class: "prosemirror-linkBtn" },
             //         ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index eadf58027..0477192d5 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -23,8 +23,6 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { MarqueeAnnotator } from "../MarqueeAnnotator";
 import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment";
-import { LinkDocPreview } from "../nodes/LinkDocPreview";
 import { Annotation } from "./Annotation";
 import { AnchorMenu } from "./AnchorMenu";
 import "./PDFViewer.scss";
@@ -88,6 +86,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     private _coverPath: any;
     private _lastSearch = false;
     private _viewerIsSetup = false;
+    private _ignoreScroll = false;
+    private _smoothScrolling = true;
 
     @computed get allAnnotations() {
         return DocUtils.FilterDocs(DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined);
@@ -119,12 +119,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         }
         runInAction(() => this._showWaiting = true);
         this.props.startupLive && this.setupPdfJsViewer();
-        if (this._mainCont.current) {
-            this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0;
-            const observer = new _global.ResizeObserver(action((entries: any) => this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0)));
-            observer.observe(this._mainCont.current);
-            this._mainCont.current.addEventListener("scroll", (e) => (e.target as any).scrollLeft = 0);
-        }
+        this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0);
 
         this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc),
             m => {
@@ -141,36 +136,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 (SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
             },
             { fireImmediately: true });
-        this._disposers.scrollY = reaction(
-            () => this.Document._scrollY,
-            (scrollY) => {
-                if (scrollY !== undefined) {
-                    (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
-                    if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
-                        const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll
-                        const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
-                        const duration = durationStr ? Number(durationStr[1]) : 1000;
-                        setTimeout(() => this.Document._scrollY = undefined, duration + delay);
-                        setTimeout(() => this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(scrollY || 0), () => this.layoutDoc._scrollTop = scrollY), delay);
-                    }
-                }
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.scrollPreviewY = reaction(
-            () => Cast(this.Document._scrollPreviewY, "number", null),
-            (scrollY) => {
-                if (scrollY !== undefined) {
-                    (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
-                    if (this.props.renderDepth === -1 && scrollY >= 0) {
-                        if (!this._mainCont.current) setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, scrollY || 0));
-                        else smoothScroll(1000, this._mainCont.current, scrollY || 0);
-                        this.Document._scrollPreviewY = undefined;
-                    }
-                }
-            },
-            { fireImmediately: true }
-        );
         this._disposers.curPage = reaction(
             () => this.Document._curPage,
             (page) => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
@@ -212,6 +177,19 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         }
     }
 
+    // scrolls to focus on a nested annotation document.  if this is part a link preview then it will jump to the scroll location,
+    // otherwise it will scroll smoothly.
+    scrollFocus = (doc: Doc, smooth: boolean) => {
+        const mainCont = this._mainCont.current;
+        if (mainCont) {
+            if (smooth) {
+                smoothScroll(500, mainCont, NumCast(doc.y));
+            } else {
+                mainCont.scrollTop = NumCast(doc.y);
+            }
+        }
+    }
+
     @action
     setupPdfJsViewer = async () => {
         if (this._viewerIsSetup) return;
@@ -220,14 +198,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         this.props.setPdfViewer(this);
         await this.initialLoad();
 
-        this._disposers.scrollTop = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null),
-            (stop) => {
-                if (stop !== undefined && this.layoutDoc._scrollY === undefined && this._mainCont.current) {
-                    (this._mainCont.current.scrollTop = stop);
-                }
-            },
-            { fireImmediately: true });
-
         this._disposers.filterScript = reaction(
             () => Cast(this.Document.filterScript, ScriptField),
             action(scriptField => {
@@ -242,13 +212,40 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         this.createPdfViewer();
     }
 
-    pagesinit = action(() => {
+    pagesinit = () => {
         if (this._pdfViewer._setDocumentViewerElement.offsetParent) {
-            this._pdfViewer.currentScaleValue = this._zoomed = 1;
+            runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1);
             this.gotoPage(this.Document._curPage || 1);
         }
         document.removeEventListener("pagesinit", this.pagesinit);
-    });
+        var quickScroll: string | undefined = "";
+        this._disposers.scroll = reaction(
+            () => NumCast(this.Document._scrollTop),
+            (pos) => {
+                if (!this._ignoreScroll) {
+                    (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+                    const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
+                    const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll
+                    const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+                    const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+                    const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+                    if (duration) {
+                        this._smoothScrolling = true;
+                        setTimeout(() => {
+                            this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(pos || 0));
+                            setTimeout(() => this._smoothScrolling = false, duration);
+                        }, delay);
+                    } else {
+                        this._smoothScrolling = true;
+                        this._mainCont.current?.scrollTo({ top: Math.abs(pos || 0) });
+                        this._smoothScrolling = false;
+                    }
+                }
+            },
+            { fireImmediately: true }
+        );
+        quickScroll = undefined;
+    }
 
     createPdfViewer() {
         if (!this._mainCont.current) { // bcz: I don't think this is ever triggered or needed
@@ -301,30 +298,19 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         }
     }
 
-    @action
-    scrollToFrame = (duration: number, top: number) => {
-        this._mainCont.current && smoothScroll(duration, this._mainCont.current, top);
-    }
-
     @action
     scrollToAnnotation = (scrollToAnnotation: Doc) => {
         if (scrollToAnnotation) {
-            const offset = (this.props.PanelHeight() / this.contentScaling) / 2;
-            this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset);
+            this.scrollFocus(scrollToAnnotation, true);
             Doc.linkFollowHighlight(scrollToAnnotation);
         }
     }
 
-    pageDelay: any;
-    @action
     onScroll = (e: React.UIEvent<HTMLElement>) => {
-        if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
-            this.pageDelay && clearTimeout(this.pageDelay);
-            this.pageDelay = setTimeout(() => {
-                this.Document._scrollY === undefined && this._mainCont.current && (this.layoutDoc._scrollTop = this._mainCont.current.scrollTop);
-                this.pageDelay = undefined;
-                //this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber);
-            }, 1000);
+        if (this._mainCont.current && !this._smoothScrolling) {
+            this._ignoreScroll = true;
+            this.layoutDoc._scrollTop = this._mainCont.current.scrollTop;
+            this._ignoreScroll = false;
         }
     }
 
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index b10c2b015..f35c85110 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -26,10 +26,7 @@ export const documentSchema = createSchema({
     y: "number",                // y coordinate when in a freeform view 
     z: "number",                // z "coordinate" - non-zero specifies the overlay layer of a freeformview
     zIndex: "number",           // zIndex of a document in a freeform view
-    _scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
-    _scrollX: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
-    _scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
-    _scrollLeft: "number",        // scroll position of a scrollable document (pdf, text, web)
+    _scrollTop: "number",       // scroll position of a scrollable document (pdf, text, web)
 
     // appearance properties on the layout
     "_backgroundGrid-spacing": "number", // the size of the grid for collection views
@@ -117,7 +114,7 @@ export const collectionSchema = createSchema({
     childLayoutTemplate: Doc, // layout template to use to render children of a collecion
     childLayoutString: "string", //layout string to use to render children of a collection
     childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
-    dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string",   // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
+    dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToAnchorID: "string",   // id of anchor being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToAnchorID should be set to undefined by this doc after it sets up its scroll,etc.
     onChildClick: ScriptField, // script to run for each child when its clicked
     onChildDoubleClick: ScriptField, // script to run for each child when its clicked
     onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ecb3fb343..b9c5a13c1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -96,7 +96,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
         } else {
             DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
         }
-        !receiver[UpdatingFromServer] && UndoManager.AddEvent({
+        (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({
             redo: () => receiver[prop] = value,
             undo: () => receiver[prop] = curValue
         });
-- 
cgit v1.2.3-70-g09d2


From 81bd2378ffa753e851390c2616e66a71d23c9989 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 9 Feb 2021 12:16:25 -0500
Subject: started to cleanup LinkDocPreview, LinkMenu.

---
 src/client/util/LinkManager.ts                     |   1 -
 src/client/views/DocumentDecorations.scss          |   2 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/linking/LinkMenu.scss             |  40 ++------
 src/client/views/linking/LinkMenu.tsx              |  76 +++++---------
 src/client/views/linking/LinkMenuGroup.tsx         |  10 +-
 src/client/views/linking/LinkMenuItem.tsx          | 112 +++++++--------------
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  10 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   4 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  30 ++----
 src/client/views/nodes/VideoBox.tsx                |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 .../formattedText/FormattedTextBoxComment.tsx      |  10 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   2 +-
 .../views/nodes/formattedText/TooltipTextMenu.scss |   2 +-
 17 files changed, 105 insertions(+), 210 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index afc0e76be..ecf245d03 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -129,7 +129,6 @@ export class LinkManager {
         await LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
     public static async traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
-        LinkDocPreview.TargetDoc = undefined;
         FormattedTextBoxComment.linkDoc = undefined;
         const linkDocs = link ? [link] : DocListCast(doc.links);
         const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 461ea0fef..de8a3c909 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -4,7 +4,7 @@ $linkGap : 3px;
 
 .documentDecorations {
     position: absolute;
-    z-index: 20000;
+    z-index: 2000;
 }
 
 .documentDecorations-container {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bc3d05005..d53e66c47 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -625,7 +625,7 @@ export class MainView extends React.Component {
             {this.search}
             <CollectionMenu />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
-            {DocumentLinksButton.EditLink ? <LinkMenu docView={DocumentLinksButton.EditLink} docprops={DocumentLinksButton.EditLink.props} changeFlyout={emptyFunction} /> : (null)}
+            {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} docprops={LinkDocPreview.LinkInfo.docprops}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href} /> : (null)}
             <GestureOverlay >
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index ebf8878cb..a90bf8b0a 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -4,29 +4,23 @@
     width: auto;
     height: auto;
     position: absolute;
-    z-index: 20001;
-
-    .linkMenu-list {
+    z-index: 2001;
 
+    .linkMenu-list,
+    .linkMenu-listEditor
+     {
         display: inline-block;
-
+        position: relative;
         border: 1px solid black;
-
         box-shadow: 3px 3px 1.5px grey;
+        background: white;
 
+        min-width: 170px;
         max-height: 170px;
         overflow-y: scroll;
-        position: relative;
         z-index: 10;
-        background: white;
-        min-width: 170px;
-        //border-radius: 5px;
-        //padding-top: 6.5px;
-        //padding-bottom: 6.5px;
-        //padding-left: 6.5px;
-        //padding-right: 2px;
-        //width: calc(auto + 50px);
-
+     }
+     .linkMenu-list {
         white-space: nowrap;
         overflow-x: hidden;
         width: 240px;
@@ -40,22 +34,6 @@
             scrollbar-color: rgb(201, 239, 252);
         }
     }
-
-    .linkMenu-listEditor {
-
-        display: inline-block;
-
-        border: 1px solid black;
-
-        box-shadow: 3px 3px 1.5px grey;
-
-        max-height: 170px;
-        overflow-y: scroll;
-        position: relative;
-        z-index: 10;
-        background: white;
-        min-width: 170px;
-    }
 }
 
 .linkMenu-group {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index b32022024..8cd069210 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -13,49 +13,28 @@ import React = require("react");
 interface Props {
     docView: DocumentView;
     changeFlyout: () => void;
-    docprops: DocumentViewSharedProps;
 }
 
 @observer
 export class LinkMenu extends React.Component<Props> {
-
-    @observable private _editingLink?: Doc;
-    @observable private _linkMenuRef = React.createRef<HTMLDivElement>();
     private _editorRef = React.createRef<HTMLDivElement>();
+    @observable _editingLink?: Doc;
+    @observable _linkMenuRef = React.createRef<HTMLDivElement>();
 
-    //@observable private _numLinks: number = 0;
-
-    // @computed get overflow() {
-    //     if (this._numLinks) {
-    //         return "scroll";
-    //     }
-    //     return "auto";
-    // }
-
-    @action
-    onClick = (e: PointerEvent) => {
-
-        LinkDocPreview.LinkInfo = undefined;
-
-
-        if (this._linkMenuRef && !this._linkMenuRef.current?.contains(e.target as any)) {
-            if (this._editorRef && !this._editorRef.current?.contains(e.target as any)) {
-                DocumentLinksButton.EditLink = undefined;
-            }
-        }
-    }
-    @action
-    componentDidMount() {
-        this._editingLink = undefined;
-        document.addEventListener("pointerdown", this.onClick);
+    @computed get position() {
+        return ((dv) => ({ x: dv?.left || 0, y: dv?.top || 0, r: dv?.right || 0, b: dv?.bottom || 0 }))(this.props.docView.getBounds());
     }
 
-    componentWillUnmount() {
-        document.removeEventListener("pointerdown", this.onClick);
-    }
+    componentDidMount() { document.addEventListener("pointerdown", this.onPointerDown); }
+    componentWillUnmount() { document.removeEventListener("pointerdown", this.onPointerDown); }
 
-    clearAllLinks = () => {
-        LinkManager.Instance.deleteAllLinksOnAnchor(this.props.docView.props.Document);
+    onPointerDown = (e: PointerEvent) => {
+        LinkDocPreview.Clear();
+        if (this._linkMenuRef && this._editorRef &&
+            !this._linkMenuRef.current?.contains(e.target as any) &&
+            !this._editorRef.current?.contains(e.target as any)) {
+            DocumentLinksButton.ClearLinkEditor();
+        }
     }
 
     renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
@@ -66,32 +45,23 @@ export class LinkMenu extends React.Component<Props> {
                 sourceDoc={this.props.docView.props.Document}
                 group={group[1]}
                 groupType={group[0]}
-                showEditor={action(linkDoc => this._editingLink = linkDoc)}
-                docprops={this.props.docprops} />);
+                showEditor={action(linkDoc => this._editingLink = linkDoc)} />);
 
         return linkItems.length ? linkItems : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
     }
 
-    @computed
-    get position() {
-        const docView = this.props.docView.getBounds();
-        return { x: docView?.left || 0, y: docView?.top || 0, r: docView?.right || 0, b: docView?.bottom || 0 };
-    }
-
     render() {
         const sourceDoc = this.props.docView.props.Document;
-        const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
-        return <div className="linkMenu" style={{ left: this.position.x, top: this.props.docView.topMost ? undefined : this.position.b + 15, bottom: this.props.docView.topMost ? 20 : undefined }} ref={this._linkMenuRef} >
-            {!this._editingLink ?
-                <div className="linkMenu-list" >
-                    {this.renderAllGroups(groups)}
-                </div> :
+        return <div className="linkMenu" ref={this._linkMenuRef}
+            style={{ left: this.position.x, top: this.props.docView.topMost ? undefined : this.position.b + 15, bottom: this.props.docView.topMost ? 20 : undefined }}
+        >
+            {this._editingLink ?
                 <div className="linkMenu-listEditor">
-                    <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
-                        showLinks={action(() => this._editingLink = undefined)} />
-                </div>
-            }
-
+                    <LinkEditor sourceDoc={sourceDoc} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
+                </div> :
+                <div className="linkMenu-list" >
+                    {this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}
+                </div>}
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 7db908393..74af78234 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -3,7 +3,7 @@ import { Doc } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { Cast } from "../../../fields/Types";
 import { LinkManager } from "../../util/LinkManager";
-import { DocumentView, DocumentViewSharedProps } from "../nodes/DocumentView";
+import { DocumentView } from "../nodes/DocumentView";
 import './LinkMenu.scss';
 import { LinkMenuItem } from "./LinkMenuItem";
 import React = require("react");
@@ -13,14 +13,11 @@ interface LinkMenuGroupProps {
     group: Doc[];
     groupType: string;
     showEditor: (linkDoc: Doc) => void;
-    docprops: DocumentViewSharedProps;
     docView: DocumentView;
 }
 
 @observer
 export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
-
-    private _drag = React.createRef<HTMLDivElement>();
     private _menuRef = React.createRef<HTMLDivElement>();
 
     render() {
@@ -31,7 +28,6 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             if (destination && this.props.sourceDoc) {
                 return <LinkMenuItem key={linkDoc[Id]}
                     groupType={this.props.groupType}
-                    docprops={this.props.docprops}
                     docView={this.props.docView}
                     linkDoc={linkDoc}
                     sourceDoc={this.props.sourceDoc}
@@ -43,11 +39,9 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
         return (
             <div className="linkMenu-group" ref={this._menuRef}>
-
                 <div className="linkMenu-group-name">
-                    <p ref={this._drag} className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
+                    <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
                 </div>
-
                 <div className="linkMenu-group-wrapper">
                     {groupItems}
                 </div>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index e9a0486d1..e9866f6e3 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -6,7 +6,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { Cast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
-import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, setupMoveUpEvents, returnFalse } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
@@ -18,6 +18,7 @@ import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
 import React = require("react");
+import { setup } from 'mocha';
 
 
 interface LinkMenuItemProps {
@@ -27,7 +28,6 @@ interface LinkMenuItemProps {
     sourceDoc: Doc;
     destinationDoc: Doc;
     showEditor: (linkDoc: Doc) => void;
-    docprops: DocumentViewSharedProps;
     menuRef: React.Ref<HTMLDivElement>;
 }
 
@@ -69,9 +69,6 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
 @observer
 export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     private _drag = React.createRef<HTMLDivElement>();
-    private _downX = 0;
-    private _downY = 0;
-    private _eleClone: any;
 
     _editRef = React.createRef<HTMLDivElement>();
     _buttonRef = React.createRef<HTMLDivElement>();
@@ -81,84 +78,45 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     onEdit = (e: React.PointerEvent): void => {
         LinkManager.currentLink = this.props.linkDoc;
-        setupMoveUpEvents(this, e, this.editMoved, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
-    }
-
-    editMoved = (e: PointerEvent) => {
-        const dragData = new DragManager.DocumentDragData([this.props.linkDoc]);
-        DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
-        return true;
+        setupMoveUpEvents(this, e, e => {
+            DragManager.StartDocumentDrag([this._editRef.current!], new DragManager.DocumentDragData([this.props.linkDoc]), e.x, e.y);
+            return true;
+        }, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
     }
 
-    @action
     onLinkButtonDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        this._eleClone = this._drag.current!.cloneNode(true);
-        e.stopPropagation();
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
-        document.addEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp);
-        document.addEventListener("pointerup", this.onLinkButtonUp);
-
-        if (this._buttonRef && !!!this._buttonRef.current?.contains(e.target as any)) {
-            LinkDocPreview.LinkInfo = undefined;
-        }
-    }
-
-    onLinkButtonUp = action((e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp);
-        LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
-        LinkDocPreview.LinkInfo = undefined;
-        DocumentLinksButton.EditLink = undefined;
-
-        e.stopPropagation();
-    });
-
-    onLinkButtonMoved = async (e: PointerEvent) => {
-        if (this._drag.current !== null && Math.abs((e.clientX - this._downX) * (e.clientX - this._downX) + (e.clientY - this._downY) * (e.clientY - this._downY)) > 5) {
-            document.removeEventListener("pointermove", this.onLinkButtonMoved);
-            document.removeEventListener("pointerup", this.onLinkButtonUp);
-
-            this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
-            StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
-        }
-        e.stopPropagation();
+        setupMoveUpEvents(this, e,
+            e => {
+                const eleClone: any = this._drag.current!.cloneNode(true);
+                eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
+                StartLinkTargetsDrag(eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
+                DocumentLinksButton.ClearLinkEditor();
+                return true;
+            },
+            emptyFunction,
+            () => {
+                DocumentLinksButton.ClearLinkEditor();
+                LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
+            });
     }
 
-    @undoBatch
-    @action
     deleteLink = (e: React.PointerEvent): void => {
-        this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
-        LinkManager.Instance.deleteLink(this.props.linkDoc);
-        e.stopPropagation();
-
-        runInAction(() => {
-            LinkDocPreview.LinkInfo = undefined;
-            DocumentLinksButton.EditLink = undefined;
-        });
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => {
+            this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+            LinkManager.Instance.deleteLink(this.props.linkDoc);
+        })));
     }
 
-    @undoBatch
-    @action
     autoMove = (e: React.PointerEvent) => {
-        e.stopPropagation();
-        this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove;
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)));
     }
 
-    @undoBatch
-    @action
     showLink = (e: React.PointerEvent) => {
-        e.stopPropagation();
-        this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay;
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)));
     }
 
-    @undoBatch
-    @action
     showAnchor = (e: React.PointerEvent) => {
-        e.stopPropagation();
-        this.props.linkDoc.hidden = !this.props.linkDoc.hidden;
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.hidden = !this.props.linkDoc.hidden)));
     }
 
     render() {
@@ -180,13 +138,13 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                 <div className={"linkMenu-item-content expand-two"}>
 
                     <div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
-                        onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
-                        onPointerEnter={action(e => this.props.linkDoc && (LinkDocPreview.LinkInfo = {
-                            docprops: this.props.docprops,
+                        onPointerLeave={LinkDocPreview.Clear}
+                        onPointerEnter={e => this.props.linkDoc && LinkDocPreview.SetLinkInfo({
+                            docprops: this.props.docView.props,
                             linkSrc: this.props.sourceDoc,
                             linkDoc: this.props.linkDoc,
                             Location: [e.clientX, e.clientY + 20]
-                        }))}
+                        })}
                         onPointerDown={this.onLinkButtonDown}>
 
                         <div className="linkMenu-text">
@@ -205,26 +163,26 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                         <div className="linkMenu-item-buttons" ref={this._buttonRef} >
 
                             <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}</div></>}>
-                                <div className="button" ref={this._editRef} onPointerDown={this.showAnchor}>
+                                <div className="button" ref={this._editRef} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.hidden ? "eye-slash" : "eye"} size="sm" /></div>
                             </Tooltip>
 
                             <Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}</div></>}>
-                                <div className="button" ref={this._editRef} onPointerDown={this.showLink}>
+                                <div className="button" ref={this._editRef} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="fa-icon" icon={!this.props.linkDoc.linkDisplay ? "eye-slash" : "eye"} size="sm" /></div>
                             </Tooltip>
 
                             <Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}</div></>}>
-                                <div className="button" ref={this._editRef} onPointerDown={this.autoMove}>
+                                <div className="button" ref={this._editRef} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.linkAutoMove ? "play" : "pause"} size="sm" /></div>
                             </Tooltip>
 
                             <Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
-                                <div className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+                                <div className="button" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
                             </Tooltip>
                             <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>}>
-                                <div className="button" onPointerDown={this.deleteLink}>
+                                <div className="button" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
                             </Tooltip>
                         </div>
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 692eaae66..fbe289f64 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -107,7 +107,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         //this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
 
         this._disposers.triggerAudio = reaction(
-            () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
+            () => !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
             start => start !== undefined && setTimeout(() => {
                 this.playFrom(start);
                 setTimeout(() => {
@@ -119,7 +119,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         );
 
         this._disposers.audioStop = reaction(
-            () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._audioStop, "number", null) : undefined,
+            () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._audioStop, "number", null) : undefined,
             audioStop => audioStop !== undefined && setTimeout(() => {
                 this.Pause();
                 setTimeout(() => this.Document._audioStop = undefined, 10);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index defa4dbf0..13a6c9df8 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -33,16 +33,16 @@ interface DocumentLinksButtonProps {
 @observer
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
-
     @observable public static StartLink: Doc | undefined;
     @observable public static StartLinkView: DocumentView | undefined;
     @observable public static AnnotationId: string | undefined;
     @observable public static AnnotationUri: string | undefined;
-    @observable public static EditLink: DocumentView | undefined;
+    @observable public static LinkEditorDocView: DocumentView | undefined;
 
     @observable public static invisibleWebDoc: Opt<Doc>;
     public static invisibleWebRef = React.createRef<HTMLDivElement>();
 
+    @action public static ClearLinkEditor() { DocumentLinksButton.LinkEditorDocView = undefined; }
     @action @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this.props.InMenu && this.props.StartLink) {
@@ -83,7 +83,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     DocumentLinksButton.StartLinkView = this.props.View;
                 }
             } else if (!this.props.InMenu) {
-                DocumentLinksButton.EditLink = this.props.View;
+                DocumentLinksButton.LinkEditorDocView = this.props.View;
             }
         }));
     }
@@ -103,7 +103,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
             //action(() => Doc.BrushDoc(this.props.View.Document));
         } else if (!this.props.InMenu) {
-            DocumentLinksButton.EditLink = this.props.View;
+            DocumentLinksButton.LinkEditorDocView = this.props.View;
         }
     }
 
@@ -264,7 +264,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     {this.linkButtonInner}
                 </Tooltip>
                 :
-                !DocumentLinksButton.EditLink && !this.props.InMenu ?
+                !DocumentLinksButton.LinkEditorDocView && !this.props.InMenu ?
                     <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
                         {this.linkButtonInner}
                     </Tooltip>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bfdacdb45..f56246e9a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -377,7 +377,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => {
-        this._componentView?.scrollFocus?.(doc, !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+        this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc); // bcz: smooth parameter should really be passed into focus() instead of inferred here
         return this.props.focus(doc, willZoom, scale, afterFocus, dontCenter, focused);
     }
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index ca6c71ea3..673c936ce 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -135,8 +135,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             </div>
         );
         return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`}
-            onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
-            onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
+            onPointerLeave={LinkDocPreview.Clear}
+            onPointerEnter={e => LinkDocPreview.SetLinkInfo({
                 docprops: this.props,
                 linkSrc: linkSource,
                 linkDoc: this.rootDoc,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 8051568ff..4808feb47 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -22,7 +22,10 @@ interface Props {
 }
 @observer
 export class LinkDocPreview extends React.Component<Props> {
-    static TargetDoc: Doc | undefined;
+    @action public static Clear() { LinkDocPreview.LinkInfo = undefined; }
+    @action public static SetLinkInfo(info: { linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }) {
+        LinkDocPreview.LinkInfo = info;
+    }
     @observable public static LinkInfo: Opt<{ linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }>;
     @observable _targetDoc: Opt<Doc>;
     @observable _toolTipText = "";
@@ -30,28 +33,13 @@ export class LinkDocPreview extends React.Component<Props> {
     _editRef = React.createRef<HTMLDivElement>();
 
     @action
-    onContextMenu = (e: React.MouseEvent) => {
-        DocumentLinksButton.EditLink = undefined;
-        LinkDocPreview.LinkInfo = undefined;
-        e.preventDefault();
-        ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
-        ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
-    }
-
-    @action.bound
-    async followDefault() {
-        DocumentLinksButton.EditLink = undefined;
-        LinkDocPreview.LinkInfo = undefined;
-        this._targetDoc && LinkManager.FollowLink(this.props.linkDoc, this._targetDoc, this.props.docprops, false);
-    }
-    componentWillUnmount() { LinkDocPreview.TargetDoc = undefined; }
+    componentWillUnmount() { LinkDocPreview.LinkInfo = undefined; }
 
     componentDidUpdate() { this.updatePreview(); }
     componentDidMount() { this.updatePreview(); }
     async updatePreview() {
         const linkDoc = this.props.linkDoc;
         const linkSrc = this.props.linkSrc;
-        LinkDocPreview.TargetDoc = undefined;
         if (this.props.href) {
             if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) {
                 wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
@@ -65,7 +53,7 @@ export class LinkDocPreview extends React.Component<Props> {
             const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
             runInAction(() => {
                 this._toolTipText = "";
-                LinkDocPreview.TargetDoc = this._targetDoc = target;
+                this._targetDoc = target;
             });
         }
     }
@@ -119,9 +107,11 @@ export class LinkDocPreview extends React.Component<Props> {
             style={{
                 position: "absolute", left: this.props.location[0],
                 top: this.props.location[1], width: this.width() + 16, height: this.height() + 16,
-                zIndex: 1000,
+                zIndex: 2004,
+                pointerEvents: "none",
                 backgroundColor: "lightblue",
-                border: "8px solid white", borderRadius: "7px",
+                border: "8px solid white",
+                borderRadius: "7px",
                 boxShadow: "3px 3px 1.5px grey",
                 borderBottom: "8px solid white", borderRight: "8px solid white"
             }}>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 760620b19..c21701f32 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -211,7 +211,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             },
             { fireImmediately: true });
         this._disposers.triggerVideo = reaction(
-            () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
+            () => !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
             time => time !== undefined && setTimeout(() => {
                 this.player && this.Play();
                 setTimeout(() => this.Document._triggerVideo = undefined, 10);
@@ -219,7 +219,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             { fireImmediately: true }
         );
         this._disposers.triggerStop = reaction(
-            () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? NumCast(this.Document._triggerVideoStop, null) : undefined,
+            () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc ? NumCast(this.Document._triggerVideoStop, null) : undefined,
             stop => stop !== undefined && setTimeout(() => {
                 this.player && this.Pause();
                 setTimeout(() => this.Document._triggerVideoStop = undefined, 10);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2409f36a0..0374b1426 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1215,7 +1215,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     const scrollRef = self._scrollRef.current;
                     if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) {
                         const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale;
-                        if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
+                        if (!LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc) {
                             scrollPos && smoothScroll(500, scrollRef, scrollPos);
                         } else {
                             scrollRef.scrollTo({ top: scrollPos });
@@ -1601,7 +1601,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
     }
     onScroll = (ev: React.UIEvent) => {
-        if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this._scrollRef.current) {
+        if (!LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc && this._scrollRef.current) {
             this._ignoreScroll = true;
             this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
             this._ignoreScroll = false;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 05f0fefd8..d1bb2ad84 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -136,8 +136,6 @@ export class FormattedTextBoxComment {
     @undoBatch
     deleteLink = action(() => {
         FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
-        LinkDocPreview.LinkInfo = undefined;
-        DocumentLinksButton.EditLink = undefined;
         FormattedTextBoxComment.Hide();
     });
 
@@ -158,6 +156,7 @@ export class FormattedTextBoxComment {
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
     }
 
+    @action
     static showCommentbox(set: string, view: EditorView, nbef: number) {
         const state = view.state;
         if (set !== "none") {
@@ -171,6 +170,13 @@ export class FormattedTextBoxComment {
             const left = Math.max((start.left + end.left) / 2, start.left + 3);
             FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
             FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+            // const props = FormattedTextBoxComment.textBox?.props.docViewPath.lastElement().props;
+            // props && (LinkDocPreview.SetLinkInfo({
+            //     docprops: props,
+            //     linkSrc: props.Document,
+            //     linkDoc: FormattedTextBoxComment.linkDoc,
+            //     Location: [start.left, start.top + 25]
+            // });
         }
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
     }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index b041c4fc9..5da868281 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -851,7 +851,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     // TODO: should check for valid URL
     @undoBatch
     makeLinkToURL = (target: string, lcoation: string) => {
-        ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", "", target);
+        ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
index e2149e9c1..0e4b752ac 100644
--- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -103,7 +103,7 @@
 }
 
 .tooltipMenu, .basic-tools {
-    z-index: 20000;
+    z-index: 3000;
     pointer-events: all;
     padding: 3px;
     padding-bottom: 5px;
-- 
cgit v1.2.3-70-g09d2


From 5fd9ffcc2bb62faa664f33a817ae1fd51c36ef98 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 9 Feb 2021 19:11:28 -0500
Subject: overhaul of linkdocpreview.  delegated linkpreviewing from
 formattedTextBoxComments to LinkDocPreview

---
 src/client/util/LinkManager.ts                     |   1 -
 src/client/views/MainView.tsx                      |   3 +-
 src/client/views/linking/LinkMenu.tsx              |   3 +-
 src/client/views/linking/LinkMenuItem.tsx          |   3 +-
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   3 +-
 src/client/views/nodes/LinkDocPreview.scss         |  69 ++++++
 src/client/views/nodes/LinkDocPreview.tsx          | 232 ++++++++++++-------
 src/client/views/nodes/VideoBox.tsx                |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  13 +-
 .../formattedText/FormattedTextBoxComment.scss     |  76 +-----
 .../formattedText/FormattedTextBoxComment.tsx      | 255 ++++-----------------
 13 files changed, 283 insertions(+), 385 deletions(-)
 create mode 100644 src/client/views/nodes/LinkDocPreview.scss

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ecf245d03..c32a78ef3 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -129,7 +129,6 @@ export class LinkManager {
         await LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
     public static async traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
-        FormattedTextBoxComment.linkDoc = undefined;
         const linkDocs = link ? [link] : DocListCast(doc.links);
         const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
         const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d53e66c47..d09b0269f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -626,8 +626,7 @@ export class MainView extends React.Component {
             <CollectionMenu />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
-            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} docprops={LinkDocPreview.LinkInfo.docprops}
-                linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href} /> : (null)}
+            {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
             <GestureOverlay >
                 {this.mainContent}
             </GestureOverlay>
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 8cd069210..c7888c5ee 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -30,8 +30,7 @@ export class LinkMenu extends React.Component<Props> {
 
     onPointerDown = (e: PointerEvent) => {
         LinkDocPreview.Clear();
-        if (this._linkMenuRef && this._editorRef &&
-            !this._linkMenuRef.current?.contains(e.target as any) &&
+        if (!this._linkMenuRef.current?.contains(e.target as any) &&
             !this._editorRef.current?.contains(e.target as any)) {
             DocumentLinksButton.ClearLinkEditor();
         }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index e9866f6e3..2bc2bc6a4 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -143,7 +143,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             docprops: this.props.docView.props,
                             linkSrc: this.props.sourceDoc,
                             linkDoc: this.props.linkDoc,
-                            Location: [e.clientX, e.clientY + 20]
+                            showHeader: false,
+                            location: [e.clientX, e.clientY + 20]
                         })}
                         onPointerDown={this.onLinkButtonDown}>
 
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index fbe289f64..e24a671d0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -107,7 +107,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         //this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
 
         this._disposers.triggerAudio = reaction(
-            () => !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
+            () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
             start => start !== undefined && setTimeout(() => {
                 this.playFrom(start);
                 setTimeout(() => {
@@ -119,7 +119,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         );
 
         this._disposers.audioStop = reaction(
-            () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._audioStop, "number", null) : undefined,
+            () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? Cast(this.Document._audioStop, "number", null) : undefined,
             audioStop => audioStop !== undefined && setTimeout(() => {
                 this.Pause();
                 setTimeout(() => this.Document._audioStop = undefined, 10);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f56246e9a..8afa53eac 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -377,7 +377,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => {
-        this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+        this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
         return this.props.focus(doc, willZoom, scale, afterFocus, dontCenter, focused);
     }
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 673c936ce..7a4209563 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -140,7 +140,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
                 docprops: this.props,
                 linkSrc: linkSource,
                 linkDoc: this.rootDoc,
-                Location: [e.clientX, e.clientY + 20]
+                showHeader: true,
+                location: [e.clientX, e.clientY + 20]
             })}
             onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
             ref={this._ref}
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
new file mode 100644
index 000000000..abbb8cdf0
--- /dev/null
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -0,0 +1,69 @@
+ .linkDocPreview {
+    position: absolute;
+    pointer-events: all;
+    background-color: lightblue;
+    border: 8px solid white;
+    border-radius: 7px;
+    box-shadow: 3px 3px 1.5px grey;
+    border-bottom: 8px solid white;
+    border-right: 8px solid white;
+    z-index: 2004;
+    .linkDocPreview-inner {
+        background-color: white;
+        border: 8px solid white;
+        width: 100%;
+        height: 100%;
+        pointer-events: none;
+
+        .linkDocPreview-info {
+            height: 37px;
+            white-space: pre;
+
+            .linkDocPreview-title {
+                padding-right: 4px;
+                float: left;
+                width: calc(100% - 48px);
+                overflow: hidden;
+                text-overflow: ellipsis;
+                height: 25px;
+
+                .linkDocPreview-description {
+                    text-decoration: none;
+                    font-style: italic;
+                    color: rgb(95, 97, 102);
+                    font-size: 10px;
+                }
+            }
+
+            .linkDocPreview-button {
+                display: inline-flex;
+                margin: 0;
+                margin-right: 3px;
+                border-radius: 50%;
+                pointer-events: auto;
+                background-color: black;
+                color: white;
+                transition: transform 0.2s;
+                text-align: center;
+                position: relative;
+                font-size: 12px;
+                width: 20px;
+                height: 20px;
+                align-items: center;
+                justify-content: center;
+
+                &:hover {
+                    background-color: rgb(77, 77, 77);
+                    cursor: pointer;
+                }
+            }
+
+            .linkDocPreview-preview-wrapper {
+                overflow: hidden;
+                align-content: center;
+                justify-content: center;
+                background-color: rgb(160, 160, 160);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 4808feb47..04a407eab 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -1,121 +1,197 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import wiki from "wikijs";
-import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from '../../../fields/FieldSymbols';
-import { Cast, FieldValue, NumCast } from "../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, emptyPath } from "../../../Utils";
+import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { NumCast, StrCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils";
+import { DocServer } from '../../DocServer';
 import { Docs } from "../../documents/Documents";
 import { LinkManager } from '../../util/LinkManager';
 import { Transform } from "../../util/Transform";
-import { ContextMenu } from '../ContextMenu';
-import { DocumentLinksButton } from './DocumentLinksButton';
-import { DocumentView, StyleProviderFunc, DocumentViewSharedProps } from "./DocumentView";
+import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
+import './LinkDocPreview.scss';
 import React = require("react");
 
-interface Props {
+interface LinkDocPreviewProps {
     linkDoc?: Doc;
     linkSrc?: Doc;
     href?: string;
     docprops: DocumentViewSharedProps;
     location: number[];
+    hrefs?: string[];
+    showHeader?: boolean;
 }
 @observer
-export class LinkDocPreview extends React.Component<Props> {
+export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     @action public static Clear() { LinkDocPreview.LinkInfo = undefined; }
-    @action public static SetLinkInfo(info: { linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }) {
-        LinkDocPreview.LinkInfo = info;
-    }
-    @observable public static LinkInfo: Opt<{ linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }>;
+    @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo != info && (LinkDocPreview.LinkInfo = info); }
+
+    _infoRef = React.createRef<HTMLDivElement>();
+    @observable public static LinkInfo: Opt<LinkDocPreviewProps>;
     @observable _targetDoc: Opt<Doc>;
+    @observable _linkDoc: Opt<Doc>;
+    @observable _linkSrc: Opt<Doc>;
     @observable _toolTipText = "";
-    _linkTarget: Opt<Doc>;
-    _editRef = React.createRef<HTMLDivElement>();
+    @observable _hrefInd = 0;
+    @observable _linkTarget: Opt<Doc>;
 
-    @action
-    componentWillUnmount() { LinkDocPreview.LinkInfo = undefined; }
+    @action componentDidUpdate(props: any) {
+        if (props.linkSrc !== this.props.linkSrc ||
+            props.linkDoc !== this.props.linkDoc ||
+            props.hrefs !== this.props.hrefs) {
+            this._linkTarget = this.props.linkDoc;
+            this._linkSrc = this.props.linkSrc;
+            this._linkDoc = this.props.linkDoc;
+            this._toolTipText = "";
+            this.updatePreview();
+        }
+    }
+    @action componentDidMount() {
+        this._linkTarget = this.props.linkDoc;
+        this._linkSrc = this.props.linkSrc;
+        this._linkDoc = this.props.linkDoc;
+        this._toolTipText = "";
+        this.updatePreview();
+        document.addEventListener("pointerdown", this.onPointerDown);
+    }
+
+    componentWillUnmount() {
+        LinkDocPreview.SetLinkInfo(undefined);
+        document.removeEventListener("pointerdown", this.onPointerDown);
+    }
+
+    onPointerDown = (e: PointerEvent) => {
+        !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear();
+    }
 
-    componentDidUpdate() { this.updatePreview(); }
-    componentDidMount() { this.updatePreview(); }
-    async updatePreview() {
+    updatePreview() {
         const linkDoc = this.props.linkDoc;
         const linkSrc = this.props.linkSrc;
-        if (this.props.href) {
-            if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) {
-                wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+        if (this.props.hrefs?.length) {
+            const href = this.props.hrefs[this._hrefInd];
+            if (href.indexOf(Utils.prepend("/doc/")) !== 0) {
+                if (href.startsWith("https://en.wikipedia.org/wiki/")) {
+                    wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+                } else {
+                    runInAction(() => this._toolTipText = "external => " + href);
+                }
             } else {
-                runInAction(() => this._toolTipText = "external => " + this.props.href);
+                const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                anchorDoc && DocServer.GetRefField(anchorDoc).then(action(async anchor => {
+                    if (anchor instanceof Doc) {
+                        this._linkDoc = DocListCast(anchor.links)[0];
+                        this._linkSrc = anchor;
+                        const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+                        runInAction(async () => {
+                            this._linkTarget = targetanchor;
+                            const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
+                            this._toolTipText = "";
+                            runInAction(() => this._targetDoc = target);
+                        });
+                    }
+                }));
             }
-        } else if (linkDoc && linkSrc) {
+        } else if (linkDoc) {
             const anchor1 = linkDoc.anchor1 as Doc;
             const anchor2 = linkDoc.anchor2 as Doc;
-            this._linkTarget = Doc.AreProtosEqual(anchor1, linkSrc) || Doc.AreProtosEqual(anchor1.annotationOn as Doc, linkSrc) ? anchor2 : anchor1;
-            const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
-            runInAction(() => {
+            runInAction(async () => {
+                this._linkTarget = Doc.AreProtosEqual(anchor1, linkSrc) || Doc.AreProtosEqual(anchor1.annotationOn as Doc, linkSrc) ? anchor2 : anchor1;
+                const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
                 this._toolTipText = "";
-                this._targetDoc = target;
+                runInAction(() => this._targetDoc = target);
             });
         }
     }
-    pointerDown = (e: React.PointerEvent) => {
-        if (this.props.linkDoc && this.props.linkSrc) {
-            LinkManager.FollowLink(this.props.linkDoc, this.props.linkSrc, this.props.docprops, false);
+    deleteLink = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._linkDoc ? LinkManager.Instance.deleteLink(this._linkDoc) : null));
+    }
+    nextHref = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => {
+            this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+            this.updatePreview();
+        }));
+    }
+
+    followLink = (e: React.PointerEvent) => {
+        if (this._linkDoc && this._linkSrc) {
+            LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docprops, false);
         } else if (this.props.href) {
             this.props.docprops?.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right");
         }
     }
     width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
     height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
-    @computed get targetDocView() {
-        return !this._targetDoc ?
-            <div style={{ pointerEvents: "all", maxWidth: 225, maxHeight: 225, width: "100%", height: "100%", overflow: "hidden" }}>
-                <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}>
-                    {this._toolTipText}
+    @computed get previewHeader() {
+        return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
+            <div className="LinkDocPreview-info" ref={this._infoRef}>
+                <div className="LinkDocPreview-title">
+                    {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
+                    <p className="LinkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
+                </div>
+                <div className="wrapper" style={{ float: "right" }}>
+                    {(this.props.hrefs?.length || 0) <= 1 ? (null) :
+                        <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
+                            <div className="LinkDocPreview-button" onPointerDown={this.nextHref}>
+                                <FontAwesomeIcon className="LinkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
+                            </div>
+                        </Tooltip>}
+
+                    <Tooltip title={<div className="dash-tooltip">Delete Link</div>} placement="top">
+                        <div className="LinkDocPreview-button" onPointerDown={this.deleteLink}>
+                            <FontAwesomeIcon className="LinkDocPreview-fa-icon" icon="trash" color="white" size="sm" />
+                        </div>
+                    </Tooltip>
+                </div>
+            </div>;
+    }
+
+    @computed get docPreview() {
+        return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
+            <div className="LinkDocPreview-inner">
+                {!this.props.showHeader ? (null) : this.previewHeader}
+                <div className="LinkDocPreview-preview-wrapper">
+                    {this._toolTipText ? this._toolTipText :
+                        <DocumentView ref={(r) => {
+                            const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!);
+                            targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
+                        }}
+                            Document={this._targetDoc}
+                            moveDocument={returnFalse}
+                            rootSelected={returnFalse}
+                            styleProvider={this.props.docprops?.styleProvider}
+                            layerProvider={this.props.docprops?.layerProvider}
+                            docViewPath={emptyPath}
+                            ScreenToLocalTransform={Transform.Identity}
+                            parentActive={returnFalse}
+                            addDocument={returnFalse}
+                            removeDocument={returnFalse}
+                            addDocTab={returnFalse}
+                            pinToPres={returnFalse}
+                            dontRegisterView={true}
+                            docFilters={returnEmptyFilter}
+                            docRangeFilters={returnEmptyFilter}
+                            searchFilterDocs={returnEmptyDoclist}
+                            ContainingCollectionDoc={undefined}
+                            ContainingCollectionView={undefined}
+                            renderDepth={-1}
+                            PanelWidth={this.width}
+                            PanelHeight={this.height}
+                            focus={emptyFunction}
+                            whenActiveChanged={returnFalse}
+                            bringToFront={returnFalse}
+                            NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
+                            NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
+                        />}
                 </div>
-            </div>
-            :
-            <DocumentView ref={r => this._linkTarget !== this._targetDoc && this._linkTarget && r?.focus(this._linkTarget)}
-                Document={this._targetDoc}
-                moveDocument={returnFalse}
-                rootSelected={returnFalse}
-                ScreenToLocalTransform={Transform.Identity}
-                parentActive={returnFalse}
-                addDocument={returnFalse}
-                removeDocument={returnFalse}
-                addDocTab={returnFalse}
-                pinToPres={returnFalse}
-                dontRegisterView={true}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionDoc={undefined}
-                ContainingCollectionView={undefined}
-                renderDepth={-1}
-                PanelWidth={this.width}
-                PanelHeight={this.height}
-                focus={emptyFunction}
-                whenActiveChanged={returnFalse}
-                bringToFront={returnFalse}
-                styleProvider={this.props.docprops?.styleProvider}
-                layerProvider={this.props.docprops?.layerProvider}
-                docViewPath={emptyPath}
-            />;
+            </div>;
     }
 
     render() {
-        return <div className="linkDocPreview"
-            style={{
-                position: "absolute", left: this.props.location[0],
-                top: this.props.location[1], width: this.width() + 16, height: this.height() + 16,
-                zIndex: 2004,
-                pointerEvents: "none",
-                backgroundColor: "lightblue",
-                border: "8px solid white",
-                borderRadius: "7px",
-                boxShadow: "3px 3px 1.5px grey",
-                borderBottom: "8px solid white", borderRight: "8px solid white"
-            }}>
-            {this.targetDocView}
+        return <div className="linkDocPreview" onPointerDown={this.followLink}
+            style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + 16 }}>
+            {this.docPreview}
         </div>;
     }
 }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index c21701f32..324861573 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -211,7 +211,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             },
             { fireImmediately: true });
         this._disposers.triggerVideo = reaction(
-            () => !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
+            () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
             time => time !== undefined && setTimeout(() => {
                 this.player && this.Play();
                 setTimeout(() => this.Document._triggerVideo = undefined, 10);
@@ -219,7 +219,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             { fireImmediately: true }
         );
         this._disposers.triggerStop = reaction(
-            () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc ? NumCast(this.Document._triggerVideoStop, null) : undefined,
+            () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? NumCast(this.Document._triggerVideoStop, null) : undefined,
             stop => stop !== undefined && setTimeout(() => {
                 this.player && this.Pause();
                 setTimeout(() => this.Document._triggerVideoStop = undefined, 10);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0374b1426..bf868634b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1215,7 +1215,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     const scrollRef = self._scrollRef.current;
                     if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) {
                         const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale;
-                        if (!LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc) {
+                        if (!LinkDocPreview.LinkInfo) {
                             scrollPos && smoothScroll(500, scrollRef, scrollPos);
                         } else {
                             scrollRef.scrollTo({ top: scrollPos });
@@ -1338,7 +1338,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     onPointerUp = (e: React.PointerEvent): void => {
         if (!this._downEvent) return;
         this._downEvent = false;
-        if (!(e.nativeEvent as any).formattedHandled) {
+        if (!(e.nativeEvent as any).formattedHandled && this.active(true)) {
             const editor = this._editorView!;
             FormattedTextBoxComment.textBox = this;
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -1366,13 +1366,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             e.preventDefault();
         }
         FormattedTextBoxComment.Hide();
-        if (FormattedTextBoxComment.linkDoc) {
-            if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
-            } else {
-                LinkManager.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document, this.props, false);
-            }
-        }
 
         (e.nativeEvent as any).formattedHandled = true;
 
@@ -1601,7 +1594,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
     }
     onScroll = (ev: React.UIEvent) => {
-        if (!LinkDocPreview.LinkInfo && !FormattedTextBoxComment.linkDoc && this._scrollRef.current) {
+        if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
             this._ignoreScroll = true;
             this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
             this._ignoreScroll = false;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 81afba4d7..3251319b9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -1,6 +1,8 @@
 .FormattedTextBox-tooltip {
     position: absolute;
-    pointer-events: none;
+    pointer-events: all;
+    height: 100%;
+    overflow: hidden;
     z-index: 20;
     background: white;
     border: 1px solid silver;
@@ -9,67 +11,9 @@
     -webkit-transform: translateX(-50%);
     transform: translateX(-50%);
     box-shadow: 3px 3px 1.5px grey;
-
-    .FormattedTextBoxComment {
-        background-color: white;
-        border: 8px solid white;
-        //width: 200px;
-
-        //display: flex;
-        .FormattedTextBoxComment-info {
-
-            margin-bottom: 37px;
-
-            .FormattedTextBoxComment-title {
-                padding-right: 4px;
-                float: left;
-
-                .FormattedTextBoxComment-description {
-                    text-decoration: none;
-                    font-style: italic;
-                    color: rgb(95, 97, 102);
-                    font-size: 10px;
-                }
-            }
-
-            .FormattedTextBoxComment-button {
-                display: inline;
-                padding-left: 6px;
-                padding-right: 6px;
-                padding-top: 2.5px;
-                padding-bottom: 2.5px;
-                width: 17px;
-                height: 17px;
-                margin: 0;
-                margin-right: 3px;
-                border-radius: 50%;
-                pointer-events: auto;
-                background-color: rgb(0, 0, 0);
-                color: rgb(255, 255, 255);
-                transition: transform 0.2s;
-                text-align: center;
-                position: relative;
-                font-size: 12px;
-
-                &:hover {
-                    background-color: rgb(77, 77, 77);
-                    cursor: pointer;
-                }
-            }
-        }
-
-        .FormattedTextBoxComment-preview-wrapper {
-            //width: 170px;
-            height: 170px;
-            overflow: hidden;
-            //padding-top: 5px;
-            margin-top: 10px;
-            margin-bottom: 8px;
-            align-content: center;
-            justify-content: center;
-            background-color: rgb(160, 160, 160);
-        }
-    }
+    max-width: 400;
+    max-height: 235;
+    height:max-content;
 }
 
 .FormattedTextBox-tooltip:before {
@@ -96,12 +40,4 @@
     border: 5px solid transparent;
     border-bottom-width: 0;
     border-top-color: white;
-}
-
-.FormattedTextBoxComment-buttons {
-    display: none;
-    position: absolute;
-    top: 50%;
-    right: 0;
-    transform: translateY(-50%);
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index d1bb2ad84..827bb2591 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,28 +1,17 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
 import { action, observable } from "mobx";
 import { Mark, ResolvedPos } from "prosemirror-model";
 import { EditorState, Plugin } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import * as ReactDOM from 'react-dom';
-import wiki from "wikijs";
-import { Doc, DocCastAsync, DocListCast, Opt } from "../../../../fields/Doc";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, Utils, emptyPath } from "../../../../Utils";
+import { Doc, DocListCast, Opt } from "../../../../fields/Doc";
+import { Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { LinkManager } from "../../../util/LinkManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { DocumentLinksButton } from "../DocumentLinksButton";
-import { DocumentView } from "../DocumentView";
 import { LinkDocPreview } from "../LinkDocPreview";
 import { FormattedTextBox } from "./FormattedTextBox";
 import './FormattedTextBoxComment.scss';
 import { schema } from "./schema_rts";
 import React = require("react");
-import { DefaultStyleProvider } from "../../StyleProvider";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -64,67 +53,24 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
 export class FormattedTextBoxComment {
     static tooltip: HTMLElement;
     static tooltipText: HTMLElement;
-    static tooltipInput: HTMLInputElement;
     static start: number;
     static end: number;
     static mark: Mark;
     static textBox: FormattedTextBox | undefined;
-    static linkDoc: Doc | undefined;
-
-    static _deleteRef: Opt<HTMLDivElement | null>;
-    static _followRef: Opt<HTMLDivElement | null>;
-    static _nextRef: Opt<HTMLDivElement | null>;
-
-    static _lastState?: EditorState;
-    static _lastView?: EditorView;
-
-    @observable static _hrefInd = 0;
-    static _hrefs: string[] | undefined = [];
 
     constructor(view: any) {
         if (!FormattedTextBoxComment.tooltip) {
             const root = document.getElementById("root");
-            FormattedTextBoxComment.tooltipInput = document.createElement("input");
-            FormattedTextBoxComment.tooltipInput.type = "checkbox";
             FormattedTextBoxComment.tooltip = document.createElement("div");
             FormattedTextBoxComment.tooltipText = document.createElement("div");
-            //FormattedTextBoxComment.tooltipText.style.width = "100%";
-            FormattedTextBoxComment.tooltipText.style.height = "100%";
+            FormattedTextBoxComment.tooltipText.style.height = "max-content";
             FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
             FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
             FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
-            FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
-            FormattedTextBoxComment.tooltip.style.maxWidth = "400px";
-            FormattedTextBoxComment.tooltip.style.maxHeight = "235px";
-            //FormattedTextBoxComment.tooltip.style.width = "100%";
-            FormattedTextBoxComment.tooltip.style.height = "100%";
-            FormattedTextBoxComment.tooltip.style.overflow = "hidden";
             FormattedTextBoxComment.tooltip.style.display = "none";
-            // FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
-            FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => {
-                const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
+            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
                 const textBox = FormattedTextBoxComment.textBox;
-                const linkDoc = FormattedTextBoxComment.linkDoc;
-                if (linkDoc && !keep && textBox) {
-                    if (linkDoc.author) {
-                        if (FormattedTextBoxComment._deleteRef?.contains(e.target as any)) {
-                            this.deleteLink();
-                        } else if (FormattedTextBoxComment._nextRef?.contains(e.target as any)) {
-                            FormattedTextBoxComment.showPreview(FormattedTextBoxComment._lastView!, FormattedTextBoxComment._lastState, FormattedTextBoxComment._hrefs?.[(++FormattedTextBoxComment._hrefInd) % FormattedTextBoxComment._hrefs?.length]);
-                        } else {
-                            FormattedTextBoxComment.linkDoc = undefined;
-                            if (linkDoc.type !== DocumentType.LINK) {
-                                textBox.props.addDocTab(linkDoc, e.ctrlKey ? "add" : "add:right");
-                            } else {
-                                const target = LinkManager.getOppositeAnchor(linkDoc, textBox.dataDoc);
-                                target && LinkManager.FollowLink(linkDoc, textBox.dataDoc, textBox.props, e.altKey);
-                            }
-                        }
-                    }
-                } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
-                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, useCors: true }), "add:right");
-                }
-                keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
+                false && FormattedTextBoxComment.start !== undefined && textBox?.adoptAnnotation(
                     FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
                 e.stopPropagation();
                 e.preventDefault();
@@ -132,21 +78,9 @@ export class FormattedTextBoxComment {
             root?.appendChild(FormattedTextBoxComment.tooltip);
         }
     }
-
-    @undoBatch
-    deleteLink = action(() => {
-        FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
-        FormattedTextBoxComment.Hide();
-    });
-
     public static Hide() {
         FormattedTextBoxComment.textBox = undefined;
-        FormattedTextBoxComment.linkDoc = undefined;
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
-        try {
-            ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-            FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
-        } catch (e) { }
     }
     public static SetState(textBox: any, start: number, end: number, mark: Mark) {
         FormattedTextBoxComment.textBox = textBox;
@@ -170,180 +104,71 @@ export class FormattedTextBoxComment {
             const left = Math.max((start.left + end.left) / 2, start.left + 3);
             FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
             FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
-            // const props = FormattedTextBoxComment.textBox?.props.docViewPath.lastElement().props;
-            // props && (LinkDocPreview.SetLinkInfo({
-            //     docprops: props,
-            //     linkSrc: props.Document,
-            //     linkDoc: FormattedTextBoxComment.linkDoc,
-            //     Location: [start.left, start.top + 25]
-            // });
         }
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
     }
 
-    static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
-        // Don't do anything if the document/selection didn't change
-        if (!forceUrl && lastState?.doc.eq(view.state.doc) && lastState?.selection.eq(view.state.selection)) {
-            return;
+    static update(view: EditorView, lastState?: EditorState, hrefs: string = "") {
+        if (FormattedTextBoxComment.textBox && (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) {
+            FormattedTextBoxComment.setupPreview(view, FormattedTextBoxComment.textBox, hrefs ? hrefs.trim().split(" ") : undefined);
         }
-        FormattedTextBoxComment._lastState = lastState;
-        FormattedTextBoxComment._lastView = view;
-        FormattedTextBoxComment._hrefs = forceUrl ? forceUrl.trim().split(" ") : undefined;
-        FormattedTextBoxComment._hrefInd = 0;
-        FormattedTextBoxComment.linkDoc = undefined;
-        FormattedTextBoxComment.showPreview(view, lastState, FormattedTextBoxComment._hrefs?.[FormattedTextBoxComment._hrefInd]);
     }
 
-    static showPreview(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
+    static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) {
         const state = view.state;
-        const textBox = FormattedTextBoxComment.textBox;
-        if (!textBox || !textBox.props) {
-            return;
-        }
-        let set = "none";
-        let nbef = 0;
-        FormattedTextBoxComment.tooltipInput.style.display = "none";
-        FormattedTextBoxComment.tooltip.style.width = "";
-        FormattedTextBoxComment.tooltip.style.height = "";
-        (FormattedTextBoxComment.tooltipText as any).href = "";
-        FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
-        FormattedTextBoxComment.tooltipText.style.overflow = "";
         // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
+        var hide = true;
         if (state.selection.$from) {
-            nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+            const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
-            const noselection = view.state.selection.$from === view.state.selection.$to;
+            const noselection = state.selection.$from === state.selection.$to;
             let child: any = null;
             state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             const mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+                FormattedTextBoxComment.SetState(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
             }
             if (mark && child && ((nbef && naft) || !noselection)) {
                 FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString();
-                set = "";
-                FormattedTextBoxComment.tooltipInput.style.display = "";
+                FormattedTextBoxComment.showCommentbox("", view, nbef);
+                hide = false;
             }
         }
         // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
-        if (set === "none" && state.selection.$from) {
-            nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+        if (hide && state.selection.$from) {
+            const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
             let child: any = null;
             state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             child = child || (nbef && state.selection.$from.nodeBefore);
             const mark = child ? findLinkMark(child.marks) : undefined;
-            const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allAnchors.find((item: { href: string }) => item.href)?.href;
-            if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
-                try {
-                    ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-                    FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
-                } catch (e) { }
-                FormattedTextBoxComment.tooltipText = document.createElement("div");
-                FormattedTextBoxComment.tooltipText.className = "FormattedTextBoxComment-toolTipText";
-                FormattedTextBoxComment.tooltipText.style.width = "100%";
-                FormattedTextBoxComment.tooltipText.style.height = "100%";
-                FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
-                FormattedTextBoxComment.tooltipText.style.cursor = "pointer";
-                FormattedTextBoxComment.tooltipText.textContent = "URL: " + href;
-                (FormattedTextBoxComment.tooltipText as any).href = href;
-                FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
-
-                if (href.startsWith("https://en.wikipedia.org/wiki/")) {
-                    wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
-                } else {
-                    FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
-                    FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
-                }
-                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                    const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    FormattedTextBoxComment.tooltipText.textContent = "target not found...";
-                    (FormattedTextBoxComment.tooltipText as any).href = "";
-                    anchorDoc && DocServer.GetRefField(anchorDoc).then(async anchor => {
-                        if (anchor instanceof Doc) {
-                            const linkDoc = DocListCast(anchor.links)[0];
-                            (FormattedTextBoxComment.tooltipText as any).href = href;
-                            FormattedTextBoxComment.linkDoc = linkDoc;
-                            const targetanchor = LinkManager.getOppositeAnchor(linkDoc, anchor);
-                            const target = targetanchor?.annotationOn ? await DocCastAsync(targetanchor.annotationOn) : targetanchor;
-                            if (target?.author) {
-                                FormattedTextBoxComment.showCommentbox("", view, nbef);
-
-                                const title = StrCast(target.title).length > 16 ? StrCast(target.title).substr(0, 16) + "..." : target.title;
-
-                                const docPreview = <div className="FormattedTextBoxComment">
-                                    <div className="FormattedTextBoxComment-info">
-                                        <div className="FormattedTextBoxComment-title">
-                                            {title}
-                                            {FormattedTextBoxComment.linkDoc.description === "" ? (null) :
-                                                <p className="FormattedTextBoxComment-description"> {StrCast(FormattedTextBoxComment.linkDoc.description)}</p>}
-                                        </div>
-                                        <div className="wrapper" style={{ float: "right" }}>
-                                            {(FormattedTextBoxComment._hrefs?.length || 0) <= 1 ? (null) : <Tooltip title={<><div className="dash-tooltip">Next Link</div></>} placement="top">
-                                                <div className="FormattedTextBoxComment-button" ref={(r) => this._nextRef = r}>
-                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="chevron-right" color="white" size="sm" />
-                                                </div>
-                                            </Tooltip>}
-
-                                            <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top">
-                                                <div className="FormattedTextBoxComment-button" ref={(r) => this._deleteRef = r}>
-                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white" size="sm" />
-                                                </div>
-                                            </Tooltip>
-
-                                            <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top">
-                                                <div className="FormattedTextBoxComment-button" ref={(r) => this._followRef = r}>
-                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white" size="sm" />
-                                                </div>
-                                            </Tooltip>
-                                        </div>
-                                    </div>
-                                    <div className="FormattedTextBoxComment-preview-wrapper">
-                                        <DocumentView ref={(r) => targetanchor && target !== targetanchor && r?.focus(targetanchor)}
-                                            Document={target}
-                                            moveDocument={returnFalse}
-                                            rootSelected={returnFalse}
-                                            styleProvider={DefaultStyleProvider}
-                                            layerProvider={undefined}
-                                            docViewPath={emptyPath}
-                                            ScreenToLocalTransform={Transform.Identity}
-                                            parentActive={returnFalse}
-                                            addDocument={returnFalse}
-                                            removeDocument={returnFalse}
-                                            addDocTab={returnFalse}
-                                            pinToPres={returnFalse}
-                                            dontRegisterView={true}
-                                            docFilters={returnEmptyFilter}
-                                            docRangeFilters={returnEmptyFilter}
-                                            searchFilterDocs={returnEmptyDoclist}
-                                            ContainingCollectionDoc={undefined}
-                                            ContainingCollectionView={undefined}
-                                            renderDepth={-1}
-                                            PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))}
-                                            PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))}
-                                            focus={emptyFunction}
-                                            whenActiveChanged={returnFalse}
-                                            bringToFront={returnFalse}
-                                            NativeWidth={Doc.NativeWidth(target) ? (() => Doc.NativeWidth(target)) : undefined}
-                                            NativeHeight={Doc.NativeHeight(target) ? (() => Doc.NativeHeight(target)) : undefined}
-                                        />
-                                    </div>
-                                </div>;
-
-                                FormattedTextBoxComment.showCommentbox("", view, nbef);
-
-                                ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
-
-                                //FormattedTextBoxComment.tooltip.style.width = "100%";
-                                FormattedTextBoxComment.tooltip.style.height = "100%";
-                            }
-                        }
+            const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allAnchors.find((item: { href: string }) => item.href)?.href;
+            if ((href && child && nbef && naft && mark?.attrs.showPreview)) {
+                const anchorDoc = href.indexOf(Utils.prepend("/doc/")) === 0 ? href.replace(Utils.prepend("/doc/"), "").split("?")[0] : undefined;
+                if (anchorDoc) {
+                    DocServer.GetRefField(anchorDoc).then(async anchor =>
+                        anchor instanceof Doc && textBox && LinkDocPreview.SetLinkInfo({
+                            docprops: textBox.props.docViewPath.lastElement().props,
+                            linkSrc: textBox.props.Document,
+                            linkDoc: DocListCast(anchor.links)[0],
+                            location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
+                            hrefs,
+                            showHeader: true
+                        })
+                    );
+                } else if (hrefs?.length) {
+                    LinkDocPreview.SetLinkInfo({
+                        docprops: textBox.props.docViewPath.lastElement().props,
+                        linkSrc: textBox.props.Document,
+                        linkDoc: undefined,
+                        location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
+                        hrefs,
+                        showHeader: true
                     });
                 }
-                set = "";
             }
         }
-        FormattedTextBoxComment.showCommentbox(set, view, nbef);
+        if (hide) FormattedTextBoxComment.Hide();
     }
 
     destroy() { }
-- 
cgit v1.2.3-70-g09d2


From 30928b9bd3a90a04f5735e0ffbf92d0f74ad5d7d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 9 Feb 2021 19:57:29 -0500
Subject: cleanup of formattedtextboxcomment and linkDocPreview

---
 src/client/views/nodes/LinkDocPreview.tsx          | 36 +++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 10 ++-
 .../formattedText/FormattedTextBoxComment.scss     | 10 ++-
 .../formattedText/FormattedTextBoxComment.tsx      | 77 +++++++---------------
 4 files changed, 56 insertions(+), 77 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 04a407eab..b1410e1f6 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -80,15 +80,17 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             } else {
                 const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                 anchorDoc && DocServer.GetRefField(anchorDoc).then(action(async anchor => {
-                    if (anchor instanceof Doc) {
+                    if (anchor instanceof Doc && DocListCast(anchor.links).length) {
                         this._linkDoc = DocListCast(anchor.links)[0];
                         this._linkSrc = anchor;
                         const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
                         runInAction(async () => {
                             this._linkTarget = targetanchor;
                             const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
-                            this._toolTipText = "";
-                            runInAction(() => this._targetDoc = target);
+                            runInAction(() => {
+                                this._toolTipText = "";
+                                this._targetDoc = target;
+                            });
                         });
                     }
                 }));
@@ -99,8 +101,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             runInAction(async () => {
                 this._linkTarget = Doc.AreProtosEqual(anchor1, linkSrc) || Doc.AreProtosEqual(anchor1.annotationOn as Doc, linkSrc) ? anchor2 : anchor1;
                 const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
-                this._toolTipText = "";
-                runInAction(() => this._targetDoc = target);
+                runInAction(() => {
+                    this._toolTipText = "";
+                    this._targetDoc = target;
+                });
             });
         }
     }
@@ -125,22 +129,22 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
     @computed get previewHeader() {
         return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
-            <div className="LinkDocPreview-info" ref={this._infoRef}>
-                <div className="LinkDocPreview-title">
+            <div className="linkDocPreview-info" ref={this._infoRef}>
+                <div className="linkDocPreview-title">
                     {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
-                    <p className="LinkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
+                    <p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
                 </div>
                 <div className="wrapper" style={{ float: "right" }}>
                     {(this.props.hrefs?.length || 0) <= 1 ? (null) :
                         <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
-                            <div className="LinkDocPreview-button" onPointerDown={this.nextHref}>
-                                <FontAwesomeIcon className="LinkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
+                            <div className="linkDocPreview-button" onPointerDown={this.nextHref}>
+                                <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
                             </div>
                         </Tooltip>}
 
                     <Tooltip title={<div className="dash-tooltip">Delete Link</div>} placement="top">
-                        <div className="LinkDocPreview-button" onPointerDown={this.deleteLink}>
-                            <FontAwesomeIcon className="LinkDocPreview-fa-icon" icon="trash" color="white" size="sm" />
+                        <div className="linkDocPreview-button" onPointerDown={this.deleteLink}>
+                            <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="trash" color="white" size="sm" />
                         </div>
                     </Tooltip>
                 </div>
@@ -149,15 +153,15 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
 
     @computed get docPreview() {
         return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
-            <div className="LinkDocPreview-inner">
+            <div className="linkDocPreview-inner">
                 {!this.props.showHeader ? (null) : this.previewHeader}
-                <div className="LinkDocPreview-preview-wrapper">
+                <div className="linkDocPreview-preview-wrapper">
                     {this._toolTipText ? this._toolTipText :
                         <DocumentView ref={(r) => {
                             const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!);
                             targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
                         }}
-                            Document={this._targetDoc}
+                            Document={this._targetDoc!}
                             moveDocument={returnFalse}
                             rootSelected={returnFalse}
                             styleProvider={this.props.docprops?.styleProvider}
@@ -194,4 +198,4 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             {this.docPreview}
         </div>;
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bf868634b..fece45e18 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -55,9 +55,8 @@ import { DocumentButtonBar } from '../../DocumentButtonBar';
 import { AudioBox } from '../AudioBox';
 import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
+import { FormattedTextBoxComment, findLinkMark } from './FormattedTextBoxComment';
 import React = require("react");
-import { LinkManager } from '../../../util/LinkManager';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
 import { CollectionViewType } from '../../collections/CollectionView';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -825,8 +824,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     props: {
                         attributes: { class: "ProseMirror-example-setup-style" }
                     }
-                }),
-                formattedTextBoxCommentPlugin
+                }), new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } })
             ]
         };
     }
@@ -1613,8 +1611,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 this.layoutDoc.limitHeight = undefined;
                 this.layoutDoc._autoHeight = false;
             }
-            const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight, 0);
-            const dh = NumCast(this.rootDoc._height, 0);
+            const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
+            const dh = NumCast(this.rootDoc._height);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + this.titleHeight);
             if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
                 // 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...
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 3251319b9..55b8446e9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -1,4 +1,4 @@
-.FormattedTextBox-tooltip {
+.formattedTextBox-tooltip {
     position: absolute;
     pointer-events: all;
     height: 100%;
@@ -14,9 +14,13 @@
     max-width: 400;
     max-height: 235;
     height:max-content;
+    .formattedTextBox-tooltipText {
+        height: max-content;
+        text-overflow: ellipsis;
+    }
 }
 
-.FormattedTextBox-tooltip:before {
+.formattedTextBox-tooltip:before {
     content: "";
     height: 0;
     width: 0;
@@ -29,7 +33,7 @@
     border-top-color: silver;
 }
 
-.FormattedTextBox-tooltip:after {
+.formattedTextBox-tooltip:after {
     content: "";
     height: 0;
     width: 0;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 827bb2591..e4a1e0f1b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,33 +1,19 @@
-import { action, observable } from "mobx";
 import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState, Plugin } from "prosemirror-state";
+import { EditorState } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Opt } from "../../../../fields/Doc";
+import { Doc, DocListCast } from "../../../../fields/Doc";
 import { Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
 import { LinkDocPreview } from "../LinkDocPreview";
 import { FormattedTextBox } from "./FormattedTextBox";
 import './FormattedTextBoxComment.scss';
 import { schema } from "./schema_rts";
-import React = require("react");
 
-export let formattedTextBoxCommentPlugin = new Plugin({
-    view(editorView) { return new FormattedTextBoxComment(editorView); }
-});
-export function findOtherUserMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
-}
-export function findUserMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.attrs.userid);
-}
-export function findLinkMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.type === schema.marks.linkAnchor);
-}
+export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); }
+export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); }
+export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.linkAnchor); }
 export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
-    let before = 0;
-    let nbef = rpos.nodeBefore;
+    let before = 0, nbef = rpos.nodeBefore;
     while (nbef && finder(nbef.marks)) {
         before += nbef.nodeSize;
         rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
@@ -36,8 +22,7 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma
     return before;
 }
 export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
-    let after = 0;
-    let naft = rpos.nodeAfter;
+    let after = 0, naft = rpos.nodeAfter;
     while (naft && finder(naft.marks)) {
         after += naft.nodeSize;
         rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
@@ -49,7 +34,6 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
 // this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
 // this will also display metadata information about text when the view is configured to display things like other people who authored text.
 // 
-
 export class FormattedTextBoxComment {
     static tooltip: HTMLElement;
     static tooltipText: HTMLElement;
@@ -60,14 +44,12 @@ export class FormattedTextBoxComment {
 
     constructor(view: any) {
         if (!FormattedTextBoxComment.tooltip) {
-            const root = document.getElementById("root");
             FormattedTextBoxComment.tooltip = document.createElement("div");
-            FormattedTextBoxComment.tooltipText = document.createElement("div");
-            FormattedTextBoxComment.tooltipText.style.height = "max-content";
-            FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
-            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
             FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
             FormattedTextBoxComment.tooltip.style.display = "none";
+            FormattedTextBoxComment.tooltipText = document.createElement("div");
+            FormattedTextBoxComment.tooltipText.className = "FormattedTextBox-tooltipText";
+            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
             FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
                 const textBox = FormattedTextBoxComment.textBox;
                 false && FormattedTextBoxComment.start !== undefined && textBox?.adoptAnnotation(
@@ -75,37 +57,34 @@ export class FormattedTextBoxComment {
                 e.stopPropagation();
                 e.preventDefault();
             };
-            root?.appendChild(FormattedTextBoxComment.tooltip);
+            document.getElementById("root")?.appendChild(FormattedTextBoxComment.tooltip);
         }
     }
     public static Hide() {
         FormattedTextBoxComment.textBox = undefined;
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+        FormattedTextBoxComment.tooltip.style.display = "none";
     }
     public static SetState(textBox: any, start: number, end: number, mark: Mark) {
         FormattedTextBoxComment.textBox = textBox;
         FormattedTextBoxComment.start = start;
         FormattedTextBoxComment.end = end;
         FormattedTextBoxComment.mark = mark;
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
+        FormattedTextBoxComment.tooltip.style.display = "";
     }
 
-    @action
     static showCommentbox(set: string, view: EditorView, nbef: number) {
         const state = view.state;
         if (set !== "none") {
             // These are in screen coordinates
-            // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
             const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
             // The box in which the tooltip is positioned, to use as base
             const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
-            // Find a center-ish x position from the selection endpoints (when
-            // crossing lines, end may be more to the left)
+            // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
             const left = Math.max((start.left + end.left) / 2, start.left + 3);
             FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
             FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
         }
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+        FormattedTextBoxComment.tooltip.style.display = set;
     }
 
     static update(view: EditorView, lastState?: EditorState, hrefs: string = "") {
@@ -139,33 +118,27 @@ export class FormattedTextBoxComment {
             const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
             let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any) => !child && node.marks.length && (child = node));
             child = child || (nbef && state.selection.$from.nodeBefore);
             const mark = child ? findLinkMark(child.marks) : undefined;
             const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allAnchors.find((item: { href: string }) => item.href)?.href;
             if ((href && child && nbef && naft && mark?.attrs.showPreview)) {
-                const anchorDoc = href.indexOf(Utils.prepend("/doc/")) === 0 ? href.replace(Utils.prepend("/doc/"), "").split("?")[0] : undefined;
-                if (anchorDoc) {
-                    DocServer.GetRefField(anchorDoc).then(async anchor =>
-                        anchor instanceof Doc && textBox && LinkDocPreview.SetLinkInfo({
-                            docprops: textBox.props.docViewPath.lastElement().props,
-                            linkSrc: textBox.props.Document,
-                            linkDoc: DocListCast(anchor.links)[0],
-                            location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
-                            hrefs,
-                            showHeader: true
-                        })
-                    );
-                } else if (hrefs?.length) {
-                    LinkDocPreview.SetLinkInfo({
+                const showPreview = (link?: Doc) => {
+                    return LinkDocPreview.SetLinkInfo({
                         docprops: textBox.props.docViewPath.lastElement().props,
                         linkSrc: textBox.props.Document,
-                        linkDoc: undefined,
+                        linkDoc: link,
                         location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
                         hrefs,
                         showHeader: true
                     });
                 }
+                const anchorDoc = href.indexOf(Utils.prepend("/doc/")) === 0 ? href.replace(Utils.prepend("/doc/"), "").split("?")[0] : undefined;
+                if (anchorDoc) {
+                    DocServer.GetRefField(anchorDoc).then(anchor => anchor instanceof Doc && showPreview(DocListCast(anchor.links)[0]));
+                } else if (hrefs?.length) {
+                    showPreview()
+                }
             }
         }
         if (hide) FormattedTextBoxComment.Hide();
-- 
cgit v1.2.3-70-g09d2


From 7fedb7f75829269e46da5eaeb3b58166f9ab2126 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Feb 2021 00:48:40 -0500
Subject: fixes for removing individual overlapping textanchors in a textBox.
 Hopefully near final cleanup of linkDocPreview and FormattedTextComment

---
 src/client/views/linking/LinkMenuItem.tsx          |   2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   2 +-
 src/client/views/nodes/LinkDocPreview.scss         |   3 +
 src/client/views/nodes/LinkDocPreview.tsx          | 100 ++++++++------------
 .../views/nodes/formattedText/FormattedTextBox.tsx |   5 +-
 .../formattedText/FormattedTextBoxComment.tsx      | 103 ++++++++-------------
 src/client/views/nodes/formattedText/marks_rts.ts  |   5 +-
 .../nodes/formattedText/prosemirrorPatches.js      |  18 ++--
 8 files changed, 103 insertions(+), 135 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 2bc2bc6a4..53a7ae9ab 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -140,7 +140,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                     <div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
                         onPointerLeave={LinkDocPreview.Clear}
                         onPointerEnter={e => this.props.linkDoc && LinkDocPreview.SetLinkInfo({
-                            docprops: this.props.docView.props,
+                            docProps: this.props.docView.props,
                             linkSrc: this.props.sourceDoc,
                             linkDoc: this.props.linkDoc,
                             showHeader: false,
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 7a4209563..db5414069 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -137,7 +137,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`}
             onPointerLeave={LinkDocPreview.Clear}
             onPointerEnter={e => LinkDocPreview.SetLinkInfo({
-                docprops: this.props,
+                docProps: this.props,
                 linkSrc: linkSource,
                 linkDoc: this.rootDoc,
                 showHeader: true,
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
index abbb8cdf0..b7aeaa072 100644
--- a/src/client/views/nodes/LinkDocPreview.scss
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -19,6 +19,9 @@
             height: 37px;
             white-space: pre;
 
+            .linkDocPreview-buttonBar {
+                float: right;
+            }
             .linkDocPreview-title {
                 padding-right: 4px;
                 float: left;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 463719824..576d1d15b 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -13,11 +13,12 @@ import { Transform } from "../../util/Transform";
 import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
 import './LinkDocPreview.scss';
 import React = require("react");
+import { undoBatch } from '../../util/UndoManager';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
     linkSrc?: Doc;
-    docprops: DocumentViewSharedProps;
+    docProps: DocumentViewSharedProps;
     location: number[];
     hrefs?: string[];
     showHeader?: boolean;
@@ -34,25 +35,24 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     @observable _linkSrc: Opt<Doc>;
     @observable _toolTipText = "";
     @observable _hrefInd = 0;
-    @observable _linkTarget: Opt<Doc>;
 
-    @action componentDidUpdate(props: any) {
-        if (props.linkSrc !== this.props.linkSrc ||
-            props.linkDoc !== this.props.linkDoc ||
-            props.hrefs !== this.props.hrefs) {
-            this._linkTarget = this.props.linkDoc;
-            this._linkSrc = this.props.linkSrc;
-            this._linkDoc = this.props.linkDoc;
-            this._toolTipText = "";
-            this.updatePreview();
-        }
-    }
-    @action componentDidMount() {
-        this._linkTarget = this.props.linkDoc;
+    @action init() {
+        var linkTarget = this.props.linkDoc;
         this._linkSrc = this.props.linkSrc;
         this._linkDoc = this.props.linkDoc;
+        const anchor1 = this._linkDoc?.anchor1 as Doc;
+        const anchor2 = this._linkDoc?.anchor2 as Doc;
+        if (anchor1 && anchor2) {
+            linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1;
+        }
+        this._targetDoc = linkTarget?.annotationOn as Doc ?? linkTarget;
         this._toolTipText = "";
-        this.updatePreview();
+    }
+    componentDidUpdate(props: any) {
+        if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
+    }
+    componentDidMount() {
+        this.init();
         document.addEventListener("pointerdown", this.onPointerDown);
     }
 
@@ -62,66 +62,46 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     }
 
     onPointerDown = (e: PointerEvent) => {
-        !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear();
+        !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
     }
 
-    updatePreview() {
-        const linkDoc = this.props.linkDoc;
-        const linkSrc = this.props.linkSrc;
+    @computed get href() {
         if (this.props.hrefs?.length) {
             const href = this.props.hrefs[this._hrefInd];
-            if (href.indexOf(Utils.prepend("/doc/")) !== 0) {
+            if (href.indexOf(Utils.prepend("/doc/")) !== 0) {  // link to a web page URL -- try to show a preview
                 if (href.startsWith("https://en.wikipedia.org/wiki/")) {
                     wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
                 } else {
-                    runInAction(() => this._toolTipText = "external => " + href);
+                    setTimeout(action(() => this._toolTipText = "url => " + href));
                 }
-            } else {
+            } else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
                 const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                anchorDoc && DocServer.GetRefField(anchorDoc).then(action(async anchor => {
+                anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
                     if (anchor instanceof Doc && DocListCast(anchor.links).length) {
                         this._linkDoc = DocListCast(anchor.links)[0];
                         this._linkSrc = anchor;
-                        const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
-                        runInAction(async () => {
-                            this._linkTarget = targetanchor;
-                            const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
-                            runInAction(() => {
-                                this._toolTipText = "";
-                                this._targetDoc = target;
-                            });
-                        });
+                        const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+                        this._targetDoc = linkTarget?.annotationOn as Doc ?? linkTarget;
+                        this._toolTipText = "";
                     }
                 }));
             }
-        } else if (linkDoc) {
-            const anchor1 = linkDoc.anchor1 as Doc;
-            const anchor2 = linkDoc.anchor2 as Doc;
-            runInAction(async () => {
-                this._linkTarget = Doc.AreProtosEqual(anchor1, linkSrc) || Doc.AreProtosEqual(anchor1.annotationOn as Doc, linkSrc) ? anchor2 : anchor1;
-                const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget;
-                runInAction(() => {
-                    this._toolTipText = "";
-                    this._targetDoc = target;
-                });
-            });
+            return href;
         }
+        return undefined;
     }
     deleteLink = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._linkDoc ? LinkManager.Instance.deleteLink(this._linkDoc) : null));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)));
     }
     nextHref = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => {
-            this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
-            this.updatePreview();
-        }));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1)));
     }
 
     followLink = (e: React.PointerEvent) => {
         if (this._linkDoc && this._linkSrc) {
-            LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docprops, false);
+            LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
         } else if (this.props.hrefs?.length) {
-            this.props.docprops?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right");
+            this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right");
         }
     }
     width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
@@ -133,13 +113,12 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                     {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
                     <p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
                 </div>
-                <div className="wrapper" style={{ float: "right" }}>
-                    {(this.props.hrefs?.length || 0) <= 1 ? (null) :
-                        <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
-                            <div className="linkDocPreview-button" onPointerDown={this.nextHref}>
-                                <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
-                            </div>
-                        </Tooltip>}
+                <div className="linkDocPreview-buttonBar" >
+                    <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
+                        <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? "gray" : "green" }} onPointerDown={this.nextHref}>
+                            <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
+                        </div>
+                    </Tooltip>
 
                     <Tooltip title={<div className="dash-tooltip">Delete Link</div>} placement="top">
                         <div className="linkDocPreview-button" onPointerDown={this.deleteLink}>
@@ -151,6 +130,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     }
 
     @computed get docPreview() {
+        const href = this.href; // needs to be here to trigger lookup of web pages and docs on server
         return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
             <div className="linkDocPreview-inner">
                 {!this.props.showHeader ? (null) : this.previewHeader}
@@ -163,8 +143,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             Document={this._targetDoc!}
                             moveDocument={returnFalse}
                             rootSelected={returnFalse}
-                            styleProvider={this.props.docprops?.styleProvider}
-                            layerProvider={this.props.docprops?.layerProvider}
+                            styleProvider={this.props.docProps?.styleProvider}
+                            layerProvider={this.props.docProps?.layerProvider}
                             docViewPath={emptyPath}
                             ScreenToLocalTransform={Transform.Identity}
                             parentActive={returnFalse}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fece45e18..2434bee40 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -177,6 +177,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             });
             if (allFoundLinkAnchors.length) {
                 this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allAnchors: allFoundLinkAnchors }));
+
+                this.setupEditor(this.config, this.fieldKey);
             }
         }
     }
@@ -186,6 +188,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         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 }));
+            this.setupEditor(this.config, this.fieldKey);
         }
     }
 
@@ -1233,7 +1236,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
-            // !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
             const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
             if (startupText) {
                 const { state: { tr }, dispatch } = this._editorView;
@@ -1376,6 +1378,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     onFocused = (e: React.FocusEvent): void => {
         FormattedTextBox.FocusedBox = this;
         this.tryUpdateHeight();
+        //applyDevTools.applyDevTools(this._editorView);
 
         // see if we need to preserve the insertion point
         const prosediv = this.ProseRef?.children?.[0] as any;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index e4a1e0f1b..89df5e246 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,9 +1,7 @@
 import { Mark, ResolvedPos } from "prosemirror-model";
 import { EditorState } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
-import { Doc, DocListCast } from "../../../../fields/Doc";
-import { Utils } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
+import { Doc } from "../../../../fields/Doc";
 import { LinkDocPreview } from "../LinkDocPreview";
 import { FormattedTextBox } from "./FormattedTextBox";
 import './FormattedTextBoxComment.scss';
@@ -37,54 +35,51 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
 export class FormattedTextBoxComment {
     static tooltip: HTMLElement;
     static tooltipText: HTMLElement;
-    static start: number;
-    static end: number;
-    static mark: Mark;
+    static startUserMarkRegion: number;
+    static endUserMarkRegion: number;
+    static userMark: Mark;
     static textBox: FormattedTextBox | undefined;
 
     constructor(view: any) {
         if (!FormattedTextBoxComment.tooltip) {
-            FormattedTextBoxComment.tooltip = document.createElement("div");
-            FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
-            FormattedTextBoxComment.tooltip.style.display = "none";
-            FormattedTextBoxComment.tooltipText = document.createElement("div");
-            FormattedTextBoxComment.tooltipText.className = "FormattedTextBox-tooltipText";
-            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
-            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
-                const textBox = FormattedTextBoxComment.textBox;
-                false && FormattedTextBoxComment.start !== undefined && textBox?.adoptAnnotation(
-                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
+            const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div");
+            const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div");
+            tooltip.className = "FormattedTextBox-tooltip";
+            tooltipText.className = "FormattedTextBox-tooltipText";
+            tooltip.style.display = "none";
+            tooltip.appendChild(tooltipText);
+            tooltip.onpointerdown = (e: PointerEvent) => {
+                const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
+                false && startUserMarkRegion !== undefined && textBox?.adoptAnnotation(startUserMarkRegion, endUserMarkRegion, userMark);
                 e.stopPropagation();
                 e.preventDefault();
             };
-            document.getElementById("root")?.appendChild(FormattedTextBoxComment.tooltip);
+            document.getElementById("root")?.appendChild(tooltip);
         }
     }
     public static Hide() {
         FormattedTextBoxComment.textBox = undefined;
         FormattedTextBoxComment.tooltip.style.display = "none";
     }
-    public static SetState(textBox: any, start: number, end: number, mark: Mark) {
+    public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) {
         FormattedTextBoxComment.textBox = textBox;
-        FormattedTextBoxComment.start = start;
-        FormattedTextBoxComment.end = end;
-        FormattedTextBoxComment.mark = mark;
+        FormattedTextBoxComment.startUserMarkRegion = start;
+        FormattedTextBoxComment.endUserMarkRegion = end;
+        FormattedTextBoxComment.userMark = mark;
         FormattedTextBoxComment.tooltip.style.display = "";
     }
 
-    static showCommentbox(set: string, view: EditorView, nbef: number) {
+    static showCommentbox(view: EditorView, nbef: number) {
         const state = view.state;
-        if (set !== "none") {
-            // These are in screen coordinates
-            const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-            // The box in which the tooltip is positioned, to use as base
-            const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
-            // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
-            const left = Math.max((start.left + end.left) / 2, start.left + 3);
-            FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
-            FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
-        }
-        FormattedTextBoxComment.tooltip.style.display = set;
+        // These are in screen coordinates
+        const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+        // The box in which the tooltip is positioned, to use as base
+        const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+        // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
+        const left = Math.max((start.left + end.left) / 2, start.left + 3);
+        FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+        FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+        FormattedTextBoxComment.tooltip.style.display = "";
     }
 
     static update(view: EditorView, lastState?: EditorState, hrefs: string = "") {
@@ -96,7 +91,6 @@ export class FormattedTextBoxComment {
     static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) {
         const state = view.state;
         // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
-        var hide = true;
         if (state.selection.$from) {
             const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
@@ -105,43 +99,26 @@ export class FormattedTextBoxComment {
             state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             const mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                FormattedTextBoxComment.SetState(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+                FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
             }
             if (mark && child && ((nbef && naft) || !noselection)) {
                 FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString();
-                FormattedTextBoxComment.showCommentbox("", view, nbef);
-                hide = false;
-            }
+                FormattedTextBoxComment.showCommentbox(view, nbef);
+            } else FormattedTextBoxComment.Hide();
         }
+
         // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
-        if (hide && state.selection.$from) {
+        if (state.selection.$from && hrefs) {
             const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
-            let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any) => !child && node.marks.length && (child = node));
-            child = child || (nbef && state.selection.$from.nodeBefore);
-            const mark = child ? findLinkMark(child.marks) : undefined;
-            const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allAnchors.find((item: { href: string }) => item.href)?.href;
-            if ((href && child && nbef && naft && mark?.attrs.showPreview)) {
-                const showPreview = (link?: Doc) => {
-                    return LinkDocPreview.SetLinkInfo({
-                        docprops: textBox.props.docViewPath.lastElement().props,
-                        linkSrc: textBox.props.Document,
-                        linkDoc: link,
-                        location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
-                        hrefs,
-                        showHeader: true
-                    });
-                }
-                const anchorDoc = href.indexOf(Utils.prepend("/doc/")) === 0 ? href.replace(Utils.prepend("/doc/"), "").split("?")[0] : undefined;
-                if (anchorDoc) {
-                    DocServer.GetRefField(anchorDoc).then(anchor => anchor instanceof Doc && showPreview(DocListCast(anchor.links)[0]));
-                } else if (hrefs?.length) {
-                    showPreview()
-                }
-            }
+            nbef && naft && LinkDocPreview.SetLinkInfo({
+                docProps: textBox.props,
+                linkSrc: textBox.rootDoc,
+                location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
+                hrefs,
+                showHeader: true
+            });
         }
-        if (hide) FormattedTextBoxComment.Hide();
     }
 
     destroy() { }
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index aceba0387..655ee7e44 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -23,7 +23,6 @@ export const marks: { [index: string]: MarkSpec } = {
     linkAnchor: {
         attrs: {
             allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
-            showPreview: { default: true },
             location: { default: null },
             title: { default: null },
             docref: { default: false } // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
@@ -32,8 +31,8 @@ export const marks: { [index: string]: MarkSpec } = {
         parseDOM: [{
             tag: "a[href]", getAttrs(dom: any) {
                 return {
-                    allAnchors: [{ href: dom.getAttribute("shref"), title: dom.getAttribute("title"), anchorId: dom.getAttribute("class") }],
                     location: dom.getAttribute("location"),
+                    title: dom.getAttribute("title")
                 };
             }
         }],
@@ -47,7 +46,7 @@ export const marks: { [index: string]: MarkSpec } = {
                     href: node.attrs.allAnchors[0].href,
                 }, node.attrs.title], ["br"]] :
                 //node.attrs.allLinks.length === 1 ?
-                ["a", { ...node.attrs, class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, href: node.attrs.allAnchors[0]?.href, style: `text-decoration: "underline"` }, 0];
+                ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
             // ["div", { class: "prosemirror-anchor" },
             //     ["span", { class: "prosemirror-linkBtn" },
             //         ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 746c93868..5bc323a4d 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -143,17 +143,21 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
 // :: ([Mark]) → ?Mark
 // Tests whether there is a mark of this type in the given set.
 function isInSetWithAttrs(mark, set, attrs) {
+    var markAllAnchors = attrs.allAnchors !== undefined ? Array.from(attrs.allAnchors.map(al => al.anchorId)) : [];
     for (var i = 0; i < set.length; i++) {
         if (set[i].type == mark) {
-            if (Array.from(Object.keys(attrs)).reduce((p, akey) => {
+            return Array.from(Object.keys(attrs)).reduce((p, akey) => {
                 if (p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey])) return true;
-                set[i].attrs.allLinks = Array.from(set[i].attrs.allLinks).filter(a => !Array.from(attrs.allLinks.map(al => al.targetId)).includes(a.targetId) || !Array.from(attrs.allLinks.map(al => al.linkId).includes(a.linkId)))
+                // bcz: hack to allow special case of anchors in Dash to be removed from a mark which has multiple
+                if (set[i].attrs.allAnchors !== undefined) {
+                    set[i].attrs.allAnchors = set[i].attrs.allAnchors.filter(a => !markAllAnchors.includes(a.anchorId));
+                    if (set[i].attrs.allAnchors.length) return undefined;
+                }
                 return false;
-            }, true)) {
-                return set[i];
-            }
+            }, true);
         }
     }
+    return undefined;
 };
 
 // :: (number, number, ?union<Mark, MarkType>) → this
@@ -170,7 +174,9 @@ function removeMarkWithAttrs(tr, from, to, mark, attrs) {
         step++;
         var toRemove = null;
         if (mark) {
-            if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; }
+            const inset = isInSetWithAttrs(mark, node.marks, attrs);
+            if (inset === true) { toRemove = [mark]; }
+            // if (inset === undefined) { console.log("lightened")  } // anchorids were removed from the mark, but the mark wasn't removed since there are still anchorsids left
         } else {
             toRemove = node.marks;
         }
-- 
cgit v1.2.3-70-g09d2


From 861363c59997db5cde1b96786b156b64630465d7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Feb 2021 10:35:34 -0500
Subject: fixed some z-index issues with lightbox and typing new notes +
 videobox zooming zindex

---
 src/client/views/LightboxView.scss                        | 2 +-
 src/client/views/PreviewCursor.scss                       | 1 +
 src/client/views/collections/CollectionMenu.scss          | 6 +++---
 src/client/views/nodes/VideoBox.scss                      | 5 +++++
 src/client/views/nodes/VideoBox.tsx                       | 2 +-
 src/client/views/nodes/WebBox.scss                        | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 ++-
 src/client/views/search/SearchBox.scss                    | 2 +-
 8 files changed, 15 insertions(+), 8 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index 9847966bf..35f11b699 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -4,7 +4,7 @@
     width: 100%;
     height: 100%;
     background: #000000bb;
-    z-index: 10000;
+    z-index: 1000;
     .lightboxView-contents {
         position: absolute;
     }
diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss
index d384fd284..de9bd69c4 100644
--- a/src/client/views/PreviewCursor.scss
+++ b/src/client/views/PreviewCursor.scss
@@ -7,4 +7,5 @@
     left:0;
     pointer-events: none;
     opacity: 1;
+    z-index: 1001;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 180eaba4e..47adb6a1c 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -6,7 +6,7 @@
     display: inline-flex;
     width: 100%;
     opacity: 0.9;
-    z-index: 9001;
+    z-index: 901;
     transition: top .5s;
     background: #323232;
     color: white;
@@ -30,7 +30,7 @@
         height: 32px;
         border-bottom: .5px solid rgb(180, 180, 180);
         overflow: visible;
-        z-index: 9001;
+        z-index: 901;
         border: unset;
 
         .collectionViewBaseChrome {
@@ -325,7 +325,7 @@
 .webBox-urlEditor {
     position: relative;
     opacity: 0.9;
-    z-index: 9001;
+    z-index: 901;
     transition: top .5s;
 
     .urlEditor {
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index b9123587b..dd8d77603 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -13,6 +13,11 @@
     .collectionStackedTimeline {
         background: beige;
     }
+    .videoBox-stackPanel {
+        z-index: -1;
+        width: 100%;
+        position: absolute; 
+    }
 }
 
 .videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 324861573..a821eb7c2 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -489,7 +489,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time;
     timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100;
     @computed get renderTimeline() {
-        return <div style={{ width: "100%", transition: this.transition, height: `${100 - this.heightPercent}%`, position: "absolute" }}>
+        return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
             <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
                 fieldKey={this.annotationKey}
                 renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 5a7c1c904..ca6611a6b 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -96,7 +96,7 @@
     }
 
     .webBox-annotationToggle {
-        z-index: 9001;
+        z-index: 901;
         position: absolute;
         top: 2;
         left: 2;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2434bee40..a91157519 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -66,6 +66,7 @@ import { StyleProp } from '../../StyleProvider';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
 import { DocumentManager } from '../../../util/DocumentManager';
+import { LightboxView } from '../../LightboxView';
 const translateGoogleApi = require("translate-google-api");
 
 export interface FormattedTextBoxProps {
@@ -1244,7 +1245,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
+        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || this.props.docViewPath.includes(LightboxView.LightboxDocView.current!));
         if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 76f9b66eb..4f5b7e41a 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -13,7 +13,7 @@
     overflow-x: visible;
     background: lightgrey;
     overflow: visible;
-    z-index: 10000;
+    z-index: 1000;
 
     .searchBox-bar {
         height: $searchpanel-height;
-- 
cgit v1.2.3-70-g09d2


From 5e9faf9fc5a2aeb3b5c0377b756a746934097619 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Feb 2021 11:41:26 -0500
Subject: changed docViewPath to be a function to avoid mobx invalidations

---
 src/client/views/GestureOverlay.tsx                        |  2 +-
 src/client/views/LightboxView.tsx                          |  2 +-
 src/client/views/MainView.tsx                              | 14 +++++++-------
 src/client/views/OverlayView.tsx                           |  2 +-
 src/client/views/Palette.tsx                               |  2 +-
 src/client/views/PropertiesView.tsx                        |  4 ++--
 src/client/views/TemplateMenu.tsx                          |  2 +-
 src/client/views/collections/CollectionLinearView.tsx      |  4 ++--
 src/client/views/collections/CollectionSchemaView.tsx      |  4 ++--
 src/client/views/collections/SchemaTable.tsx               |  2 +-
 src/client/views/collections/TabDocView.tsx                |  4 ++--
 src/client/views/collections/TreeView.tsx                  |  4 ++--
 .../collectionMulticolumn/CollectionMulticolumnView.tsx    |  4 ++--
 .../collectionMulticolumn/CollectionMultirowView.tsx       |  4 ++--
 src/client/views/nodes/DocumentView.tsx                    |  9 +++++----
 src/client/views/nodes/KeyValuePair.tsx                    |  2 +-
 src/client/views/nodes/LinkDocPreview.tsx                  |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx  |  2 +-
 src/client/views/presentationview/PresElementBox.tsx       |  4 ++--
 src/mobile/AudioUpload.tsx                                 |  2 +-
 src/mobile/MobileInterface.tsx                             |  2 +-
 21 files changed, 39 insertions(+), 38 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 9306cf9ae..d7e7e055f 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -872,7 +872,7 @@ export class GestureOverlay extends Touchable {
                 renderDepth={0}
                 styleProvider={returnEmptyString}
                 layerProvider={undefined}
-                docViewPath={emptyPath}
+                docViewPath={returnEmptyDoclist}
                 focus={emptyFunction}
                 parentActive={returnTrue}
                 whenActiveChanged={emptyFunction}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index aa6e4d909..f19aff5f0 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -66,7 +66,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
                         rootSelected={returnTrue}
-                        docViewPath={emptyPath}
+                        docViewPath={returnEmptyDoclist}
                         removeDocument={undefined}
                         styleProvider={DefaultStyleProvider}
                         layerProvider={returnTrue}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d09b0269f..4c164b85b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -249,7 +249,7 @@ export class MainView extends React.Component {
             addDocument={undefined}
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
-            docViewPath={emptyPath}
+            docViewPath={returnEmptyDoclist}
             layerProvider={undefined}
             styleProvider={undefined}
             rootSelected={returnTrue}
@@ -340,7 +340,7 @@ export class MainView extends React.Component {
                         addDocument={undefined}
                         addDocTab={this.addDocTabFunc}
                         pinToPres={emptyFunction}
-                        docViewPath={emptyPath}
+                        docViewPath={returnEmptyDoclist}
                         layerProvider={undefined}
                         styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards ? this.DashboardStyleProvider : DefaultStyleProvider}
                         rootSelected={returnTrue}
@@ -379,7 +379,7 @@ export class MainView extends React.Component {
                 PanelWidth={this.menuPanelWidth}
                 PanelHeight={this.getContentsHeight}
                 renderDepth={0}
-                docViewPath={emptyPath}
+                docViewPath={returnEmptyDoclist}
                 focus={emptyFunction}
                 styleProvider={DefaultStyleProvider}
                 layerProvider={undefined}
@@ -490,7 +490,7 @@ export class MainView extends React.Component {
                     select={emptyFunction}
                     active={returnFalse}
                     isSelected={returnFalse}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     moveDocument={this.moveButtonDoc}
                     CollectionView={undefined}
                     addDocument={this.addButtonDoc}
@@ -564,7 +564,7 @@ export class MainView extends React.Component {
                 PanelHeight={this.getPHeight}
                 renderDepth={0}
                 focus={emptyFunction}
-                docViewPath={emptyPath}
+                docViewPath={returnEmptyDoclist}
                 parentActive={returnFalse}
                 whenActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
@@ -599,7 +599,7 @@ export class MainView extends React.Component {
                     active={returnFalse}
                     whenActiveChanged={returnFalse}
                     focus={returnFalse}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     PanelWidth={() => 500}
                     PanelHeight={() => 800}
                     docFilters={returnEmptyFilter}
@@ -657,7 +657,7 @@ export class MainView extends React.Component {
                             Document={invisibleDoc}
                             dropAction={"move"}
                             isSelected={returnFalse}
-                            docViewPath={emptyPath}
+                            docViewPath={returnEmptyDoclist}
                             select={returnFalse}
                             rootSelected={returnFalse}
                             renderDepth={0}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 628003391..64f907f4c 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -194,7 +194,7 @@ export class OverlayView extends React.Component {
                     focus={emptyFunction}
                     styleProvider={DefaultStyleProvider}
                     layerProvider={undefined}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     addDocTab={returnFalse}
                     pinToPres={emptyFunction}
                     docFilters={returnEmptyFilter}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 6268be31c..fbf67f0a0 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -50,7 +50,7 @@ export default class Palette extends React.Component<PaletteProps> {
                             PanelHeight={() => window.screen.height}
                             renderDepth={0}
                             focus={emptyFunction}
-                            docViewPath={emptyPath}
+                            docViewPath={returnEmptyDoclist}
                             styleProvider={returnEmptyString}
                             layerProvider={undefined}
                             parentActive={returnTrue}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index a5ddf8044..6ee5e4d8c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -11,7 +11,7 @@ import { InkField } from "../../fields/InkField";
 import { ComputedField } from "../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, emptyPath } from "../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../Utils";
 import { DocumentType } from "../documents/DocumentTypes";
 import { DocumentManager } from "../util/DocumentManager";
 import { SelectionManager } from "../util/SelectionManager";
@@ -271,7 +271,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     rootSelected={returnFalse}
                     styleProvider={DefaultStyleProvider}
                     layerProvider={undefined}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     freezeDimensions={true}
                     dontCenter={"y"}
                     NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f9aae64f1..cdaf1bd0c 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -133,7 +133,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 ContainingCollectionView={undefined}
                 styleProvider={DefaultStyleProvider}
                 layerProvider={undefined}
-                docViewPath={emptyPath}
+                docViewPath={returnEmptyDoclist}
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 79cc1719c..ead0ce6c4 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -7,7 +7,7 @@ import { documentSchema } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { makeInterface } from '../../../fields/Schema';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnTrue, Utils, emptyPath } from '../../../Utils';
+import { emptyFunction, returnTrue, Utils, emptyPath, returnEmptyDoclist } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { Transform } from '../../util/Transform';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
@@ -153,7 +153,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 focus={emptyFunction}
                                 styleProvider={this.props.styleProvider}
                                 layerProvider={this.props.layerProvider}
-                                docViewPath={emptyPath}
+                                docViewPath={returnEmptyDoclist}
                                 parentActive={returnTrue}
                                 whenActiveChanged={emptyFunction}
                                 bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index e246dc020..66064e354 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema";
 import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
 import { Cast, NumCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { SnappingManager } from "../../util/SnappingManager";
 import { Transform } from "../../util/Transform";
@@ -415,7 +415,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                     searchFilterDocs={this.searchFilterDocs}
                     styleProvider={DefaultStyleProvider}
                     layerProvider={undefined}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     ContainingCollectionDoc={this.props.CollectionView?.props.Document}
                     ContainingCollectionView={this.props.CollectionView}
                     moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 3c83c241b..d4b4cf333 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -573,7 +573,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                         DataDoc={this._showDataDoc}
                         styleProvider={DefaultStyleProvider}
                         layerProvider={undefined}
-                        docViewPath={emptyPath}
+                        docViewPath={returnEmptyDoclist}
                         freezeDimensions={true}
                         focus={emptyFunction}
                         renderDepth={this.props.renderDepth}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 1026da64c..86276b693 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -315,7 +315,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
                     parentActive={returnFalse}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
                     active={returnTrue}
@@ -411,7 +411,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 parentActive={this.active}
                 whenActiveChanged={emptyFunction}
                 focus={this.focusFunc}
-                docViewPath={emptyPath}
+                docViewPath={returnEmptyDoclist}
                 bringToFront={emptyFunction}
                 pinToPres={TabDocView.PinDoc} />
                 {this._document._viewType !== CollectionViewType.Freeform ? (null) :
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 5deeb6997..6b7911b13 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -550,7 +550,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 DataDoc={undefined}
                 styleProvider={this.titleStyleProvider}
                 layerProvider={undefined}
-                docViewPath={emptyPath}
+                docViewPath={returnEmptyDoclist}
                 treeViewDoc={this.props.treeView.props.Document}
                 addDocument={undefined}
                 addDocTab={this.props.addDocTab}
@@ -642,7 +642,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             rootSelected={returnTrue}
             styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
             layerProvider={undefined}
-            docViewPath={emptyPath}
+            docViewPath={returnEmptyDoclist}
             docFilters={returnEmptyFilter}
             docRangeFilters={returnEmptyFilter}
             searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 3d9673438..e51417f64 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -6,7 +6,7 @@ import { documentSchema } from '../../../../fields/documentSchemas';
 import { List } from '../../../../fields/List';
 import { makeInterface } from '../../../../fields/Schema';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse, emptyPath } from '../../../../Utils';
+import { returnFalse, emptyPath, returnEmptyDoclist } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
@@ -218,7 +218,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             DataDoc={layout.resolvedDataDoc as Doc}
             styleProvider={this.props.styleProvider}
             layerProvider={undefined}
-            docViewPath={emptyPath}
+            docViewPath={returnEmptyDoclist}
             LayoutTemplate={this.props.childLayoutTemplate}
             LayoutTemplateString={this.props.childLayoutString}
             freezeDimensions={this.props.childFreezeDimensions}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 278ca20c1..d61a2bb72 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -6,7 +6,7 @@ import { documentSchema } from '../../../../fields/documentSchemas';
 import { List } from '../../../../fields/List';
 import { makeInterface } from '../../../../fields/Schema';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse, emptyPath } from '../../../../Utils';
+import { returnFalse, emptyPath, returnEmptyDoclist } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
@@ -218,7 +218,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             DataDoc={layout.resolvedDataDoc as Doc}
             styleProvider={this.props.styleProvider}
             layerProvider={undefined}
-            docViewPath={emptyPath}
+            docViewPath={returnEmptyDoclist}
             LayoutTemplate={this.props.childLayoutTemplate}
             LayoutTemplateString={this.props.childLayoutString}
             freezeDimensions={this.props.childFreezeDimensions}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8afa53eac..190dfd6be 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -65,7 +65,7 @@ export interface DocumentViewSharedProps {
     CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    docViewPath: DocumentView[];
+    docViewPath: () => DocumentView[];
     layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
     styleProvider: Opt<StyleProviderFunc>;
     focus: DocFocusFunc;
@@ -117,7 +117,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (ctrlPressed: boolean) => void;
     DocumentView: any;
-    viewPath: DocumentView[];
+    viewPath: () => DocumentView[];
 }
 
 @observer
@@ -884,7 +884,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     get allLinks() { return this.docView?.allLinks || []; }
     get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
 
-    @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath, this] : [this]; }
+    @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
     @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
     @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); }
     @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); }
@@ -955,6 +955,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }), 400);
     });
 
+    docViewPathFunc = () => this.docViewPath;
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
     select = (ctrlPressed: boolean) => SelectionManager.SelectView(this, ctrlPressed);
     NativeWidth = () => this.nativeWidth;
@@ -978,7 +979,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         const internalProps = {
             ...this.props,
             DocumentView: this,
-            viewPath: this.docViewPath,
+            viewPath: this.docViewPathFunc,
             PanelWidth: this.PanelWidth,
             PanelHeight: this.PanelHeight,
             NativeWidth: this.NativeWidth,
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 6f222d53b..ce9d8bed5 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,7 +61,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             layerProvider: undefined,
-            docViewPath: emptyPath,
+            docViewPath: returnEmptyDoclist,
             ContainingCollectionView: undefined,
             ContainingCollectionDoc: undefined,
             fieldKey: this.props.keyName,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index eb27a6241..6155ed493 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -145,7 +145,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             rootSelected={returnFalse}
                             styleProvider={this.props.docProps?.styleProvider}
                             layerProvider={this.props.docProps?.layerProvider}
-                            docViewPath={emptyPath}
+                            docViewPath={returnEmptyDoclist}
                             ScreenToLocalTransform={Transform.Identity}
                             parentActive={returnFalse}
                             addDocument={returnFalse}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a91157519..9e2e8f1be 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1245,7 +1245,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || this.props.docViewPath.includes(LightboxView.LightboxDocView.current!));
+        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || this.props.docViewPath().includes(LightboxView.LightboxDocView.current!));
         if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 9f9200fa4..01a55660a 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -7,7 +7,7 @@ import { documentSchema } from '../../../fields/documentSchemas';
 import { Id } from "../../../fields/FieldSymbols";
 import { createSchema, makeInterface } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, emptyPath } from "../../../Utils";
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, emptyPath, returnEmptyDoclist } from "../../../Utils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { DocumentManager } from "../../util/DocumentManager";
@@ -93,7 +93,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
                     DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     styleProvider={this.styleProvider}
                     layerProvider={this.props.layerProvider}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     rootSelected={returnTrue}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index fe3595181..cbae71270 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -96,7 +96,7 @@ export class AudioUpload extends React.Component {
                         focus={emptyFunction}
                         layerProvider={undefined}
                         styleProvider={() => "rgba(0,0,0,0)"}
-                        docViewPath={emptyPath}
+                        docViewPath={returnEmptyDoclist}
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index f6c283c3a..058c61a6d 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -213,7 +213,7 @@ export class MobileInterface extends React.Component {
                     focus={emptyFunction}
                     styleProvider={this.whitebackground}
                     layerProvider={undefined}
-                    docViewPath={emptyPath}
+                    docViewPath={returnEmptyDoclist}
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-- 
cgit v1.2.3-70-g09d2


From 0f560a9032076d1ed096740e04a54d484ee0aaf6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Feb 2021 16:06:53 -0500
Subject: fixes for stacking views to allow new text to be entered properly
 without losing focus.  fixed document decorations for stacking views.  fixed
 lightboxView criteria for going to an existing view in a lightbox instad of
 creating a new one.

---
 src/client/util/DocumentManager.ts                 | 20 +++++----
 src/client/util/LinkManager.ts                     |  3 +-
 .../views/collections/CollectionStackingView.tsx   | 49 ++++++++++------------
 src/client/views/collections/TreeView.tsx          |  2 +-
 src/client/views/nodes/DocumentContentsView.tsx    | 21 +++++++---
 src/client/views/nodes/DocumentView.tsx            | 31 ++++++++------
 .../nodes/formattedText/FormattedTextBox.scss      |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 8 files changed, 72 insertions(+), 56 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 2a48f74b2..811d04eb3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -101,22 +101,28 @@ export class DocumentManager {
         return this.getDocumentViewById(toFind[Id], preferredCollection);
     }
 
+    public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+        const docViews = DocumentManager.Instance.DocumentViews;
+        const views: DocumentView[] = [];
+        docViews.map(view => view.docViewPath.includes(LightboxView.LightboxDocView.current!) && view.rootDoc === toFind && views.push(view));
+        return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
+    }
     public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
         const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc);
         return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
     }
     public getDocumentViews(toFind: Doc): DocumentView[] {
         const toReturn: DocumentView[] = [];
-        const docViews = DocumentManager.Instance.DocumentViews;
+        const docViews = DocumentManager.Instance.DocumentViews.filter(view => !view.docViewPath.includes(LightboxView.LightboxDocView.current!));
+        const lightboxViews = DocumentManager.Instance.DocumentViews.filter(view => view.docViewPath.includes(LightboxView.LightboxDocView.current!));
 
         // heuristic to return the "best" documents first:
+        //   choose a document in the lightbox first
         //   choose an exact match over an alias match
-        //   choose documents that have a PanelWidth() over those that don't (the treeview documents have no panelWidth)
-        docViews.map(view => view.docViewPath.includes(LightboxView.LightboxDocView.current!) && view.rootDoc === toFind && toReturn.push(view));
-        docViews.map(view => view.props.PanelWidth() > 1 && view.rootDoc === toFind && toReturn.push(view));
-        docViews.map(view => view.props.PanelWidth() <= 1 && view.rootDoc === toFind && toReturn.push(view));
-        docViews.map(view => view.props.PanelWidth() > 1 && view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
-        docViews.map(view => view.props.PanelWidth() <= 1 && view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
+        lightboxViews.map(view => view.rootDoc === toFind && toReturn.push(view));
+        lightboxViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
+        docViews.map(view => view.rootDoc === toFind && toReturn.push(view));
+        docViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
 
         return toReturn;
     }
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index c32a78ef3..a684a202d 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -145,7 +145,8 @@ export class LinkManager {
                     doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
                         (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
                 if (target) {
-                    if (LightboxView.LightboxDoc && doc.annotationOn !== LightboxView.LightboxDoc) { // following a link should replace an existing lightboxDoc unless the target is an annotation on the lightbox document
+                    if (LightboxView.LightboxDoc && !DocumentManager.Instance.getLightboxDocumentView(doc)) {
+                        //doc.annotationOn !== LightboxView.LightboxDoc) { // following a link should replace an existing lightboxDoc unless the target is an annotation on the lightbox document
                         runInAction(() => LightboxView.LightboxDoc = (target.annotationOn as Doc) ?? target);
                         finished?.();
                     } else {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 745987780..4e093a83b 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -80,13 +80,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
             const width = () => this.getDocWidth(d);
-            const dref = React.createRef<HTMLDivElement>();
-            const dxf = () => this.getDocTransform(d, dref.current!);
-            this._docXfs.push({ dxf, width, height });
             const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
             const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
-            return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
-                {this.getDisplayDoc(d, dxf, width)}
+            return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} style={style} >
+                {this.getDisplayDoc(d, width)}
             </div>;
         });
     }
@@ -171,7 +168,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         return this.props.addDocTab(doc, where);
     }
 
-
     focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, didFocus?: boolean) => {
         Doc.BrushDoc(doc);
         this.props.focus(this.props.Document, true);  // bcz: want our containing collection to zoom
@@ -186,27 +182,30 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         afterFocus && setTimeout(afterFocus, 500);
     }
 
-    getDisplayDoc(doc: Doc, dxf: () => Transform, width: () => number) {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
+        if (property === StyleProp.Opacity && doc) {
+            if (this.props.childOpacity) {
+                return this.props.childOpacity();
+            }
+            if (this.Document._currentFrame !== undefined) {
+                return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
+            }
+        }
+        return this.props.styleProvider?.(doc, props, property);
+    };
+    getDisplayDoc(doc: Doc, width: () => number) {
         const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
-        const styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
-            if (property === StyleProp.Opacity && doc) {
-                if (this.props.childOpacity) {
-                    return this.props.childOpacity();
-                }
-                if (this.Document._currentFrame !== undefined) {
-                    return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
-                }
-            }
-            return this.props.styleProvider?.(doc, props, property);
-        };
-        return <DocumentView
+
+        let dref: Opt<HTMLDivElement>;
+        const stackedDocTransform = () => this.getDocTransform(doc, dref);
+        return <DocumentView ref={r => dref = r?.ContentDiv ? r.ContentDiv : undefined}
             Document={doc}
             DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
             renderDepth={this.props.renderDepth + 1}
             PanelWidth={width}
             PanelHeight={height}
-            styleProvider={styleProvider}
+            styleProvider={this.styleProvider}
             layerProvider={this.props.layerProvider}
             docViewPath={this.props.docViewPath}
             LayoutTemplate={this.props.childLayoutTemplate}
@@ -220,7 +219,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
             onDoubleClick={this.onChildDoubleClickHandler}
-            ScreenToLocalTransform={dxf}
+            ScreenToLocalTransform={stackedDocTransform}
             focus={this.focusDocument}
             docFilters={this.docFilters}
             docRangeFilters={this.docRangeFilters}
@@ -387,14 +386,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         />;
     }
 
-    getDocTransform(doc: Doc, dref: HTMLDivElement) {
-        if (!dref) return Transform.Identity();
+    getDocTransform(doc: Doc, dref?: HTMLDivElement) {
         const y = this._scroll; // required for document decorations to update when the text box container is scrolled
         const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
-        const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
-        const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        const offsety = 0;
-        return this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + offsety);
+        return new Transform(-translateX, -translateY, 1).scale(this.props.ScreenToLocalTransform().Scale);
     }
 
     forceAutoHeight = () => {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6b7911b13..14075db1f 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -642,7 +642,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             rootSelected={returnTrue}
             styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
             layerProvider={undefined}
-            docViewPath={returnEmptyDoclist}
+            docViewPath={this.props.treeView.props.docViewPath}
             docFilters={returnEmptyFilter}
             docRangeFilters={returnEmptyFilter}
             searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 6ecd70330..4b4720d58 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -111,7 +111,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
     scaling?: () => number,
     layoutKey: string,
     hideOnLeave?: boolean,
-    makeLink?: () => Opt<Doc>,  // function to call when a link is made
 }> {
     @computed get layout(): string {
         TraceMobx();
@@ -141,16 +140,17 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
         const docOnlyProps = [  // these are the properties in  DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
             "freezeDimensions",
+            "hideResizeHandles",
             "hideTitle",
             "treeViewDoc",
-            "dragDivName",
             "contentPointerEvents",
             "radialMenu",
             "LayoutTemplateString",
             "LayoutTemplate",
+            "dontCenter",
             "ContentScaling",
-            "contentFittingScaling",
             "contextMenuItems",
+            "onClick",
             "onDoubleClick",
             "onPointerDown",
             "onPointerUp",
@@ -166,7 +166,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
         return { props: list };
     }
 
-    render() {
+    componentWillUpdate(oldProps: any, newState: any) {
+        // console.log("willupdate", oldProps, this.props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
+    }
+
+    @computed get renderData() {
         TraceMobx();
         let layoutFrame = this.layout;
 
@@ -201,9 +205,14 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
         };
         const onClick = makeFuncProp("onClick");
         const onInput = makeFuncProp("onInput");
-
         const bindings = this.CreateBindings(onClick, onInput);
-        //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
+        return { bindings, layoutFrame };
+    }
+
+    render() {
+        TraceMobx();
+        const { bindings, layoutFrame } = this.renderData;
+
         return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
             <ObserverJsxParser
                 key={42}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 190dfd6be..47668d0f3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -116,7 +116,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
     NativeHeight: () => number;
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (ctrlPressed: boolean) => void;
-    DocumentView: any;
+    DocumentView: () => DocumentView;
     viewPath: () => DocumentView[];
 }
 
@@ -154,7 +154,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
     @computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
     @computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
-    @computed get finalLayoutKey() { return StrCast(this.props.Document.layoutKey, "layout"); }
+    @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
     @computed get nativeWidth() { return this.props.NativeWidth(); }
     @computed get nativeHeight() { return this.props.NativeHeight(); }
     @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
@@ -346,7 +346,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     startDragging(x: number, y: number, dropAction: dropActionType) {
         if (this._mainCont.current) {
             const ffview = this.props.CollectionFreeFormDocumentView;
-            ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView));
+            ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView()));
             const dragData = new DragManager.DocumentDragData([this.props.Document]);
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
@@ -398,7 +398,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             self: this.rootDoc,
                             scriptContext: this.props.scriptContext,
                             thisContainer: this.props.ContainingCollectionDoc,
-                            documentView: this.props.DocumentView,
+                            documentView: this.props.DocumentView(),
                             clientX: e.clientX,
                             clientY: e.clientY,
                             shiftKey: e.shiftKey
@@ -424,7 +424,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     self: this.rootDoc,
                     scriptContext: this.props.scriptContext,
                     thisContainer: this.props.ContainingCollectionDoc,
-                    documentView: this.props.DocumentView,
+                    documentView: this.props.DocumentView(),
                     clientX: clientX,
                     clientY: clientY,
                     shiftKey
@@ -461,7 +461,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
             if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
                 e.stopPropagation();
-                if (SelectionManager.IsSelected(this.props.DocumentView, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
+                if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
                 // TODO: check here for panning/inking
             }
             return;
@@ -476,7 +476,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
                 !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
                 e.stopPropagation();
-                if (SelectionManager.IsSelected(this.props.DocumentView, true) && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
+                if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
             }
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
@@ -599,7 +599,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
             e.preventDefault();
             e.stopPropagation();
-            !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView, false);
+            !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
         }
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (e) {
@@ -678,7 +678,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const more = cm.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
         if (!Doc.IsSystem(this.rootDoc)) {
-            (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView), icon: "users" });
+            (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
             if (!Doc.UserDoc().noviceMode) {
                 moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
                 moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -708,7 +708,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
-        !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView, false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+        !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
     }
 
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
@@ -741,7 +741,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 layoutKey={this.finalLayoutKey} />
             {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
             {this.hideLinkButton ? (null) :
-                <DocumentLinksButton View={this.props.DocumentView} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
+                <DocumentLinksButton View={this.props.DocumentView()} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
         </div>;
     }
 
@@ -782,7 +782,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
         const showCaption = StrCast(this.layoutDoc._showCaption);
         const captionView = !showCaption ? (null) :
-            <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
+            <div className="documentView-captionWrapper"
+                style={{
+                    backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]),
+                    color: StrCast(this.layoutDoc["caption-color"])
+                }}>
                 <DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
                     yMargin={10}
                     xMargin={10}
@@ -963,6 +967,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     PanelWidth = () => this.panelWidth;
     PanelHeight = () => this.panelHeight;
     ContentScale = () => this.nativeScaling;
+    selfView = () => this;
     screenToLocalTransform = () => {
         return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling);
     }
@@ -978,7 +983,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         TraceMobx();
         const internalProps = {
             ...this.props,
-            DocumentView: this,
+            DocumentView: this.selfView,
             viewPath: this.docViewPathFunc,
             PanelWidth: this.PanelWidth,
             PanelHeight: this.PanelHeight,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 81bca4c00..33425ba15 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -389,6 +389,7 @@ footnote::after {
         overflow-x: hidden;
         color: initial;
         max-height: 100%;
+        width: 100%;
         display: flex;
         flex-direction: row;
         transition: opacity 1s;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9e2e8f1be..d608cc5df 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1719,7 +1719,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 <div className={`formattedTextBox-cont`} ref={this._ref}
                     style={{
                         overflow: this.layoutDoc._autoHeight ? "hidden" : undefined,
-                        width: "100%",
                         height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined),
                         background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)),
                         color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)),
-- 
cgit v1.2.3-70-g09d2


From 01081a8d2064127bfeccbd52dfaae10728123b9f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Feb 2021 17:10:23 -0500
Subject: fixed escaping menu to not close lightbox.  fixed formattedTextBox
 dragging of sidebar to not scroll.  fixed css width of formattedTextBox text
 area

---
 src/client/views/GlobalKeyHandler.ts                       | 6 ++++--
 src/client/views/MainView.tsx                              | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.scss | 1 +
 src/client/views/nodes/formattedText/FormattedTextBox.tsx  | 1 +
 4 files changed, 7 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e70dea0f6..09b95315c 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -115,7 +115,6 @@ export class KeyManager {
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
                 InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = false);
-                LightboxView.LightboxDoc = undefined;
 
                 Doc.SetSelectedTool(InkTool.None);
                 var doDeselect = true;
@@ -129,7 +128,10 @@ export class KeyManager {
                 } else {
                     doDeselect = !ContextMenu.Instance.closeMenu();
                 }
-                doDeselect && SelectionManager.DeselectAll();
+                if (doDeselect) {
+                    SelectionManager.DeselectAll();
+                    LightboxView.LightboxDoc = undefined;
+                }
                 DictationManager.Controls.stop();
                 GoogleAuthenticationManager.Instance.cancel();
                 SharingManager.Instance.close();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4c164b85b..ca07fa879 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -640,7 +640,7 @@ export class MainView extends React.Component {
             <TimelineMenu />
             {this.snapLines}
             <div className="mainView-webRef" ref={this.makeWebRef} />
-            <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 100]} />
+            <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
         </div>);
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 33425ba15..0f2f9cdb7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -41,6 +41,7 @@ audiotag:hover {
     display: flex;
     flex-direction: row;
     transition: opacity 1s;
+    width: 100%;
 
     .formattedTextBox-dictation {
         height: 12px;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d608cc5df..81e385e95 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -614,6 +614,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const bounds = this.CurrentDiv.getBoundingClientRect();
         this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
         this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+        e.preventDefault();
         return false;
     }
     @undoBatch
-- 
cgit v1.2.3-70-g09d2


From f240c85ff0adee914b43d9d169fa31260e03265d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Feb 2021 19:06:53 -0500
Subject: fixed following link to document in another context to work in
 LightboxView

---
 src/client/util/DocumentManager.ts                      | 17 +++++++----------
 src/client/util/LinkManager.ts                          |  3 ---
 src/client/views/LightboxView.tsx                       |  1 +
 src/client/views/collections/TabDocView.tsx             |  9 ++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx      |  2 +-
 5 files changed, 13 insertions(+), 19 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d7335c8c6..9a8b662e7 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -104,7 +104,7 @@ export class DocumentManager {
     public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
         const docViews = DocumentManager.Instance.DocumentViews;
         const views: DocumentView[] = [];
-        docViews.map(view => view.docViewPath.includes(LightboxView.LightboxDocView.current!) && view.rootDoc === toFind && views.push(view));
+        docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && view.rootDoc === toFind && views.push(view));
         return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
     }
     public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
@@ -113,14 +113,14 @@ export class DocumentManager {
     }
     public getDocumentViews(toFind: Doc): DocumentView[] {
         const toReturn: DocumentView[] = [];
-        const docViews = DocumentManager.Instance.DocumentViews.filter(view => !view.docViewPath.includes(LightboxView.LightboxDocView.current!));
-        const lightboxViews = DocumentManager.Instance.DocumentViews.filter(view => view.docViewPath.includes(LightboxView.LightboxDocView.current!));
+        const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
+        const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
 
         // heuristic to return the "best" documents first:
         //   choose a document in the lightbox first
         //   choose an exact match over an alias match
-        lightboxViews.map(view => view.rootDoc === toFind && toReturn.push(view));
-        lightboxViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
+        lightViews.map(view => view.rootDoc === toFind && toReturn.push(view));
+        lightViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
         docViews.map(view => view.rootDoc === toFind && toReturn.push(view));
         docViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
 
@@ -142,10 +142,7 @@ export class DocumentManager {
         originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
         finished?: () => void,
     ): Promise<void> => {
-        const getFirstDocView = (toFind: Doc, originatingDoc?: Doc) => {
-            const docView = DocumentManager.Instance.getFirstDocumentView(toFind, originatingDoc);
-            return (!LightboxView.LightboxDoc || docView?.docViewPath.includes(LightboxView.LightboxDocView.current!)) ? docView : undefined;
-        };
+        const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
         const focusAndFinish = () => { finished?.(); return false; };
         const highlight = () => {
             const finalDocView = getFirstDocView(targetDoc);
@@ -218,7 +215,7 @@ export class DocumentManager {
                 } else {  // there's no context view so we need to create one first and try again when that finishes
                     const finishFunc = () => this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished);
                     if (LightboxView.LightboxDoc) {
-                        runInAction(() => LightboxView.LightboxDoc = annotatedDoc ? annotatedDoc : targetDoc);
+                        runInAction(() => LightboxView.LightboxDoc = targetDocContext);
                         setTimeout(() => finishFunc, 250);
                     } else {
                         createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 7ad37f059..9034484d3 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -4,8 +4,6 @@ import { Doc, DocListCast, Opt } from "../../fields/Doc";
 import { BoolCast, Cast, StrCast } from "../../fields/Types";
 import { LightboxView } from "../views/LightboxView";
 import { DocumentViewSharedProps } from "../views/nodes/DocumentView";
-import { FormattedTextBoxComment } from "../views/nodes/formattedText/FormattedTextBoxComment";
-import { LinkDocPreview } from "../views/nodes/LinkDocPreview";
 import { CreateViewFunc, DocumentManager } from "./DocumentManager";
 import { SharingManager } from "./SharingManager";
 import { UndoManager } from "./UndoManager";
@@ -146,7 +144,6 @@ export class LinkManager {
                         (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
                 if (target) {
                     if (LightboxView.LightboxDoc && !DocumentManager.Instance.getLightboxDocumentView(doc)) {
-                        //doc.annotationOn !== LightboxView.LightboxDoc) { // following a link should replace an existing lightboxDoc unless the target is an annotation on the lightbox document
                         runInAction(() => LightboxView.LightboxDoc = (target.annotationOn as Doc) ?? target);
                         finished?.();
                     } else {
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index f19aff5f0..4e9491ec6 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -19,6 +19,7 @@ interface LightboxViewProps {
 @observer
 export class LightboxView extends React.Component<LightboxViewProps> {
     @observable public static LightboxDoc: Opt<Doc>;
+    public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); }
     public static LightboxHistory: (Opt<Doc>)[] = [];
     public static LightboxFuture: (Opt<Doc>)[] = [];
     public static LightboxDocView = React.createRef<DocumentView>();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 86276b693..38e1551e6 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -12,7 +12,7 @@ import { FieldId } from "../../../fields/RefField";
 import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, Utils, returnEmptyDoclist, emptyPath } from "../../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -22,9 +22,10 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocumentView, DocAfterFocusFunc, DocumentViewProps } from "../nodes/DocumentView";
+import { LightboxView } from '../LightboxView';
+import { DocAfterFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import { FieldViewProps } from '../nodes/FieldView';
-import { PresBox, PresMovement, PinProps } from '../nodes/PresBox';
+import { PinProps, PresBox, PresMovement } from '../nodes/PresBox';
 import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
 import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
@@ -33,8 +34,6 @@ import { CollectionViewType } from './CollectionView';
 import "./TabDocView.scss";
 import React = require("react");
 import Color = require('color');
-import { MainView } from '../MainView';
-import { LightboxView } from '../LightboxView';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 81e385e95..c15c30803 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1246,7 +1246,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || this.props.docViewPath().includes(LightboxView.LightboxDocView.current!));
+        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 = "";
             this.props.select(false);
-- 
cgit v1.2.3-70-g09d2


From aed4a386bf57ba7b1b144bacd39f9f9ccabe0dfd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Feb 2021 22:32:29 -0500
Subject: simplified focus'ing on documents.  refactored scrollFocus code. 
 changed focus in 2D to move doc into view but not center.

---
 src/Utils.ts                                       |  7 +++
 src/client/util/DocumentManager.ts                 | 11 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 44 ++++++-------------
 src/client/views/nodes/DocumentView.tsx            | 18 ++++----
 src/client/views/nodes/PDFBox.tsx                  |  3 +-
 src/client/views/nodes/WebBox.tsx                  | 50 ++++++++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++-
 src/client/views/pdf/PDFViewer.tsx                 | 15 ++++---
 8 files changed, 80 insertions(+), 78 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index c7074c3da..061c74611 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -191,6 +191,13 @@ export namespace Utils {
         return { h: h, s: s, l: l };
     }
 
+    export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number) {
+        if (scrollTop + contextHgt < targetY + Math.max(targetHgt * 1.1, 100)) {
+            return Math.min(scrollTop, targetY + Math.max(targetHgt * 1.1, 100) - contextHgt);
+        } else if (scrollTop > targetY - targetHgt * .1) {
+            return Math.max(0, targetY - targetHgt * .1);
+        }
+    }
 
     export function clamp(n: number, lower: number, upper: number) {
         return Math.max(lower, Math.min(upper, n));
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9a8b662e7..ac7710d34 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -150,29 +150,26 @@ export class DocumentManager {
         };
         const docView = getFirstDocView(targetDoc, originatingDoc);
         let annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
-        if (annotatedDoc && annotatedDoc !== originatingDoc?.context && !targetDoc?.isPushpin) {
+        if (!docView && annotatedDoc && annotatedDoc !== originatingDoc?.context && targetDoc.type === DocumentType.TEXTANCHOR) {
             const first = getFirstDocView(annotatedDoc);
             if (first) {
                 annotatedDoc = first.rootDoc;
                 first.focus(targetDoc, false);
             }
-        }
-        if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
-            const sameContext = annotatedDoc && annotatedDoc === originatingDoc?.context;
+        } else if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
             if (originatingDoc?.isPushpin) {
                 docView.props.focus(docView.rootDoc, willZoom, undefined, (didFocus: boolean) => {
                     if (!didFocus || docView.rootDoc.hidden) {
                         docView.rootDoc.hidden = !docView.rootDoc.hidden;
                     }
                     return focusAndFinish();
-                }, sameContext, false);// don't want to focus the container if the source and target are in the same container, so pass 'sameContext' for dontCenter parameter
-                //finished?.();
+                });
             }
             else {
                 docView.select(false);
                 docView.rootDoc.hidden && (docView.rootDoc.hidden = undefined);
                 // @ts-ignore
-                docView.props.focus(docView.rootDoc, willZoom, undefined, focusAndFinish, sameContext, false);
+                docView.props.focus(docView.rootDoc, willZoom, undefined, focusAndFinish);
             }
             highlight();
         } else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a52522def..68a65dfe7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -885,7 +885,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
     }
 
-    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, didFocus?: boolean) => {
+    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
         const state = HistoryUtil.getState();
 
         // TODO This technically isn't correct if type !== "doc", as
@@ -903,31 +903,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
-            // only consider the document to be an annotation if it's an annotation on this collection's document (ignore annotations on some other document that are somehow being focused on here)
-            const annotOn = Doc.AreProtosEqual(doc.annotationOn as Doc, this.props.Document) ? Cast(doc.annotationOn, Doc) as Doc : undefined;
-            let delay = 1000;
-            if (!annotOn) {
-                !dontCenter && this.props.focus(doc);
-                afterFocus && setTimeout(afterFocus, delay);
-            } else {
-                const contextHgt = this.props.PanelHeight();
-                const curScroll = NumCast(this.props.Document._scrollTop);
-                let scrollTo = curScroll;
-                if (curScroll + contextHgt < NumCast(doc.y)) {
-                    scrollTo = NumCast(doc.y) + Math.max(NumCast(doc._height), 100) - contextHgt;
-                } else if (curScroll > NumCast(doc.y)) {
-                    scrollTo = Math.max(0, NumCast(doc.y) - 50);
-                }
-                if (curScroll !== scrollTo || this.props.Document._viewTransition) {
-                    delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0;
-                    !dontCenter && this.props.focus(this.props.Document);
-                    afterFocus && setTimeout(() => afterFocus?.(delay ? true : false), delay);
-                } else {
-                    !dontCenter && delay && this.props.focus(this.props.Document);
-                    afterFocus?.(!dontCenter && delay ? true : false);
-                }
-            }
-
+            this.props.focus(doc, undefined, undefined, afterFocus);
         } else {
             const layoutdoc = Doc.Layout(doc);
             const savedState = { px: NumCast(this.Document._panX), py: NumCast(this.Document._panY), s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
@@ -937,8 +913,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             let newPanY = savedState.py;
             if (!layoutdoc.annotationOn) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 willZoom && this.setScaleToZoom(layoutdoc, scale);
-                newPanX = (NumCast(doc.x) + doc[WidthSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeWidth(this.props.Document)) / 2 / this.zoomScaling() : 0);
-                newPanY = (NumCast(doc.y) + doc[HeightSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeHeight(this.props.Document)) / 2 / this.zoomScaling() : 0);
+                const cx = NumCast(this.props.Document._panX);
+                const cy = NumCast(this.props.Document._panY);
+                const pwid = this.props.PanelWidth() / NumCast(this.props.Document._viewScale, 1);
+                const phgt = this.props.PanelHeight() / NumCast(this.props.Document._viewScale, 1);
+                const screen = { left: cx - pwid / 2, right: cx + pwid / 2, top: cy - phgt / 2, bot: cy + phgt / 2 };
+                const bounds = { left: NumCast(doc.x) - pwid / 10, right: NumCast(doc.x) + doc[WidthSym]() + pwid / 10, top: NumCast(doc.y) - phgt / 10, bot: NumCast(doc.y) + doc[HeightSym]() + phgt / 10 };
+                newPanX = cx + Math.min(0, bounds.left - screen.left) + Math.max(0, bounds.right - screen.right);
+                newPanY = cy + Math.min(0, bounds.top - screen.top) + Math.max(0, bounds.bot - screen.bot);
                 newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
                 HistoryUtil.pushState(newState);
             }
@@ -949,12 +931,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
             Doc.BrushDoc(this.props.Document);
 
-            const newDidFocus = didFocus || (newPanX !== savedState.px || newPanY !== savedState.py);
-
             const newAfterFocus = (didFocus: boolean) => {
                 afterFocus && setTimeout(() => {
                     // @ts-ignore
-                    if (afterFocus?.(!dontCenter && (didFocus || (newPanX !== savedState.px || newPanY !== savedState.py)))) {
+                    if (afterFocus?.(didFocus || (newPanX !== savedState.px || newPanY !== savedState.py))) {
                         this.Document._panX = savedState.px;
                         this.Document._panY = savedState.py;
                         this.Document[this.scaleFieldKey] = savedState.s;
@@ -964,7 +944,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 }, newPanX !== savedState.px || newPanY !== savedState.py ? 500 : 0);
                 return false;
             };
-            this.props.focus(this.props.Document, undefined, undefined, newAfterFocus, undefined, newDidFocus);
+            this.props.focus(this.props.Document, undefined, undefined, newAfterFocus);
             !doc.hidden && Doc.linkFollowHighlight(doc);
         }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 47668d0f3..a5698e138 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, HeightSym } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -44,11 +44,11 @@ import { LinkDocPreview } from "./LinkDocPreview";
 import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
 
 export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
-export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
+export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     getAnchor: () => Doc;
-    scrollFocus?: (doc: Doc, smooth: boolean) => void;
+    scrollFocus?: (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => void;
     back?: () => boolean;
     forward?: () => boolean;
     url?: () => string;
@@ -376,9 +376,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
-    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => {
-        this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
-        return this.props.focus(doc, willZoom, scale, afterFocus, dontCenter, focused);
+    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
+        if (this._componentView?.scrollFocus) {
+            return this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo, afterFocus); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+        }
+        return this.props.focus(doc, willZoom, scale, afterFocus);
     }
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
@@ -917,8 +919,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
     contentsActive = () => this.docView?.contentsActive();
-    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => {
-        return this.docView?.focus(doc, willZoom, scale, afterFocus, dontCenter, focused);
+    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, focused?: boolean) => {
+        return this.docView?.focus(doc, willZoom, scale, afterFocus, focused);
     }
     getBounds = () => {
         if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 496caedaa..4b926bb6f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -22,6 +22,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
+import { DocAfterFocusFunc } from './DocumentView';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -79,7 +80,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         }
     }
 
-    scrollFocus = (doc: Doc, smooth: boolean) => doc !== this.rootDoc && this._pdfViewer?.scrollFocus(doc, smooth);
+    scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => { this._pdfViewer?.scrollFocus(doc, smooth, afterFocus); }
     getAnchor = () => this.rootDoc;
     componentWillUnmount() { this._selectReactionDisposer?.(); }
     componentDidMount() {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index ee152ddb3..d3d58b2f8 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -29,6 +29,7 @@ import { Annotation } from "../pdf/Annotation";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./WebBox.scss";
 import React = require("react");
+import { DocFocusFunc, DocAfterFocusFunc, DocumentView } from "./DocumentView";
 const htmlToText = require("html-to-text");
 
 type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -110,22 +111,21 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     getAnchor = () => this.rootDoc;
-    scrollFocus = (doc: Doc, smooth: boolean) => {
+    scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
         if (doc !== this.rootDoc && this.webpage && this._outerRef.current) {
-            this._initialScroll !== undefined && (this._initialScroll = NumCast(doc.y));
-            this._ignoreScroll = true;
-            if (smooth) {
-                smoothScroll(500, this.webpage as any as HTMLElement, NumCast(doc.y));
-                smoothScroll(500, this._outerRef.current, NumCast(doc.y));
-            } else {
-                this.webpage.scrollTop = NumCast(doc.y);
-                this._outerRef.current.scrollTop = NumCast(doc.y);
+            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+            if (scrollTo !== undefined) {
+                this._initialScroll !== undefined && (this._initialScroll = scrollTo);
+                this._ignoreScroll = true;
+                this.goTo(scrollTo, smooth ? 500 : 0);
+                this.layoutDoc._scrollTop = scrollTo;
+                this._ignoreScroll = false;
+                return afterFocus?.(true);
             }
-            smooth && (this.layoutDoc._scrollTop = NumCast(doc.y));
-            this._ignoreScroll = false;
         } else {
             this._initialScroll = NumCast(doc.y);
         }
+        afterFocus?.(false);
     }
 
     async componentDidMount() {
@@ -164,29 +164,35 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             }
         }
 
-        var quickScroll: string | undefined = "";
+        var quickScroll = true;
         this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
             (scrollTop) => {
-                if (quickScroll !== undefined) {
+                if (quickScroll) {
                     this._initialScroll = scrollTop;
                 }
-                else if (!this._ignoreScroll && this._outerRef.current && this.webpage) {
+                else if (!this._ignoreScroll) {
                     const viewTrans = StrCast(this.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                     const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
-                    if (duration) {
-                        smoothScroll(duration, this.webpage as any as HTMLElement, scrollTop);
-                        smoothScroll(duration, this._outerRef.current, scrollTop);
-                    } else {
-                        this.webpage.scrollTop = scrollTop;
-                        this._outerRef.current.scrollTop = scrollTop;
-                    }
+                    this.goTo(scrollTop, duration);
                 }
             },
             { fireImmediately: true }
         );
-        quickScroll = undefined;
+        quickScroll = false;
+    }
+
+    goTo = (scrollTop: number, duration: number) => {
+        if (this._outerRef.current && this.webpage) {
+            if (duration) {
+                smoothScroll(duration, this.webpage as any as HTMLElement, scrollTop);
+                smoothScroll(duration, this._outerRef.current, scrollTop);
+            } else {
+                this.webpage.scrollTop = scrollTop;
+                this._outerRef.current.scrollTop = scrollTop;
+            }
+        }
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c15c30803..183719e31 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,6 +67,7 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { LightboxView } from '../../LightboxView';
+import { DocAfterFocusFunc } from '../DocumentView';
 const translateGoogleApi = require("translate-google-api");
 
 export interface FormattedTextBoxProps {
@@ -867,7 +868,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
     }
 
-    scrollFocus = (doc: Doc, smooth: boolean) => {
+    scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
         const anchorId = doc[Id];
         const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
             const nodes: Node[] = [];
@@ -905,8 +906,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
                 const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
                 addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" });
-                setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), 1500);
+                setTimeout(() => {
+                    clearStyleSheetRules(FormattedTextBox._highlightStyleSheet);
+                    afterFocus?.(true);
+                }, 1500);
             }
+        } else {
+            afterFocus?.(false);
         }
     }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0477192d5..dd9dfa733 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -28,6 +28,7 @@ import { AnchorMenu } from "./AnchorMenu";
 import "./PDFViewer.scss";
 const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
 import React = require("react");
+import { DocAfterFocusFunc } from "../nodes/DocumentView";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 const _global = (window /* browser */ || global /* node */) as any;
@@ -179,15 +180,17 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     // scrolls to focus on a nested annotation document.  if this is part a link preview then it will jump to the scroll location,
     // otherwise it will scroll smoothly.
-    scrollFocus = (doc: Doc, smooth: boolean) => {
+    scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
         const mainCont = this._mainCont.current;
-        if (mainCont) {
-            if (smooth) {
-                smoothScroll(500, mainCont, NumCast(doc.y));
-            } else {
-                mainCont.scrollTop = NumCast(doc.y);
+        if (doc !== this.rootDoc && mainCont) {
+            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+            if (scrollTo !== undefined) {
+                if (smooth) smoothScroll(500, mainCont, scrollTo);
+                else mainCont.scrollTop = scrollTo;
+                return afterFocus?.(true);
             }
         }
+        afterFocus?.(false);
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2

