From 46c3885a8c6fee0ed9ac384d0a60716550736970 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 18 Jan 2021 11:12:01 -0500
Subject: fixed marquee dragging in webBox to pick correlate with cursor again.

---
 src/client/views/nodes/WebBox.tsx | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7fb14b0cc..876243533 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -484,13 +484,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     @undoBatch
     @action
     makeAnnotationDocument = (color: string): Opt<Doc> => {
+        const scale = this.props.scaling?.() || 1;
         if (this._savedAnnotations.size() === 0) return undefined;
         const anno = this._savedAnnotations.values()[0][0];
         const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.Document, title: "Annotation on " + this.Document.title });
-        if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
-        if (anno.style.top) annoDoc.y = NumCast(this.layoutDoc._scrollTop) + parseInt(anno.style.top);
-        if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
-        if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
+        if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale;
+        if (anno.style.top) annoDoc.y = (NumCast(this.layoutDoc._scrollTop) + parseInt(anno.style.top)) / scale;
+        if (anno.style.height) annoDoc._height = parseInt(anno.style.height) / scale;
+        if (anno.style.width) annoDoc._width = parseInt(anno.style.width) / scale;
         anno.remove();
         this._savedAnnotations.clear();
         return annoDoc;
@@ -575,8 +576,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 const nwidth = Doc.NativeWidth(this.Document) || 1;
                 const boundingRect = this._mainCont.current.getBoundingClientRect();
                 const boundingHeight = nheight / nwidth * boundingRect.width;
-                this._startX = (e.clientX - boundingRect.left) / boundingRect.width * nwidth;
-                this._startY = (e.clientY - boundingRect.top) / boundingHeight * nheight;
+                this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width);
+                this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop;
                 this._marqueeHeight = this._marqueeWidth = 0;
                 this._marqueeing = true;
             }
@@ -591,11 +592,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         if (this._marqueeing && this._mainCont.current) {
             // transform positions and find the width and height to set the marquee to
             const boundingRect = this._mainCont.current.getBoundingClientRect();
-            const boundingHeight = (Doc.NativeHeight(this.Document) || 1) / (Doc.NativeWidth(this.Document) || 1) * boundingRect.width;
-            const curX = (e.clientX - boundingRect.left) / boundingRect.width * (Doc.NativeWidth(this.Document) || 1);
-            const curY = (e.clientY - boundingRect.top) / boundingHeight * (Doc.NativeHeight(this.Document) || 1);
-            this._marqueeWidth = curX - this._startX;
-            this._marqueeHeight = curY - this._startY;
+            this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width)) - this._startX;
+            this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height)) - this._startY + this._mainCont.current.scrollTop;
             this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
             this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
             this._marqueeWidth = Math.abs(this._marqueeWidth);
@@ -709,7 +707,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 {this.annotationLayer}
                 <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
             </div >
-
             {this.props.isSelected() ? this.editToggleBtn() : null}
         </div>);
     }
-- 
cgit v1.2.3-70-g09d2


From c94a8d93ea7373af54355b5c47bf14a021cf4563 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 18 Jan 2021 11:29:43 -0500
Subject: fixed creating right margins for WebBox's

---
 src/client/views/nodes/WebBox.tsx | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

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

diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 876243533..4c4f3f15a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -458,7 +458,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         return (<>
             <div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
                 style={{
-                    width: `${100 / scale}%`,
+                    width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / scale}%`,
                     height: `${100 / scale}%`,
                     transform: `scale(${scale})`
                 }}
@@ -572,18 +572,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             }
             else if (this._mainCont.current) {
                 // set marquee x and y positions to the spatially transformed position
-                const nheight = Doc.NativeHeight(this.Document) || 1;
-                const nwidth = Doc.NativeWidth(this.Document) || 1;
                 const boundingRect = this._mainCont.current.getBoundingClientRect();
-                const boundingHeight = nheight / nwidth * boundingRect.width;
                 this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width);
                 this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop;
                 this._marqueeHeight = this._marqueeWidth = 0;
                 this._marqueeing = true;
             }
-            document.removeEventListener("pointermove", this.onSelectMove);
             document.addEventListener("pointermove", this.onSelectMove);
-            document.removeEventListener("pointerup", this.onSelectEnd);
             document.addEventListener("pointerup", this.onSelectEnd);
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 90c8fe926efa245f256c7e0a496beaecf0572294 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 18 Jan 2021 16:48:57 -0500
Subject: factored out marque selection code for WebBox and PDFBox into
 MarqueeAnnotator.

---
 src/client/views/MarqueeAnnotator.scss |  12 ++
 src/client/views/MarqueeAnnotator.tsx  | 198 ++++++++++++++++++++++++
 src/client/views/nodes/WebBox.tsx      | 196 ++++--------------------
 src/client/views/pdf/PDFViewer.scss    |  16 +-
 src/client/views/pdf/PDFViewer.tsx     | 272 ++++-----------------------------
 5 files changed, 273 insertions(+), 421 deletions(-)
 create mode 100644 src/client/views/MarqueeAnnotator.scss
 create mode 100644 src/client/views/MarqueeAnnotator.tsx

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

diff --git a/src/client/views/MarqueeAnnotator.scss b/src/client/views/MarqueeAnnotator.scss
new file mode 100644
index 000000000..c90d48a65
--- /dev/null
+++ b/src/client/views/MarqueeAnnotator.scss
@@ -0,0 +1,12 @@
+
+.marqueeAnnotator-annotationBox {
+    position: absolute;
+    background-color: rgba(245, 230, 95, 0.616);
+}
+
+
+.marqueeAnnotator-dragBox {
+    position:absolute;
+    background-color: transparent;
+    opacity: 0.1;
+}
\ No newline at end of file
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
new file mode 100644
index 000000000..bcc03bea1
--- /dev/null
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -0,0 +1,198 @@
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Dictionary } from "typescript-collections";
+import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
+import { GetEffectiveAcl } from "../../fields/util";
+import { DocUtils, Docs } from "../documents/Documents";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { DragManager } from "../util/DragManager";
+import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
+import { PDFMenu } from "./pdf/PDFMenu";
+import "./MarqueeAnnotator.scss";
+import React = require("react");
+import { undoBatch } from "../util/UndoManager";
+import { NumCast } from "../../fields/Types";
+import { DocumentType } from "../documents/DocumentTypes";
+import { List } from "../../fields/List";
+const _global = (window /* browser */ || global /* node */) as any;
+
+export interface MarqueeAnnotatorProps {
+    rootDoc: Doc;
+    clientX: number;
+    clientY: number;
+    scaling?: () => number;
+    mainCont: HTMLDivElement;
+    savedAnnotations: Dictionary<number, HTMLDivElement[]>;
+    annotationLayer: HTMLDivElement;
+    addDocument: (doc: Doc) => boolean;
+    getPageFromScroll?: (top: number) => number;
+    finishMarquee: () => void;
+}
+@observer
+export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
+    private _startX: number = 0;
+    private _startY: number = 0;
+    @observable private _left: number = 0;
+    @observable private _top: number = 0;
+    @observable private _width: number = 0;
+    @observable private _height: number = 0;
+
+    @action componentDidMount() {
+        // set marquee x and y positions to the spatially transformed position
+        const boundingRect = this.props.mainCont.getBoundingClientRect();
+        this._startX = this._left = (this.props.clientX - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
+        this._startY = this._top = (this.props.clientY - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
+        this._height = this._width = 0;
+        document.addEventListener("pointermove", this.onSelectMove);
+        document.addEventListener("pointerup", this.onSelectEnd);
+    }
+    componentWillUnmount() {
+        document.removeEventListener("pointermove", this.onSelectMove);
+        document.removeEventListener("pointerup", this.onSelectEnd);
+    }
+
+    @undoBatch
+    @action
+    makeAnnotationDocument = (color: string): Opt<Doc> => {
+        if (this.props.savedAnnotations.size() === 0) return undefined;
+        if ((this.props.savedAnnotations.values()[0][0] as any).marqueeing) {
+            const scale = this.props.scaling?.() || 1;
+            const anno = this.props.savedAnnotations.values()[0][0];
+            const mainAnnoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title });
+            if (anno.style.left) mainAnnoDoc.x = parseInt(anno.style.left) / scale;
+            if (anno.style.top) mainAnnoDoc.y = (NumCast(this.props.rootDoc._scrollTop) + parseInt(anno.style.top)) / scale;
+            if (anno.style.height) mainAnnoDoc._height = parseInt(anno.style.height) / scale;
+            if (anno.style.width) mainAnnoDoc._width = parseInt(anno.style.width) / scale;
+            mainAnnoDoc.group = mainAnnoDoc;
+            anno.remove();
+            this.props.savedAnnotations.clear();
+            return mainAnnoDoc;
+        } else {
+            const mainAnnoDoc = Docs.Create.FreeformDocument([], { type: DocumentType.PDFANNO, annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 });
+            const mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
+
+            let maxX = -Number.MAX_VALUE;
+            let minY = Number.MAX_VALUE;
+            const annoDocs: Doc[] = [];
+            this.props.savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => {
+                const annoDoc = new Doc();
+                if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
+                if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
+                if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
+                if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
+                annoDoc.group = mainAnnoDoc;
+                annoDoc.backgroundColor = color;
+                annoDocs.push(annoDoc);
+                anno.remove();
+                (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY));
+                (annoDoc.x !== undefined) && (maxX = Math.max(NumCast(annoDoc.x) + NumCast(annoDoc._width), maxX));
+            }));
+
+            mainAnnoDocProto.y = Math.max(minY, 0);
+            mainAnnoDocProto.x = Math.max(maxX, 0);
+            // mainAnnoDocProto.text = this._selectionText;
+            mainAnnoDocProto.annotations = new List<Doc>(annoDocs);
+            this.props.savedAnnotations.clear();
+            return mainAnnoDoc;
+        }
+    }
+    @action
+    highlight = (color: string) => {
+        // creates annotation documents for current highlights
+        const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
+        const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color);
+        annotationDoc && this.props.addDocument(annotationDoc);
+        return annotationDoc as Doc ?? undefined;
+    }
+
+    public static previewNewAnnotation = action((savedAnnotations: Dictionary<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
+        if (div.style.top) {
+            div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString();
+        }
+        annotationLayer.append(div);
+        div.style.backgroundColor = "#ACCEF7";
+        div.style.opacity = "0.5";
+        const savedPage = savedAnnotations.getValue(page);
+        if (savedPage) {
+            savedPage.push(div);
+            savedAnnotations.setValue(page, savedPage);
+        }
+        else {
+            savedAnnotations.setValue(page, [div]);
+        }
+    });
+
+    @action
+    onSelectMove = (e: PointerEvent) => {
+        // transform positions and find the width and height to set the marquee to
+        const boundingRect = this.props.mainCont.getBoundingClientRect();
+        this._width = ((e.clientX - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width)) - this._startX;
+        this._height = ((e.clientY - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height)) - this._startY + this.props.mainCont.scrollTop;
+        this._left = Math.min(this._startX, this._startX + this._width);
+        this._top = Math.min(this._startY, this._startY + this._height);
+        this._width = Math.abs(this._width);
+        this._height = Math.abs(this._height);
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+    onSelectEnd = (e: PointerEvent) => {
+        if (!e.ctrlKey) {
+            PDFMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height };
+        }
+
+        const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox");
+        if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though).
+            const copy = document.createElement("div");
+            ["left", "top", "width", "height", "border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]);
+            copy.className = "marqueeAnnotator-annotationBox";
+            (copy as any).marqueeing = true;
+            MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
+        }
+        PDFMenu.Instance.Highlight = this.highlight;
+        /**
+         * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        PDFMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
+            e.preventDefault();
+            e.stopPropagation();
+            const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100);
+            FormattedTextBox.SelectOnLoad = targetDoc[Id];
+            const anchorHighlightDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
+            if (anchorHighlightDoc) {
+                DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.rootDoc, anchorHighlightDoc, targetDoc), e.pageX, e.pageY, {
+                    dragComplete: e => {
+                        if (!e.aborted && e.annoDragData && !e.linkDocument) {
+                            e.linkDocument = DocUtils.MakeLink({ doc: anchorHighlightDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+                            anchorHighlightDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing?
+                            anchorHighlightDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc;
+                        }
+                        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
+                    }
+                });
+            }
+        });
+
+        if (this._width > 10 || this._height > 10) {  // configure and show the annotation/link menu if a the drag region is big enough
+            PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+
+            if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
+                this.highlight("rgba(245, 230, 95, 0.75)");  // yellowish highlight color for highlighted text (should match PDFMenu's highlight color)
+            }
+        }
+        this.props.finishMarquee();
+    }
+
+    render() {
+        return <div className="marqueeAnnotator-dragBox"
+            style={{
+                left: `${this._left}px`, top: `${this._top}px`,
+                width: `${this._width}px`, height: `${this._height}px`,
+                border: `${this._width === 0 ? "" : "2px dashed black"}`,
+                opacity: 0.2
+            }}>
+        </div>;
+    }
+}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 4c4f3f15a..f9e71bc92 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,9 +1,10 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Dictionary } from "typescript-collections";
 import * as WebRequest from 'web-request';
-import { Doc, DocListCast, Opt, AclAddonly, AclEdit, AclAdmin, DataSym, HeightSym, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
 import { HtmlField } from "../../../fields/HtmlField";
@@ -12,9 +13,9 @@ import { List } from "../../../fields/List";
 import { listSpec, makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { WebField } from "../../../fields/URLField";
-import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
-import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue, OmitKeys, smoothScroll } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { TraceMobx } from "../../../fields/util";
+import { addStyleSheet, clearStyleSheetRules, emptyFunction, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 import { undoBatch } from "../../util/UndoManager";
@@ -25,14 +26,10 @@ import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { Annotation } from "../pdf/Annotation";
 import { PDFMenu } from "../pdf/PDFMenu";
-import { PdfViewerMarquee } from "../pdf/PDFViewer";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./WebBox.scss";
-import "../pdf/PDFViewer.scss";
 import React = require("react");
-import { Tooltip } from '@material-ui/core';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { MarqueeAnnotator } from "../MarqueeAnnotator";
 const htmlToText = require("html-to-text");
 
 type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -40,17 +37,12 @@ const WebDocument = makeInterface(documentSchema);
 
 @observer
 export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
-
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     static _annotationStyle: any = addStyleSheet();
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    private _startX: number = 0;
-    private _startY: number = 0;
-    @observable private _marqueeX: number = 0;
-    @observable private _marqueeY: number = 0;
-    @observable private _marqueeWidth: number = 0;
-    @observable private _marqueeHeight: number = 0;
+    private _downX: number = 0;
+    private _downY: number = 0;
     @observable private _marqueeing: boolean = false;
     @observable private _url: string = "hello";
     @observable private _pressX: number = 0;
@@ -211,9 +203,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this._iframe?.removeEventListener('wheel', this.iframeWheel);
     }
 
-    onUrlDragover = (e: React.DragEvent) => {
-        e.preventDefault();
-    }
+    onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); }
 
     @undoBatch
     @action
@@ -256,6 +246,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     urlHash(s: string) {
         return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0);
     }
+
     @action
     submitURL = () => {
         if (!this._url.startsWith("http")) this._url = "http://" + this._url;
@@ -283,9 +274,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     onValueKeyDown = async (e: React.KeyboardEvent) => {
-        if (e.key === "Enter") {
-            this.submitURL();
-        }
+        if (e.key === "Enter") this.submitURL();
         e.stopPropagation();
     }
 
@@ -300,22 +289,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     _ignore = 0;
-    onPreWheel = (e: React.WheelEvent) => {
-        this._ignore = e.timeStamp;
-    }
-    onPrePointer = (e: React.PointerEvent) => {
-        this._ignore = e.timeStamp;
-    }
+    onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; }
+    onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; }
     onPostPointer = (e: React.PointerEvent) => {
-        if (this._ignore !== e.timeStamp) {
-            e.stopPropagation();
-        }
+        if (this._ignore !== e.timeStamp) e.stopPropagation();
     }
 
     onPostWheel = (e: React.WheelEvent) => {
-        if (this._ignore !== e.timeStamp) {
-            e.stopPropagation();
-        }
+        if (this._ignore !== e.timeStamp) e.stopPropagation();
     }
 
     onLongPressDown = (e: React.PointerEvent) => {
@@ -431,7 +412,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     @computed
     get urlContent() {
-
         const field = this.dataDoc[this.props.fieldKey];
         let view;
         if (field instanceof HtmlField) {
@@ -449,6 +429,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         }
         return view;
     }
+
     @computed
     get content() {
         const view = this.urlContent;
@@ -476,26 +457,9 @@ 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); }
 
-    @undoBatch
-    @action
-    makeAnnotationDocument = (color: string): Opt<Doc> => {
-        const scale = this.props.scaling?.() || 1;
-        if (this._savedAnnotations.size() === 0) return undefined;
-        const anno = this._savedAnnotations.values()[0][0];
-        const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.Document, title: "Annotation on " + this.Document.title });
-        if (anno.style.left) annoDoc.x = parseInt(anno.style.left) / scale;
-        if (anno.style.top) annoDoc.y = (NumCast(this.layoutDoc._scrollTop) + parseInt(anno.style.top)) / scale;
-        if (anno.style.height) annoDoc._height = parseInt(anno.style.height) / scale;
-        if (anno.style.width) annoDoc._width = parseInt(anno.style.width) / scale;
-        anno.remove();
-        this._savedAnnotations.clear();
-        return annoDoc;
-    }
     @computed get annotationLayer() {
         TraceMobx();
         return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
@@ -504,65 +468,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             }
         </div>;
     }
-    @action
-    createAnnotation = (div: HTMLDivElement, page: number) => {
-        if (this._annotationLayer.current) {
-            if (div.style.top) {
-                div.style.top = (parseInt(div.style.top)).toString();
-            }
-            this._annotationLayer.current.append(div);
-            div.style.backgroundColor = "#ACCEF7";
-            div.style.opacity = "0.5";
-            const savedPage = this._savedAnnotations.getValue(page);
-            if (savedPage) {
-                savedPage.push(div);
-                this._savedAnnotations.setValue(page, savedPage);
-            }
-            else {
-                this._savedAnnotations.setValue(page, [div]);
-            }
-        }
-    }
-
-    @action
-    highlight = (color: string) => {
-        // creates annotation documents for current highlights
-        const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
-        const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) ? this.makeAnnotationDocument(color.replace(/[0-9.]*\)/, "0.3)")) : undefined;
-        annotationDoc && this.addDocument?.(annotationDoc);
-        return annotationDoc ?? undefined;
-    }
-    /**
-     * This is temporary for creating annotations from highlights. It will
-     * start a drag event and create or put the necessary info into the drag event.
-     */
-    @action
-    startDrag = async (e: PointerEvent, ele: HTMLElement) => {
-        e.preventDefault();
-        e.stopPropagation();
 
-        const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.Document.title, 0, 0, 125, 125);
-        FormattedTextBox.SelectOnLoad = targetDoc[Id];
-        const annotationDoc = this.highlight("rgba(173, 216, 230, 0.35)"); // hyperlink color
-        if (annotationDoc) {
-            DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && !e.linkDocument) {
-                        e.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
-                        annotationDoc.isLinkButton = true;
-                        annotationDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.Document;
-                    }
-                }
-            });
-        }
-    }
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        this._marqueeing = false;
+        this._downX = e.clientX;
+        this._downY = e.clientY;
         if (!e.altKey && e.button === 0 && this.active(true)) {
             // clear out old marquees and initialize menu for new selection
-            PDFMenu.Instance.StartDrag = this.startDrag;
-            PDFMenu.Instance.Highlight = this.highlight;
             PDFMenu.Instance.Status = "pdf";
             PDFMenu.Instance.fadeOut(true);
             this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
@@ -570,79 +482,30 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             if ((e.target as any)?.parentElement.className === "textLayer") {
                 // start selecting text if mouse down on textLayer spans
             }
-            else if (this._mainCont.current) {
-                // set marquee x and y positions to the spatially transformed position
-                const boundingRect = this._mainCont.current.getBoundingClientRect();
-                this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width);
-                this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop;
-                this._marqueeHeight = this._marqueeWidth = 0;
-                this._marqueeing = true;
-            }
+            else this._marqueeing = true;
             document.addEventListener("pointermove", this.onSelectMove);
             document.addEventListener("pointerup", this.onSelectEnd);
         }
     }
+
     @action
     onSelectMove = (e: PointerEvent): void => {
-        if (this._marqueeing && this._mainCont.current) {
-            // transform positions and find the width and height to set the marquee to
-            const boundingRect = this._mainCont.current.getBoundingClientRect();
-            this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width)) - this._startX;
-            this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height)) - this._startY + this._mainCont.current.scrollTop;
-            this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
-            this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
-            this._marqueeWidth = Math.abs(this._marqueeWidth);
-            this._marqueeHeight = Math.abs(this._marqueeHeight);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-        else if (e.target && (e.target as any).parentElement === this._mainCont.current) {
-            e.stopPropagation();
-        }
+        if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation();
     }
 
+    @action
+    finishMarquee = () => { this._marqueeing = false; }
+
     @action
     onSelectEnd = (e: PointerEvent): void => {
         clearStyleSheetRules(WebBox._annotationStyle);
+        this.props.select(true);
         this._savedAnnotations.clear();
-        if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
-            const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox");
-            if (marquees?.length) { // copy the marquee and convert it to a permanent annotation.
-                const style = (marquees[0] as HTMLDivElement).style;
-                const copy = document.createElement("div");
-                copy.style.left = style.left;
-                copy.style.top = style.top;
-                copy.style.width = style.width;
-                copy.style.height = style.height;
-                copy.style.border = style.border;
-                copy.style.opacity = style.opacity;
-                (copy as any).marqueeing = true;
-                copy.className = "webBox-annotationBox";
-                this.createAnnotation(copy, 0);
-            }
 
-            if (!e.ctrlKey) {
-                PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight };
-            }
-            PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
-        }
-        //this._marqueeing = false;
-
-        if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
-            this.highlight("rgba(245, 230, 95, 0.616)");  // yellowish highlight color for highlighted text (should match PDFMenu's highlight color)
-        }
-        else {
-            PDFMenu.Instance.StartDrag = this.startDrag;
-            PDFMenu.Instance.Highlight = this.highlight;
-        }
         document.removeEventListener("pointermove", this.onSelectMove);
         document.removeEventListener("pointerup", this.onSelectEnd);
     }
-    marqueeWidth = () => this._marqueeWidth;
-    marqueeHeight = () => this._marqueeHeight;
-    marqueeX = () => this._marqueeX;
-    marqueeY = () => this._marqueeY;
-    marqueeing = () => this._marqueeing;
+
     scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
     render() {
         const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
@@ -655,9 +518,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 {this.content}
                 <div className={"webBox-outerContent"} ref={this._outerRef}
                     style={{
-                        width: `${100 / scale}%`,
-                        height: `${100 / scale}%`,
-                        transform: `scale(${scale})`,
+                        width: `${100 / scale}%`, height: `${100 / scale}%`, transform: `scale(${scale})`,
                         pointerEvents: this.layoutDoc.isAnnotating && !inactiveLayer ? "all" : "none"
                     }}
                     onWheel={e => {
@@ -700,7 +561,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                     </div>
                 </div>
                 {this.annotationLayer}
-                <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
+                {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
+                    <MarqueeAnnotator rootDoc={this.rootDoc} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} clientX={this._downX} clientY={this._downY} mainCont={this._mainCont.current} />}
             </div >
             {this.props.isSelected() ? this.editToggleBtn() : null}
         </div>);
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 0ecb4dba4..d17b2b5ef 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -34,6 +34,8 @@
         border: unset;
     }
     .pdfViewerDash-text-selected {
+        position: relative;
+        z-index: -1;
         .textLayer{
            pointer-events: all;
            user-select: text;
@@ -46,12 +48,6 @@
         }
     }
 
-    .pdfViewerDash-dragAnnotationBox {
-        position:absolute;
-        background-color: transparent;
-        opacity: 0.1;
-    }
-
     .pdfViewerDash-overlay, .pdfViewerDash-overlay-inking {
         transform-origin: left top;
         position: absolute;
@@ -83,11 +79,6 @@
         width: 100%;
         pointer-events: none;
         mix-blend-mode: multiply; // bcz: makes text fuzzy!
-
-        .pdfViewerDash-annotationBox {
-            position: absolute;
-            background-color: rgba(245, 230, 95, 0.616);
-        }
     }
     .pdfViewerDash-waiting {
         width: 70%;
@@ -102,5 +93,4 @@
 
 .pdfViewerDash-interactive {
     pointer-events: all;
-}
-   
\ No newline at end of file
+}   
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 8700a5739..03ffdb2db 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -3,31 +3,26 @@ import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import { Dictionary } from "typescript-collections";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
-import { List } from "../../../fields/List";
 import { createSchema, makeInterface } from "../../../fields/Schema";
 import { ScriptField } from "../../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { PdfField } from "../../../fields/URLField";
-import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
+import { TraceMobx } from "../../../fields/util";
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { DocUtils } from "../../documents/Documents";
 import { Networking } from "../../Network";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
 import { SelectionManager } from "../../util/SelectionManager";
 import { SharingManager } from "../../util/SharingManager";
 import { SnappingManager } from "../../util/SnappingManager";
-import { undoBatch } from "../../util/UndoManager";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
+import { MarqueeAnnotator } from "../MarqueeAnnotator";
 import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
 import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment";
 import { LinkDocPreview } from "../nodes/LinkDocPreview";
 import { Annotation } from "./Annotation";
@@ -73,10 +68,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
     @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
     @observable private Index: number = -1;
-    @observable private _marqueeX: number = 0;
-    @observable private _marqueeY: number = 0;
-    @observable private _marqueeWidth: number = 0;
-    @observable private _marqueeHeight: number = 0;
     @observable private _marqueeing: boolean = false;
     @observable private _showWaiting = true;
     @observable private _showCover = false;
@@ -92,8 +83,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _selectionText: string = "";
-    private _startX: number = 0;
-    private _startY: number = 0;
     private _downX: number = 0;
     private _downY: number = 0;
     private _coverPath: any;
@@ -196,13 +185,13 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     }
 
     copy = (e: ClipboardEvent) => {
-        if (this.props.active(true) && e.clipboardData) {
-            const annoDoc = this.makeAnnotationDocument("rgba(3,144,152,0.3)");  // copied text markup color (blueish)
-            if (annoDoc) {
-                e.clipboardData.setData("text/plain", this._selectionText);
-                e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]);
-                e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]);
-            }
+        if (this.active() && e.clipboardData) {
+            //const annoDoc = this.makeAnnotationDocument("rgba(3,144,152,0.3)");  // copied text markup color (blueish)
+            //if (annoDoc) {
+            e.clipboardData.setData("text/plain", this._selectionText);
+            // e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]);
+            // e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]);
+            //}
             e.preventDefault();
         }
     }
@@ -292,57 +281,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         this._pdfViewer.setDocument(this.props.pdf);
     }
 
-    @undoBatch
-    @action
-    makeAnnotationDocument = (color: string): Opt<Doc> => {
-        if (this._savedAnnotations.size() === 0) return undefined;
-        // let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {});
-        let mainAnnoDoc = Docs.Create.FreeformDocument([], { title: "anno", _width: 1, _height: 1 });
-        let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
-        const annoDocs: Doc[] = [];
-        let maxX = -Number.MAX_VALUE;
-        let minY = Number.MAX_VALUE;
-        if ((this._savedAnnotations.values()[0][0] as any).marqueeing) {
-            const anno = this._savedAnnotations.values()[0][0];
-            const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color.replace(/[0-9.]*\)/, ".3)"), title: "Annotation on " + this.Document.title });
-            if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
-            if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
-            if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
-            if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
-            annoDoc.group = mainAnnoDoc;
-            annoDocs.push(annoDoc);
-            anno.remove();
-            mainAnnoDoc = annoDoc;
-            mainAnnoDocProto.type = DocumentType.COL;
-            mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
-            mainAnnoDocProto.y = annoDoc.y;
-        } else {
-            this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => {
-                const annoDoc = new Doc();
-                if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
-                if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
-                if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
-                if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
-                annoDoc.group = mainAnnoDoc;
-                annoDoc.backgroundColor = color;
-                annoDocs.push(annoDoc);
-                anno.remove();
-                (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY));
-                (annoDoc.x !== undefined) && (maxX = Math.max(NumCast(annoDoc.x) + NumCast(annoDoc._width), maxX));
-            }));
-
-            mainAnnoDocProto.y = Math.max(minY, 0);
-            mainAnnoDocProto.x = Math.max(maxX, 0);
-            mainAnnoDocProto.type = DocumentType.PDFANNO;
-            mainAnnoDocProto.text = this._selectionText;
-            mainAnnoDocProto.annotations = new List<Doc>(annoDocs);
-        }
-        mainAnnoDocProto.title = "Annotation on " + this.Document.title;
-        mainAnnoDocProto.annotationOn = this.props.Document;
-        this._savedAnnotations.clear();
-        this.Index = -1;
-        return mainAnnoDoc;
-    }
+
     @action
     prevAnnotation = () => {
         this.Index = Math.max(this.Index - 1, 0);
@@ -400,25 +339,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         return index;
     }
 
-    @action
-    createAnnotation = (div: HTMLDivElement, page: number) => {
-        if (this._annotationLayer.current) {
-            if (div.style.top) {
-                div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString();
-            }
-            this._annotationLayer.current.append(div);
-            div.style.backgroundColor = "#ACCEF7";
-            div.style.opacity = "0.5";
-            const savedPage = this._savedAnnotations.getValue(page);
-            if (savedPage) {
-                savedPage.push(div);
-                this._savedAnnotations.setValue(page, savedPage);
-            }
-            else {
-                this._savedAnnotations.setValue(page, [div]);
-            }
-        }
-    }
 
     @action
     search = (searchString: string, fwd: boolean, clear: boolean = false) => {
@@ -448,7 +368,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             this._mainCont.current.addEventListener("pagesloaded", executeFind);
             this._mainCont.current.addEventListener("pagerendered", executeFind);
         }
-
     }
 
     @action
@@ -465,11 +384,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         if ((e.button !== 0 || e.altKey) && this.active(true)) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true);
         }
-        this._marqueeing = false;
         if (!e.altKey && e.button === 0 && this.active(true)) {
             // clear out old marquees and initialize menu for new selection
-            PDFMenu.Instance.StartDrag = this.startDrag;
-            PDFMenu.Instance.Highlight = this.highlight;
             PDFMenu.Instance.Status = "pdf";
             PDFMenu.Instance.fadeOut(true);
             this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
@@ -477,14 +393,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             if (e.target && (e.target as any).parentElement.className === "textLayer") {
                 // start selecting text if mouse down on textLayer spans
             }
-            else if (this._mainCont.current) {
-                // set marquee x and y positions to the spatially transformed position
-                const boundingRect = this._mainCont.current.getBoundingClientRect();
-                this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width);
-                this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop;
-                this._marqueeHeight = this._marqueeWidth = 0;
-                this._marqueeing = true;
-            }
+            else this._marqueeing = true;
             document.addEventListener("pointermove", this.onSelectMove);
             document.addEventListener("pointerup", this.onSelectEnd);
             document.addEventListener("pointerup", this.removeStyle, true);
@@ -497,21 +406,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     @action
     onSelectMove = (e: PointerEvent): void => {
-        if (this._marqueeing && this._mainCont.current) {
-            // transform positions and find the width and height to set the marquee to
-            const boundingRect = this._mainCont.current.getBoundingClientRect();
-            this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width)) - this._startX;
-            this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height)) - this._startY + this._mainCont.current.scrollTop;
-            this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
-            this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
-            this._marqueeWidth = Math.abs(this._marqueeWidth);
-            this._marqueeHeight = Math.abs(this._marqueeHeight);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-        else if (e.target && (e.target as any).parentElement === this._mainCont.current) {
-            e.stopPropagation();
-        }
+        if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation();
     }
 
     @action
@@ -522,17 +417,16 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
                 if (rect) {
-                    const scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
                     const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
                     if (rect.width !== this._mainCont.current.clientWidth) {
                         const annoBox = document.createElement("div");
-                        annoBox.className = "pdfViewerDash-annotationBox";
+                        annoBox.className = "marqueeAnnotator-annotationBox";
                         // transforms the positions from screen onto the pdf div
                         annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString();
                         annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString();
                         annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString();
                         annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString();
-                        this.createAnnotation(annoBox, this.getPageFromScroll(rect.top));
+                        this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
                     }
                 }
             }
@@ -547,117 +441,39 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         }
     }
 
+    @action
+    finishMarquee = () => { this._marqueeing = false; }
+
     @action
     onSelectEnd = (e: PointerEvent): void => {
         clearStyleSheetRules(PDFViewer._annotationStyle);
         this.props.select(false);
         this._savedAnnotations.clear();
-        if (this._marqueeing) {
-            if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
-                const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox");
-                if (marquees?.length) { // copy the marquee and convert it to a permanent annotation.
-                    const style = (marquees[0] as HTMLDivElement).style;
-                    const copy = document.createElement("div");
-                    copy.style.left = style.left;
-                    copy.style.top = style.top;
-                    copy.style.width = style.width;
-                    copy.style.height = style.height;
-                    copy.style.border = style.border;
-                    copy.style.opacity = style.opacity;
-                    (copy as any).marqueeing = true;
-                    copy.className = "pdfViewerDash-annotationBox";
-                    this.createAnnotation(copy, this.getPageFromScroll(this._marqueeY));
-                }
-
-                if (!e.ctrlKey) {
-                    PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight };
-                }
-                PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
-            }
-            this._marqueeing = false;
-        }
-        else {
-            const sel = window.getSelection();
-            if (sel?.type === "Range") {
-                const selRange = sel.getRangeAt(0);
-                this.createTextAnnotation(sel, selRange);
-                PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
-            }
-        }
-
-        if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
-            this.highlight("rgba(245, 230, 95, 0.75)");  // yellowish highlight color for highlighted text (should match PDFMenu's highlight color)
-        }
-        else {
-            PDFMenu.Instance.StartDrag = this.startDrag;
-            PDFMenu.Instance.Highlight = this.highlight;
-        }
         document.removeEventListener("pointermove", this.onSelectMove);
         document.removeEventListener("pointerup", this.onSelectEnd);
-    }
-
-    @action
-    highlight = (color: string) => {
-        // creates annotation documents for current highlights
-        const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
-        const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color);
-        annotationDoc && this.addDocument?.(annotationDoc);
-        return annotationDoc as Doc ?? undefined;
-    }
 
-    /**
-     * This is temporary for creating annotations from highlights. It will
-     * start a drag event and create or put the necessary info into the drag event.
-     */
-    @action
-    startDrag = async (e: PointerEvent, ele: HTMLElement) => {
-        e.preventDefault();
-        e.stopPropagation();
-
-        const clipDoc = Doc.MakeAlias(this.dataDoc);
-        clipDoc._fitWidth = true;
-        clipDoc._width = this.marqueeWidth();
-        clipDoc._height = this.marqueeHeight();
-        clipDoc._scrollTop = this.marqueeY();
-        const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.Document.title, 0, 0, 100, 100);
-        FormattedTextBox.SelectOnLoad = targetDoc[Id];
-        Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
-        clipDoc.rootDocument = targetDoc;
-        // DocUtils.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
-        // targetDoc.layoutKey = "layout";
-        // const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
-        // Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
-        // const snipLayout = Docs.Create.PdfDocument("http://www.msn.com", { title: "snippetView", isTemplateDoc: true, isTemplateForField: "snipped", _fitWidth: true, _width: this.marqueeWidth(), _height: this.marqueeHeight(), _scrollTop: this.marqueeY() });
-        // Doc.GetProto(snipLayout).layout = PDFBox.LayoutString("snipped");
-        const annotationDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
-        if (annotationDoc) {
-            DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && !e.linkDocument) {
-                        e.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
-                    }
-                    annotationDoc.isLinkButton = true; // prevents link button fro showing up --- maybe not a good thing?
-                    annotationDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.Document;
-                    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 above
-                }
-            });
+        const sel = window.getSelection();
+        if (sel?.type === "Range") {
+            const selRange = sel.getRangeAt(0);
+            this.createTextAnnotation(sel, selRange);
+            PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
         }
     }
 
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.layoutDoc._scrollTop || 0) : this.props.ScreenToLocalTransform();
     }
+
     onClick = (e: React.MouseEvent) => {
-        this._setPreviewCursor &&
-            e.button === 0 &&
+        if (this._setPreviewCursor && e.button === 0 &&
             Math.abs(e.clientX - this._downX) < 3 &&
-            Math.abs(e.clientY - this._downY) < 3 &&
+            Math.abs(e.clientY - this._downY) < 3) {
             this._setPreviewCursor(e.clientX, e.clientY, false);
+        }
     }
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
 
-
     getCoverImage = () => {
         if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) {
             setTimeout((() => {
@@ -745,11 +561,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             {this._showWaiting ? <img className="pdfViewerDash-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
         </>;
     }
-    marqueeWidth = () => this._marqueeWidth;
-    marqueeHeight = () => this._marqueeHeight;
-    marqueeX = () => this._marqueeX;
-    marqueeY = () => this._marqueeY;
-    marqueeing = () => this._marqueeing;
     contentZoom = () => this._zoomed;
     render() {
         TraceMobx();
@@ -766,29 +577,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             {this.overlayLayer}
             {this.overlayInfo}
             {this.standinViews}
-            <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
+            {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
+                <MarqueeAnnotator rootDoc={this.rootDoc} addDocument={this.addDocument} finishMarquee={this.finishMarquee} getPageFromScroll={this.getPageFromScroll} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} clientX={this._downX} clientY={this._downY} mainCont={this._mainCont.current} />}
         </div >;
     }
-}
-
-export interface PdfViewerMarqueeProps {
-    isMarqueeing: () => boolean;
-    width: () => number;
-    height: () => number;
-    x: () => number;
-    y: () => number;
-}
-
-@observer
-export class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
-    render() {
-        return !this.props.isMarqueeing() ? (null) : <div className="pdfViewerDash-dragAnnotationBox"
-            style={{
-                left: `${this.props.x()}px`, top: `${this.props.y()}px`,
-                width: `${this.props.width()}px`, height: `${this.props.height()}px`,
-                border: `${this.props.width() === 0 ? "" : "2px dashed black"}`,
-                opacity: 0.2
-            }}>
-        </div>;
-    }
-}
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 22a2462a6854f31f6f546d56258aec2042073d4b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 19 Jan 2021 11:31:02 -0500
Subject: more cleanup of marquee annotator.   added marquee annotator to
 images

---
 src/client/views/MarqueeAnnotator.tsx              | 40 ++++++++----
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 +
 src/client/views/nodes/ImageBox.scss               | 10 +++
 src/client/views/nodes/ImageBox.tsx                | 43 +++++++++++--
 src/client/views/nodes/WebBox.tsx                  | 72 ++++++---------------
 src/client/views/pdf/PDFViewer.tsx                 | 74 +++++++++++-----------
 6 files changed, 131 insertions(+), 109 deletions(-)

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

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index bcc03bea1..fe9c954ca 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from "mobx";
+import { action, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Dictionary } from "typescript-collections";
 import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../fields/Doc";
@@ -19,8 +19,7 @@ const _global = (window /* browser */ || global /* node */) as any;
 
 export interface MarqueeAnnotatorProps {
     rootDoc: Doc;
-    clientX: number;
-    clientY: number;
+    down: number[];
     scaling?: () => number;
     mainCont: HTMLDivElement;
     savedAnnotations: Dictionary<number, HTMLDivElement[]>;
@@ -38,11 +37,22 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     @observable private _width: number = 0;
     @observable private _height: number = 0;
 
+    constructor(props: any) {
+        super(props);
+        runInAction(() => {
+            PDFMenu.Instance.Status = "pdf";
+            PDFMenu.Instance.fadeOut(true);
+            // clear out old marquees and initialize menu for new selection
+            this.props.savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+            this.props.savedAnnotations.clear();
+        });
+    }
+
     @action componentDidMount() {
         // set marquee x and y positions to the spatially transformed position
         const boundingRect = this.props.mainCont.getBoundingClientRect();
-        this._startX = this._left = (this.props.clientX - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
-        this._startY = this._top = (this.props.clientY - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
+        this._startX = this._left = (this.props.down[0] - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
+        this._startY = this._top = (this.props.down[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
         this._height = this._width = 0;
         document.addEventListener("pointermove", this.onSelectMove);
         document.addEventListener("pointerup", this.onSelectEnd);
@@ -134,7 +144,6 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         this._width = Math.abs(this._width);
         this._height = Math.abs(this._height);
         e.stopPropagation();
-        e.preventDefault();
     }
 
     onSelectEnd = (e: PointerEvent) => {
@@ -142,14 +151,6 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             PDFMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height };
         }
 
-        const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox");
-        if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though).
-            const copy = document.createElement("div");
-            ["left", "top", "width", "height", "border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]);
-            copy.className = "marqueeAnnotator-annotationBox";
-            (copy as any).marqueeing = true;
-            MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
-        }
         PDFMenu.Instance.Highlight = this.highlight;
         /**
          * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
@@ -176,11 +177,22 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         });
 
         if (this._width > 10 || this._height > 10) {  // configure and show the annotation/link menu if a the drag region is big enough
+            const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox");
+            if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though).
+                const copy = document.createElement("div");
+                ["left", "top", "width", "height", "border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]);
+                copy.className = "marqueeAnnotator-annotationBox";
+                (copy as any).marqueeing = true;
+                MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
+            }
+
             PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
 
             if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
                 this.highlight("rgba(245, 230, 95, 0.75)");  // yellowish highlight color for highlighted text (should match PDFMenu's highlight color)
             }
+        } else {
+            runInAction(() => this._width = this._height = 0);
         }
         this.props.finishMarquee();
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d20d1abfc..258e839d0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -217,6 +217,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 // bcz: do we need this?   it kills the context menu on the main collection if !altKey
                 // e.stopPropagation();
             }
+            else PreviewCursor.Visible = false;
         }
     }
 
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index c1b95b308..41055e2db 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -5,6 +5,16 @@
     position: relative;
     transform-origin: top left;
 
+
+    .imageBox-annotationLayer {
+        position: absolute;
+        transform-origin: left top;
+        top: 0;
+        width: 100%;
+        pointer-events: none;
+        mix-blend-mode: multiply; // bcz: makes text fuzzy!
+    }
+
     .imageBox-fader {
         pointer-events: inherit;
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b0e7f4ce5..e202749aa 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -27,6 +27,10 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
 import { StyleProp } from '../StyleProvider';
+import { PDFMenu } from '../pdf/PDFMenu';
+import { Dictionary } from 'typescript-collections';
+import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { Annotation } from '../pdf/Annotation';
 const path = require('path');
 const { Howl } = require('howler');
 
@@ -63,7 +67,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _dropDisposer?: DragManager.DragDropDisposer;
-    private _pathDisposer?: IReactionDisposer;
+    private _disposers: { [name: string]: IReactionDisposer } = {};
     @observable private _audioState = 0;
     @observable static _showControls: boolean;
     @observable uploadIcon = uploadIcons.idle;
@@ -74,7 +78,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     }
 
     componentDidMount() {
-        this._pathDisposer = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
+        this._disposers.selection = reaction(() => this.props.isSelected(),
+            selected => {
+                if (!selected) {
+                    this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+                    this._savedAnnotations.clear();
+                    PDFMenu.Instance.fadeOut(true);
+                }
+            },
+            { fireImmediately: true });
+        this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
             action(({ nativeSize, width }) => {
                 if (!this.layoutDoc._height) {
                     this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth;
@@ -84,7 +97,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     }
 
     componentWillUnmount() {
-        this._pathDisposer?.();
+        Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
     @undoBatch
@@ -356,7 +369,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
             transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
         }
 
-        return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget}>
+        return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
             <div className="imageBox-fader" >
                 <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
                     src={srcpath}
@@ -402,11 +415,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.ycenter);
     contentFunc = () => [this.content];
 
+    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+    private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+    @observable _marqueeing: number[] | undefined;
+    @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+    @computed get annotationLayer() {
+        return <div className="imageBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer} />;
+    }
+    @action
+    marqueeDown = (e: React.PointerEvent) => {
+        if (!e.altKey && e.button === 0 && this.active(true)) this._marqueeing = [e.clientX, e.clientY];
+    }
+    @action
+    finishMarquee = () => {
+        this._marqueeing = undefined;
+        this.props.select(true);
+    }
+
     render() {
         TraceMobx();
         const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
         const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / (this.props.scaling?.() || 1)}px` : borderRad;
-        return (<div className={`imageBox`} onContextMenu={this.specificContextMenu}
+        return (<div className={`imageBox`} onContextMenu={this.specificContextMenu} ref={this._mainCont}
             style={{
                 width: this.props.PanelWidth() ? undefined : `100%`,
                 height: this.props.PanelWidth() ? undefined : `100%`,
@@ -437,6 +467,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
                 whenActiveChanged={this.whenActiveChanged}>
                 {this.contentFunc}
             </CollectionFreeFormView>
+            {this.annotationLayer}
+            {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
+                <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
         </div >);
     }
 }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f9e71bc92..ca3d4448d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -14,7 +14,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { WebField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, clearStyleSheetRules, emptyFunction, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils";
+import { emptyFunction, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -38,26 +38,21 @@ const WebDocument = makeInterface(documentSchema);
 @observer
 export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
-    static _annotationStyle: any = addStyleSheet();
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    private _downX: number = 0;
-    private _downY: number = 0;
-    @observable private _marqueeing: boolean = false;
+    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
+    private _disposers: { [name: string]: IReactionDisposer } = {};
+    private _longPressSecondsHack?: NodeJS.Timeout;
+    private _outerRef = React.createRef<HTMLDivElement>();
+    private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
+    private _iframeDragRef = React.createRef<HTMLDivElement>();
+    @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[]>();
-    private _selectionReactionDisposer?: IReactionDisposer;
-    private _scrollReactionDisposer?: IReactionDisposer;
-    private _scrollTopReactionDisposer?: IReactionDisposer;
-    private _moveReactionDisposer?: IReactionDisposer;
-    private _longPressSecondsHack?: NodeJS.Timeout;
-    private _outerRef = React.createRef<HTMLDivElement>();
-    private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
-    private _iframeDragRef = React.createRef<HTMLDivElement>();
-    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
+
     get scrollHeight() { return this.webpage?.scrollHeight || 1000; }
     get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
     set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
@@ -91,8 +86,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 this.webpage.scrollLeft = NumCast(this.layoutDoc._scrollLeft);
             }
         }
-        this._scrollReactionDisposer?.();
-        this._scrollReactionDisposer = reaction(() => ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }),
+        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/);
@@ -110,7 +105,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             },
             { fireImmediately: true }
         );
-        this._scrollTopReactionDisposer = reaction(() => this.layoutDoc._scrollTop,
+        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;
@@ -158,14 +153,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
         runInAction(() => this._url = urlField?.url.toString() || "");
 
-        this._moveReactionDisposer = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
+        this._disposers.scrollMove = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
             () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop));
 
-        this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
+        this._disposers.selection = reaction(() => this.props.isSelected(),
             selected => {
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
-                    this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
+                    this._savedAnnotations.clear();
                     PDFMenu.Instance.fadeOut(true);
                 }
             },
@@ -194,10 +189,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     componentWillUnmount() {
-        this._moveReactionDisposer?.();
-        this._selectionReactionDisposer?.();
-        this._scrollTopReactionDisposer?.();
-        this._scrollReactionDisposer?.();
+        Object.values(this._disposers).forEach(disposer => disposer?.());
         document.removeEventListener("pointerup", this.onLongPressUp);
         document.removeEventListener("pointermove", this.onLongPressMove);
         this._iframe?.removeEventListener('wheel', this.iframeWheel);
@@ -471,39 +463,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
         if (!e.altKey && e.button === 0 && this.active(true)) {
-            // clear out old marquees and initialize menu for new selection
-            PDFMenu.Instance.Status = "pdf";
-            PDFMenu.Instance.fadeOut(true);
-            this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
-            this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
-            if ((e.target as any)?.parentElement.className === "textLayer") {
-                // start selecting text if mouse down on textLayer spans
-            }
-            else this._marqueeing = true;
-            document.addEventListener("pointermove", this.onSelectMove);
-            document.addEventListener("pointerup", this.onSelectEnd);
+            this._marqueeing = [e.clientX, e.clientY];
         }
     }
 
     @action
-    onSelectMove = (e: PointerEvent): void => {
-        if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation();
-    }
-
-    @action
-    finishMarquee = () => { this._marqueeing = false; }
-
-    @action
-    onSelectEnd = (e: PointerEvent): void => {
-        clearStyleSheetRules(WebBox._annotationStyle);
+    finishMarquee = () => {
+        this._marqueeing = undefined;
         this.props.select(true);
-        this._savedAnnotations.clear();
-
-        document.removeEventListener("pointermove", this.onSelectMove);
-        document.removeEventListener("pointerup", this.onSelectEnd);
     }
 
     scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
@@ -562,7 +530,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 </div>
                 {this.annotationLayer}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
-                    <MarqueeAnnotator rootDoc={this.rootDoc} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} clientX={this._downX} clientY={this._downY} mainCont={this._mainCont.current} />}
+                    <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
             </div >
             {this.props.isSelected() ? this.editToggleBtn() : null}
         </div>);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 03ffdb2db..26f936c66 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -68,7 +68,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
     @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
     @observable private Index: number = -1;
-    @observable private _marqueeing: boolean = false;
+    @observable private _marqueeing: number[] | undefined;
     @observable private _showWaiting = true;
     @observable private _showCover = false;
     @observable private _zoomed = 1;
@@ -379,29 +379,30 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         // if alt+left click, drag and annotate
         this._downX = e.clientX;
         this._downY = e.clientY;
-        (e.target as any).tagName === "SPAN" && (this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" }));
         if ((this.Document._viewScale || 1) !== 1) return;
         if ((e.button !== 0 || e.altKey) && this.active(true)) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true);
         }
         if (!e.altKey && e.button === 0 && this.active(true)) {
-            // clear out old marquees and initialize menu for new selection
-            PDFMenu.Instance.Status = "pdf";
-            PDFMenu.Instance.fadeOut(true);
-            this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
-            this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
-            if (e.target && (e.target as any).parentElement.className === "textLayer") {
-                // start selecting text if mouse down on textLayer spans
+            if (e.target && (e.target as any).parentElement.className !== "textLayer") {
+                this._marqueeing = [e.clientX, e.clientY];  // if texLayer is hit, then we select text instead of using a marquee
+            } else {
+                // clear out old marquees and initialize menu for new selection
+                PDFMenu.Instance.Status = "pdf";
+                PDFMenu.Instance.fadeOut(true);
+                this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+                this._savedAnnotations.clear();
+                this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
+                document.addEventListener("pointermove", this.onSelectMove);
+                document.addEventListener("pointerup", this.onSelectEnd);
             }
-            else this._marqueeing = true;
-            document.addEventListener("pointermove", this.onSelectMove);
-            document.addEventListener("pointerup", this.onSelectEnd);
-            document.addEventListener("pointerup", this.removeStyle, true);
         }
     }
-    removeStyle = () => {
-        clearStyleSheetRules(PDFViewer._annotationStyle);
-        document.removeEventListener("pointerup", this.removeStyle);
+
+    @action
+    finishMarquee = () => {
+        this._marqueeing = undefined;
+        this.props.select(false);
     }
 
     @action
@@ -409,6 +410,21 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation();
     }
 
+    @action
+    onSelectEnd = (e: PointerEvent): void => {
+        clearStyleSheetRules(PDFViewer._annotationStyle);
+        this.props.select(false);
+        document.removeEventListener("pointermove", this.onSelectMove);
+        document.removeEventListener("pointerup", this.onSelectEnd);
+
+        const sel = window.getSelection();
+        if (sel?.type === "Range") {
+            const selRange = sel.getRangeAt(0);
+            this.createTextAnnotation(sel, selRange);
+            PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+        }
+    }
+
     @action
     createTextAnnotation = (sel: Selection, selRange: Range) => {
         if (this._mainCont.current) {
@@ -441,35 +457,17 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         }
     }
 
-    @action
-    finishMarquee = () => { this._marqueeing = false; }
-
-    @action
-    onSelectEnd = (e: PointerEvent): void => {
-        clearStyleSheetRules(PDFViewer._annotationStyle);
-        this.props.select(false);
-        this._savedAnnotations.clear();
-        document.removeEventListener("pointermove", this.onSelectMove);
-        document.removeEventListener("pointerup", this.onSelectEnd);
-
-        const sel = window.getSelection();
-        if (sel?.type === "Range") {
-            const selRange = sel.getRangeAt(0);
-            this.createTextAnnotation(sel, selRange);
-            PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
-        }
-    }
-
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.layoutDoc._scrollTop || 0) : this.props.ScreenToLocalTransform();
     }
 
     onClick = (e: React.MouseEvent) => {
         if (this._setPreviewCursor && e.button === 0 &&
-            Math.abs(e.clientX - this._downX) < 3 &&
-            Math.abs(e.clientY - this._downY) < 3) {
+            Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+            Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             this._setPreviewCursor(e.clientX, e.clientY, false);
         }
+        e.stopPropagation();
     }
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
@@ -578,7 +576,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             {this.overlayInfo}
             {this.standinViews}
             {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
-                <MarqueeAnnotator rootDoc={this.rootDoc} addDocument={this.addDocument} finishMarquee={this.finishMarquee} getPageFromScroll={this.getPageFromScroll} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} clientX={this._downX} clientY={this._downY} mainCont={this._mainCont.current} />}
+                <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} addDocument={this.addDocument} finishMarquee={this.finishMarquee} getPageFromScroll={this.getPageFromScroll} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
         </div >;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 68785a97178d229935c0429791081d7c09312dc3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 20 Jan 2021 18:35:58 -0500
Subject: moved PDFMenu to AnchorMenu.  added AnchorMenu to formattedTextBox. 
 Fixed following links from text box huyperlinks when there are multiple
 different huypertext links on the document.

---
 src/client/util/DragManager.ts                     |  24 ++--
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/MarqueeAnnotator.tsx              |  53 +++----
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   9 +-
 src/client/views/nodes/DocumentView.tsx            |  14 +-
 src/client/views/nodes/ImageBox.tsx                |   4 +-
 src/client/views/nodes/VideoBox.tsx                |   4 +-
 src/client/views/nodes/WebBox.tsx                  |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  40 ++++++
 .../formattedText/FormattedTextBoxComment.tsx      |   2 +-
 src/client/views/pdf/AnchorMenu.scss               |  25 ++++
 src/client/views/pdf/AnchorMenu.tsx                | 157 +++++++++++++++++++++
 src/client/views/pdf/Annotation.tsx                |  20 +--
 src/client/views/pdf/PDFMenu.scss                  |  25 ----
 src/client/views/pdf/PDFMenu.tsx                   | 157 ---------------------
 src/client/views/pdf/PDFViewer.tsx                 |  18 +--
 18 files changed, 311 insertions(+), 257 deletions(-)
 create mode 100644 src/client/views/pdf/AnchorMenu.scss
 create mode 100644 src/client/views/pdf/AnchorMenu.tsx
 delete mode 100644 src/client/views/pdf/PDFMenu.scss
 delete mode 100644 src/client/views/pdf/PDFMenu.tsx

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 52ccfda74..7b4d43793 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -105,14 +105,14 @@ export namespace DragManager {
         constructor(aborted: boolean, dragData: { [id: string]: any }) {
             this.aborted = aborted;
             this.docDragData = dragData instanceof DocumentDragData ? dragData : undefined;
-            this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData : undefined;
+            this.annoDragData = dragData instanceof AnchorAnnoDragData ? dragData : undefined;
             this.linkDragData = dragData instanceof LinkDragData ? dragData : undefined;
             this.columnDragData = dragData instanceof ColumnDragData ? dragData : undefined;
         }
         linkDocument?: Doc;
         aborted: boolean;
         docDragData?: DocumentDragData;
-        annoDragData?: PdfAnnoDragData;
+        annoDragData?: AnchorAnnoDragData;
         linkDragData?: LinkDragData;
         columnDragData?: ColumnDragData;
     }
@@ -152,19 +152,21 @@ export namespace DragManager {
         }
         colKey: SchemaHeaderField;
     }
-    // used by PDFs to conditionally (if the drop completes) create a text annotation when dragging from the PDF toolbar when a text region has been selected.
+    // used by PDFs,Text,Image,Video,Web to conditionally (if the drop completes) create a text annotation when dragging the annotate button from the AnchorMenu when a text/region selection has been made.
     // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag
-    export class PdfAnnoDragData {
-        constructor(dragDoc: Doc, annotationDoc: Doc, dropDoc: Doc) {
+    export class AnchorAnnoDragData {
+        constructor(dragDoc: Doc, annotationDocCreator: () => Doc, dropDocCreator: () => Doc) {
             this.dragDocument = dragDoc;
-            this.dropDocument = dropDoc;
-            this.annotationDocument = annotationDoc;
+            this.dropDocCreator = dropDocCreator;
+            this.annotationDocCreator = annotationDocCreator;
             this.offset = [0, 0];
         }
         targetContext: Doc | undefined;
         dragDocument: Doc;
-        annotationDocument: Doc;
-        dropDocument: Doc;
+        annotationDocCreator: () => Doc;
+        dropDocCreator: () => Doc;
+        dropDocument?: Doc;
+        annotationDocument?: Doc;
         offset: number[];
         dropAction: dropActionType;
         userDropAction: dropActionType;
@@ -250,7 +252,7 @@ export namespace DragManager {
     }
 
     // drag&drop the pdf annotation anchor which will create a text note  on drop via a dropCompleted() DragOption 
-    export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) {
+    export function StartAnchorAnnoDrag(eles: HTMLElement[], dragData: AnchorAnnoDragData, downX: number, downY: number, options?: DragOptions) {
         StartDrag(eles, dragData, downX, downY, options);
     }
 
@@ -353,7 +355,7 @@ export namespace DragManager {
         const xs: number[] = [];
         const ys: number[] = [];
 
-        docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
+        docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
         const elesCont = {
             left: Number.MAX_SAFE_INTEGER,
             top: Number.MAX_SAFE_INTEGER,
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 8a07c5321..1c5277de0 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -22,7 +22,7 @@ import { DocumentDecorations } from "./DocumentDecorations";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { MainView } from "./MainView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
-import { PDFMenu } from "./pdf/PDFMenu";
+import { AnchorMenu } from "./pdf/AnchorMenu";
 import { SnappingManager } from "../util/SnappingManager";
 import { SearchBox } from "./search/SearchBox";
 import { random } from "lodash";
@@ -261,7 +261,7 @@ export class KeyManager {
                 }
                 break;
             case "c":
-                if (!PDFMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
+                if (!AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
                     const bds = DocumentDecorations.Instance.Bounds;
                     const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
                     const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":");
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c1fafe3e6..bd5db62ef 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -55,7 +55,7 @@ import { RadialMenu } from './nodes/RadialMenu';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { WebBox } from './nodes/WebBox';
 import { OverlayView } from './OverlayView';
-import { PDFMenu } from './pdf/PDFMenu';
+import { AnchorMenu } from './pdf/AnchorMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { SearchBox } from './search/SearchBox';
@@ -615,7 +615,7 @@ export class MainView extends React.Component {
             <TaskCompletionBox />
             <ContextMenu />
             <RadialMenu />
-            <PDFMenu />
+            <AnchorMenu />
             <MarqueeOptionsMenu />
             <OverlayView />
             <TimelineMenu />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 21048ebe2..0ab2d1ecf 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -8,7 +8,7 @@ import { DocUtils, Docs } from "../documents/Documents";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { DragManager } from "../util/DragManager";
 import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
-import { PDFMenu } from "./pdf/PDFMenu";
+import { AnchorMenu } from "./pdf/AnchorMenu";
 import "./MarqueeAnnotator.scss";
 import React = require("react");
 import { undoBatch } from "../util/UndoManager";
@@ -40,8 +40,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     constructor(props: any) {
         super(props);
         runInAction(() => {
-            PDFMenu.Instance.Status = "marquee";
-            PDFMenu.Instance.fadeOut(true);
+            AnchorMenu.Instance.Status = "marquee";
+            AnchorMenu.Instance.fadeOut(true);
             // clear out old marquees and initialize menu for new selection
             this.props.savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
             this.props.savedAnnotations.clear();
@@ -148,32 +148,37 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
 
     onSelectEnd = (e: PointerEvent) => {
         if (!e.ctrlKey) {
-            PDFMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height };
+            AnchorMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height };
         }
 
-        PDFMenu.Instance.Highlight = this.highlight;
+        AnchorMenu.Instance.Highlight = this.highlight;
         /**
-         * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
+         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.  
          * It also initiates a Drag/Drop interaction to place the text annotation.
          */
-        PDFMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
+        AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
             e.preventDefault();
             e.stopPropagation();
-            const targetDoc = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100);
-            FormattedTextBox.SelectOnLoad = targetDoc[Id];
-            const anchorHighlightDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
-            if (anchorHighlightDoc) {
-                DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.rootDoc, anchorHighlightDoc, targetDoc), e.pageX, e.pageY, {
-                    dragComplete: e => {
-                        if (!e.aborted && e.annoDragData && !e.linkDocument) {
-                            e.linkDocument = DocUtils.MakeLink({ doc: anchorHighlightDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
-                            anchorHighlightDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing?
-                            anchorHighlightDoc.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc;
-                        }
-                        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
-                    }
-                });
+            const targetCreator = () => {
+                const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100);
+                FormattedTextBox.SelectOnLoad = target[Id];
+                return target;
+            }
+            const anchorCreator = () => {
+                const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color
+                annoDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing?
+                this.props.addDocument(annoDoc);
+                return annoDoc;
             }
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, anchorCreator, targetCreator), e.pageX, e.pageY, {
+                dragComplete: e => {
+                    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 }, "Annotation");
+                        e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc;
+                    }
+                    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
+                }
+            });
         });
 
         if (this._width > 10 || this._height > 10) {  // configure and show the annotation/link menu if a the drag region is big enough
@@ -186,10 +191,10 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                 MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
             }
 
-            PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
 
-            if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
-                this.highlight("rgba(245, 230, 95, 0.75)");  // yellowish highlight color for highlighted text (should match PDFMenu's highlight color)
+            if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
+                this.highlight("rgba(245, 230, 95, 0.75)");  // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color)
             }
         } else {
             runInAction(() => this._width = this._height = 0);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d7b9d9745..26cb4d156 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -224,7 +224,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             }
             else if (de.complete.annoDragData && (!this.props.isAnnotationOverlay || de.complete.annoDragData.dragDocument === this.props.Document)) {
                 e.stopPropagation();
-                return this.addDocument(de.complete.annoDragData.dropDocument);
+                de.complete.annoDragData.annotationDocument = de.complete.annoDragData.annotationDocCreator();
+                de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator();
+                return de.complete.annoDragData.dropDocument && this.addDocument(de.complete.annoDragData.dropDocument);
             }
             return false;
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 588ba6922..2bc716928 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -43,7 +43,6 @@ import { CollectionViewType } from "../CollectionView";
 import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
-import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
@@ -267,8 +266,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @undoBatch
     @action
-    internalPdfAnnoDrop(e: Event, annoDragData: DragManager.PdfAnnoDragData, xp: number, yp: number) {
-        const dragDoc = annoDragData.dropDocument;
+    internalPdfAnnoDrop(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]);
@@ -299,7 +298,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?.dropDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp);
+        if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp);
         if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
         return false;
     }
@@ -813,7 +812,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         else if (this.props.active(true)) {
             e.stopPropagation();
             if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
-            else this.zoom(e.clientX, e.clientY, e.deltaY);
+            else if (!this.props.isAnnotationOverlay) this.zoom(e.clientX, e.clientY, e.deltaY);
         }
         this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]);
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6371ae5fb..13e1f3d54 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -550,10 +550,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 "linking to document tabs not yet supported.  Drop link on document content.");
             return;
         }
+        if (de.complete.annoDragData) de.complete.annoDragData.annotationDocument = de.complete.annoDragData.annotationDocCreator();
         const linkSource = de.complete.annoDragData ? de.complete.annoDragData.annotationDocument : de.complete.linkDragData ? de.complete.linkDragData.linkSourceDocument : undefined;
         if (linkSource && linkSource !== this.props.Document) {
             e.stopPropagation();
-            de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: this._componentView?.getAnchor() || this.rootDoc }, "link", undefined, undefined, undefined, [de.x, de.y]);
+            const dropDoc = this._componentView?.getAnchor() || this.rootDoc;
+            if (de.complete.annoDragData) de.complete.annoDragData.dropDocument = dropDoc;
+            de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]);
         }
     }
 
@@ -873,10 +876,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get panelWidth() { return this.nativeWidth ? this.nativeWidth * this.nativeScaling : this.props.PanelWidth(); }
     @computed get panelHeight() {
         if (this.nativeHeight) {
-            if (this.props.Document._fitWidth) {
-                return Math.min(this.props.PanelHeight(), NumCast(this.props.Document.scrollHeight, this.props.PanelHeight()));
-            }
-            return Math.min(this.props.PanelHeight(), this.nativeHeight * this.nativeScaling);
+            return Math.min(this.props.PanelHeight(),
+                this.props.Document._fitWidth ?
+                    Math.max(NumCast(this.props.Document._height), NumCast((this.props.Document.scrollHeight as number) * this.props.PanelWidth() / this.nativeWidth, this.props.PanelHeight())) :
+                    this.nativeHeight * this.nativeScaling
+            );
         }
         return this.props.PanelHeight();
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e202749aa..47fa25951 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -27,7 +27,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
 import { StyleProp } from '../StyleProvider';
-import { PDFMenu } from '../pdf/PDFMenu';
+import { AnchorMenu } from '../pdf/AnchorMenu';
 import { Dictionary } from 'typescript-collections';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { Annotation } from '../pdf/Annotation';
@@ -83,7 +83,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.clear();
-                    PDFMenu.Instance.fadeOut(true);
+                    AnchorMenu.Instance.fadeOut(true);
                 }
             },
             { fireImmediately: true });
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index f1ef6d10b..55a9818ad 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -27,7 +27,7 @@ import { Transform } from "../../util/Transform";
 import { StyleProp } from "../StyleProvider";
 import { Dictionary } from "typescript-collections";
 import { MarqueeAnnotator } from "../MarqueeAnnotator";
-import { PDFMenu } from "../pdf/PDFMenu";
+import { AnchorMenu } from "../pdf/AnchorMenu";
 const path = require('path');
 
 export const timeSchema = createSchema({
@@ -195,7 +195,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.clear();
-                    PDFMenu.Instance.fadeOut(true);
+                    AnchorMenu.Instance.fadeOut(true);
                 }
             },
             { fireImmediately: true });
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index ca3d4448d..c9c4ed159 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -25,7 +25,7 @@ import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { Annotation } from "../pdf/Annotation";
-import { PDFMenu } from "../pdf/PDFMenu";
+import { AnchorMenu } from "../pdf/AnchorMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./WebBox.scss";
 import React = require("react");
@@ -161,7 +161,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.clear();
-                    PDFMenu.Instance.fadeOut(true);
+                    AnchorMenu.Instance.fadeOut(true);
                 }
             },
             { fireImmediately: true });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c129d0204..f9982f747 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -64,6 +64,8 @@ import { SnappingManager } from '../../../util/SnappingManager';
 import { LinkDocPreview } from '../LinkDocPreview';
 import { SubCollectionViewProps } from '../../collections/CollectionSubView';
 import { StyleProp } from '../../StyleProvider';
+import { AnchorMenu } from '../../pdf/AnchorMenu';
+import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
 
 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
@@ -252,6 +254,43 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             let unchanged = true;
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
 
+            if (!this._editorView.state.selection.empty) {
+                runInAction(() => {
+                    AnchorMenu.Instance.Status = "marquee";
+                    AnchorMenu.Instance.Highlight = action((color: string) => {
+                        this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView?.state, this._editorView?.dispatch);
+                        return undefined;
+                    });
+                    /**
+                     * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
+                     * It also initiates a Drag/Drop interaction to place the text annotation.
+                     */
+                    AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
+                        e.preventDefault();
+                        e.stopPropagation();
+                        const targetCreator = () => {
+                            const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100);
+                            FormattedTextBox.SelectOnLoad = target[Id];
+                            return target;
+                        }
+
+                        DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, {
+                            dragComplete: e => {
+                                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.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
+                            }
+                        });
+                    });
+                });
+                const coordsT = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
+                const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
+                AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom));
+            }
+
             const removeSelection = (json: string | undefined) => {
                 return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
             };
@@ -960,6 +999,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._disposers.selected = reaction(() => this.props.isSelected(),
             action((selected) => {
                 this._recording = false;
+                AnchorMenu.Instance.fadeOut(true);
                 if (RichTextMenu.Instance?.view === this._editorView && !selected) {
                     RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
                 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 038a91aa3..8867595ff 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -255,7 +255,7 @@ export class FormattedTextBoxComment {
                     docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
                             (FormattedTextBoxComment.tooltipText as any).href = href;
-                            FormattedTextBoxComment.linkDoc = DocListCast(textBox.props.Document.links).find(link => link.anchor1 === textBox.props.Document || link.anchor2 === textBox.props.Document ? link : undefined) || linkDoc;
+                            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) {
diff --git a/src/client/views/pdf/AnchorMenu.scss b/src/client/views/pdf/AnchorMenu.scss
new file mode 100644
index 000000000..b7afb26a5
--- /dev/null
+++ b/src/client/views/pdf/AnchorMenu.scss
@@ -0,0 +1,25 @@
+.anchorMenu-addTag {
+    display: grid;
+    width: 200px;
+    padding: 5px;
+    grid-template-columns: 90px 20px 90px;
+}
+
+.color-wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+
+    button.color-button {
+        width: 20px;
+        height: 20px;
+        border-radius: 15px !important;
+        margin: 3px;
+        border: 2px solid transparent !important;
+        padding: 3px;
+
+        &.active {
+            border: 2px solid white;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
new file mode 100644
index 000000000..e2bd5a73d
--- /dev/null
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -0,0 +1,157 @@
+import React = require("react");
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { ColorState } from "react-color";
+import { Doc, Opt } from "../../../fields/Doc";
+import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from "../../../Utils";
+import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
+import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
+import "./AnchorMenu.scss";
+
+@observer
+export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    static Instance: AnchorMenu;
+
+    private _commentCont = React.createRef<HTMLButtonElement>();
+    private _palette = [
+        "rgba(208, 2, 27, 0.8)",
+        "rgba(238, 0, 0, 0.8)",
+        "rgba(245, 166, 35, 0.8)",
+        "rgba(248, 231, 28, 0.8)",
+        "rgba(245, 230, 95, 0.616)",
+        "rgba(139, 87, 42, 0.8)",
+        "rgba(126, 211, 33, 0.8)",
+        "rgba(65, 117, 5, 0.8)",
+        "rgba(144, 19, 254, 0.8)",
+        "rgba(238, 169, 184, 0.8)",
+        "rgba(224, 187, 228, 0.8)",
+        "rgba(225, 223, 211, 0.8)",
+        "rgba(255, 255, 255, 0.8)",
+        "rgba(155, 155, 155, 0.8)",
+        "rgba(0, 0, 0, 0.8)"];
+
+    @observable private _keyValue: string = "";
+    @observable private _valueValue: string = "";
+    @observable private _added: boolean = false;
+    @observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
+
+    @observable public _colorBtn = false;
+    @observable public Highlighting: boolean = false;
+    @observable public Status: "marquee" | "annotation" | "" = "";
+
+    public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
+    public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
+    public Delete: () => void = unimplementedFunction;
+    public AddTag: (key: string, value: string) => boolean = returnFalse;
+    public PinToPres: () => void = unimplementedFunction;
+    public MakePushpin: () => void = unimplementedFunction;
+    public IsPushpin: () => boolean = returnFalse;
+    public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
+    public get Active() { return this._left > 0; }
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+
+        AnchorMenu.Instance = this;
+        AnchorMenu.Instance._canFade = false;
+    }
+
+    pointerDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, (e: PointerEvent) => {
+            this.StartDrag(e, this._commentCont.current!);
+            return true;
+        }, returnFalse, returnFalse);
+    }
+
+    @action
+    highlightClicked = (e: React.MouseEvent) => {
+        if (!this.Highlight(this.highlightColor) && this.Pinned) {
+            this.Highlighting = !this.Highlighting;
+        }
+    }
+
+    @computed get highlighter() {
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
+                <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
+                <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>Change highlighter color:</p>
+                <div className="color-wrapper">
+                    {this._palette.map(color => {
+                        if (color) {
+                            return this.highlightColor === color ?
+                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button> :
+                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button>;
+                        }
+                    })}
+                </div>
+            </div>;
+        return (
+            <Tooltip key="highlighter" title={<div className="dash-tooltip">{"Click to Highlight"}</div>}>
+                <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
+            </Tooltip>
+        );
+    }
+
+    @action changeHighlightColor = (color: string, e: React.PointerEvent) => {
+        const col: ColorState = {
+            hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
+            rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
+        };
+        e.preventDefault();
+        e.stopPropagation();
+        this.highlightColor = Utils.colorString(col);
+    }
+
+    @action keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => { this._keyValue = e.currentTarget.value; };
+    @action valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => { this._valueValue = e.currentTarget.value; };
+    @action addTag = (e: React.PointerEvent) => {
+        if (this._keyValue.length > 0 && this._valueValue.length > 0) {
+            this._added = this.AddTag(this._keyValue, this._valueValue);
+            setTimeout(action(() => this._added = false), 1000);
+        }
+    }
+
+    render() {
+        const buttons = this.Status === "marquee" ?
+            [
+                this.highlighter,
+
+                <Tooltip key="annotate" title={<div className="dash-tooltip">{"Drag to Place Annotation"}</div>}>
+                    <button className="antimodeMenu-button annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: "grab" }}>
+                        <FontAwesomeIcon icon="comment-alt" size="lg" />
+                    </button>
+                </Tooltip>,
+            ] : [
+                <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
+                    <button className="antimodeMenu-button" onPointerDown={this.Delete}>
+                        <FontAwesomeIcon icon="trash-alt" size="lg" />
+                    </button>
+                </Tooltip>,
+                <Tooltip key="Pin" title={<div className="dash-tooltip">{"Pin to Presentation"}</div>}>
+                    <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
+                        <FontAwesomeIcon icon="map-pin" size="lg" />
+                    </button>
+                </Tooltip>,
+                <Tooltip key="pushpin" title={<div className="dash-tooltip">{"toggle pushpin behavior"}</div>}>
+                    <button className="antimodeMenu-button" style={{ color: this.IsPushpin() ? "black" : "white", backgroundColor: this.IsPushpin() ? "white" : "black" }} onPointerDown={this.MakePushpin}>
+                        <FontAwesomeIcon icon="thumbtack" size="lg" />
+                    </button>
+                </Tooltip>,
+                // <div key="7" className="anchorMenu-addTag" >
+                //     <input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
+                //     <input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
+                // </div>,
+                // <button key="8" className="antimodeMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
+                //     <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
+            ];
+
+        return this.getElement(buttons);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 5ef57f986..85dd65901 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -8,7 +8,7 @@ import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../.
 import { LinkManager } from "../../util/LinkManager";
 import { undoBatch } from "../../util/UndoManager";
 import "./Annotation.scss";
-import { PDFMenu } from "./PDFMenu";
+import { AnchorMenu } from "./AnchorMenu";
 
 interface IAnnotationProps {
     anno: Doc;
@@ -84,7 +84,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
             DocListCast(group.annotations).forEach(anno => anno.delete = true);
         }
 
-        PDFMenu.Instance.fadeOut(true);
+        AnchorMenu.Instance.fadeOut(true);
     }
 
     @undoBatch
@@ -105,14 +105,14 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     @action
     onPointerDown = (e: React.PointerEvent) => {
         if (e.button === 2 || e.ctrlKey) {
-            PDFMenu.Instance.Status = "annotation";
-            PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this);
-            PDFMenu.Instance.Pinned = false;
-            PDFMenu.Instance.AddTag = this.addTag.bind(this);
-            PDFMenu.Instance.PinToPres = this.pinToPres;
-            PDFMenu.Instance.MakePushpin = this.makePushpin;
-            PDFMenu.Instance.IsPushpin = this.isPushpin;
-            PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true);
+            AnchorMenu.Instance.Status = "annotation";
+            AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+            AnchorMenu.Instance.Pinned = false;
+            AnchorMenu.Instance.AddTag = this.addTag.bind(this);
+            AnchorMenu.Instance.PinToPres = this.pinToPres;
+            AnchorMenu.Instance.MakePushpin = this.makePushpin;
+            AnchorMenu.Instance.IsPushpin = this.isPushpin;
+            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
             e.stopPropagation();
         }
         else if (e.button === 0) {
diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss
deleted file mode 100644
index fa43a99b2..000000000
--- a/src/client/views/pdf/PDFMenu.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-.pdfMenu-addTag {
-    display: grid;
-    width: 200px;
-    padding: 5px;
-    grid-template-columns: 90px 20px 90px;
-}
-
-.color-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-
-    button.color-button {
-        width: 20px;
-        height: 20px;
-        border-radius: 15px !important;
-        margin: 3px;
-        border: 2px solid transparent !important;
-        padding: 3px;
-
-        &.active {
-            border: 2px solid white;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
deleted file mode 100644
index 603e26021..000000000
--- a/src/client/views/pdf/PDFMenu.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { ColorState } from "react-color";
-import { Doc, Opt } from "../../../fields/Doc";
-import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from "../../../Utils";
-import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
-import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
-import "./PDFMenu.scss";
-
-@observer
-export class PDFMenu extends AntimodeMenu<AntimodeMenuProps> {
-    static Instance: PDFMenu;
-
-    private _commentCont = React.createRef<HTMLButtonElement>();
-    private _palette = [
-        "rgba(208, 2, 27, 0.8)",
-        "rgba(238, 0, 0, 0.8)",
-        "rgba(245, 166, 35, 0.8)",
-        "rgba(248, 231, 28, 0.8)",
-        "rgba(245, 230, 95, 0.616)",
-        "rgba(139, 87, 42, 0.8)",
-        "rgba(126, 211, 33, 0.8)",
-        "rgba(65, 117, 5, 0.8)",
-        "rgba(144, 19, 254, 0.8)",
-        "rgba(238, 169, 184, 0.8)",
-        "rgba(224, 187, 228, 0.8)",
-        "rgba(225, 223, 211, 0.8)",
-        "rgba(255, 255, 255, 0.8)",
-        "rgba(155, 155, 155, 0.8)",
-        "rgba(0, 0, 0, 0.8)"];
-
-    @observable private _keyValue: string = "";
-    @observable private _valueValue: string = "";
-    @observable private _added: boolean = false;
-    @observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
-
-    @observable public _colorBtn = false;
-    @observable public Highlighting: boolean = false;
-    @observable public Status: "marquee" | "annotation" | "" = "";
-
-    public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
-    public Delete: () => void = unimplementedFunction;
-    public AddTag: (key: string, value: string) => boolean = returnFalse;
-    public PinToPres: () => void = unimplementedFunction;
-    public MakePushpin: () => void = unimplementedFunction;
-    public IsPushpin: () => boolean = returnFalse;
-    public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
-    public get Active() { return this._left > 0; }
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-
-        PDFMenu.Instance = this;
-        PDFMenu.Instance._canFade = false;
-    }
-
-    pointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, (e: PointerEvent) => {
-            this.StartDrag(e, this._commentCont.current!);
-            return true;
-        }, returnFalse, returnFalse);
-    }
-
-    @action
-    highlightClicked = (e: React.MouseEvent) => {
-        if (!this.Highlight(this.highlightColor) && this.Pinned) {
-            this.Highlighting = !this.Highlighting;
-        }
-    }
-
-    @computed get highlighter() {
-        const button =
-            <button className="antimodeMenu-button color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
-                <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
-                <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
-            </button>;
-
-        const dropdownContent =
-            <div className="dropdown">
-                <p>Change highlighter color:</p>
-                <div className="color-wrapper">
-                    {this._palette.map(color => {
-                        if (color) {
-                            return this.highlightColor === color ?
-                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button> :
-                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button>;
-                        }
-                    })}
-                </div>
-            </div>;
-        return (
-            <Tooltip key="highlighter" title={<div className="dash-tooltip">{"Click to Highlight"}</div>}>
-                <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
-            </Tooltip>
-        );
-    }
-
-    @action changeHighlightColor = (color: string, e: React.PointerEvent) => {
-        const col: ColorState = {
-            hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
-            rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
-        };
-        e.preventDefault();
-        e.stopPropagation();
-        this.highlightColor = Utils.colorString(col);
-    }
-
-    @action keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => { this._keyValue = e.currentTarget.value; };
-    @action valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => { this._valueValue = e.currentTarget.value; };
-    @action addTag = (e: React.PointerEvent) => {
-        if (this._keyValue.length > 0 && this._valueValue.length > 0) {
-            this._added = this.AddTag(this._keyValue, this._valueValue);
-            setTimeout(action(() => this._added = false), 1000);
-        }
-    }
-
-    render() {
-        const buttons = this.Status === "marquee" ?
-            [
-                this.highlighter,
-
-                <Tooltip key="annotate" title={<div className="dash-tooltip">{"Drag to Place Annotation"}</div>}>
-                    <button className="antimodeMenu-button annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: "grab" }}>
-                        <FontAwesomeIcon icon="comment-alt" size="lg" />
-                    </button>
-                </Tooltip>,
-            ] : [
-                <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
-                    <button className="antimodeMenu-button" onPointerDown={this.Delete}>
-                        <FontAwesomeIcon icon="trash-alt" size="lg" />
-                    </button>
-                </Tooltip>,
-                <Tooltip key="Pin" title={<div className="dash-tooltip">{"Pin to Presentation"}</div>}>
-                    <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
-                        <FontAwesomeIcon icon="map-pin" size="lg" />
-                    </button>
-                </Tooltip>,
-                <Tooltip key="pushpin" title={<div className="dash-tooltip">{"toggle pushpin behavior"}</div>}>
-                    <button className="antimodeMenu-button" style={{ color: this.IsPushpin() ? "black" : "white", backgroundColor: this.IsPushpin() ? "white" : "black" }} onPointerDown={this.MakePushpin}>
-                        <FontAwesomeIcon icon="thumbtack" size="lg" />
-                    </button>
-                </Tooltip>,
-                // <div key="7" className="pdfMenu-addTag" >
-                //     <input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
-                //     <input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
-                // </div>,
-                // <button key="8" className="antimodeMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
-                //     <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
-            ];
-
-        return this.getElement(buttons);
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f9139220b..f2052d454 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -26,7 +26,7 @@ import { FieldViewProps } from "../nodes/FieldView";
 import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment";
 import { LinkDocPreview } from "../nodes/LinkDocPreview";
 import { Annotation } from "./Annotation";
-import { PDFMenu } from "./PDFMenu";
+import { AnchorMenu } from "./AnchorMenu";
 import "./PDFViewer.scss";
 const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
 import React = require("react");
@@ -137,7 +137,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
-                    PDFMenu.Instance.fadeOut(true);
+                    AnchorMenu.Instance.fadeOut(true);
                 }
                 (SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
             },
@@ -384,30 +384,32 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             this._setPreviewCursor?.(e.clientX, e.clientY, true);
         }
         if (!e.altKey && e.button === 0 && this.active(true)) {
-            if (e.target && (e.target as any).parentElement.className !== "textLayer") {
+            if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) {
                 this._marqueeing = [e.clientX, e.clientY];  // if texLayer is hit, then we select text instead of using a marquee
             } else {
                 // clear out old marquees and initialize menu for new selection
-                PDFMenu.Instance.Status = "marquee";
-                PDFMenu.Instance.fadeOut(true);
+                AnchorMenu.Instance.Status = "marquee";
+                AnchorMenu.Instance.fadeOut(true);
                 this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                 this._savedAnnotations.clear();
                 this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
-                document.addEventListener("pointermove", this.onSelectMove);
                 document.addEventListener("pointerup", this.onSelectEnd);
             }
+            document.addEventListener("pointermove", this.onSelectMove);
         }
     }
 
     @action
     finishMarquee = () => {
         this._marqueeing = undefined;
+        document.removeEventListener("pointermove", this.onSelectMove);
         this.props.select(false);
     }
 
     @action
     onSelectMove = (e: PointerEvent): void => {
-        if (e.target && (e.target as any).parentElement === this._mainCont.current) e.stopPropagation();
+        // if (e.target && (e.target as any).parentElement === this._mainCont.current) 
+        e.stopPropagation();
     }
 
     @action
@@ -421,7 +423,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         if (sel?.type === "Range") {
             const selRange = sel.getRangeAt(0);
             this.createTextAnnotation(sel, selRange);
-            PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From e369336c413a337e02afc027cc1aecdd76a496c5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 20 Jan 2021 23:36:16 -0500
Subject: fixed hiding AnchorMenu in text boxes.  encapsulated fadeOut of
 AnchorMenu within class for simplicity.

---
 src/client/views/linking/LinkMenuItem.tsx          |  3 +-
 src/client/views/nodes/ImageBox.tsx                |  1 -
 src/client/views/nodes/VideoBox.tsx                |  1 -
 src/client/views/nodes/WebBox.tsx                  |  1 -
 .../views/nodes/formattedText/FormattedTextBox.tsx | 80 +++++++++++-----------
 src/client/views/pdf/AnchorMenu.tsx                | 11 ++-
 src/client/views/pdf/Annotation.tsx                |  6 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 -
 8 files changed, 56 insertions(+), 49 deletions(-)

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

diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d6cefa5b0..19ef03a31 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -127,9 +127,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     @undoBatch
     @action
-    deleteLink = (): void => {
+    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;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 47fa25951..649fe8f40 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -83,7 +83,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.clear();
-                    AnchorMenu.Instance.fadeOut(true);
                 }
             },
             { fireImmediately: true });
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 55a9818ad..6354c677e 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -195,7 +195,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.clear();
-                    AnchorMenu.Instance.fadeOut(true);
                 }
             },
             { fireImmediately: true });
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c9c4ed159..69f797880 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -161,7 +161,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.clear();
-                    AnchorMenu.Instance.fadeOut(true);
                 }
             },
             { fireImmediately: true });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f9982f747..ac5ea66ff 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -59,7 +59,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark }
 import React = require("react");
 import { LinkManager } from '../../../util/LinkManager';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
-import { CollectionViewType, CollectionViewProps } from '../../collections/CollectionView';
+import { CollectionViewType } from '../../collections/CollectionView';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { LinkDocPreview } from '../LinkDocPreview';
 import { SubCollectionViewProps } from '../../collections/CollectionSubView';
@@ -85,6 +85,7 @@ type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, da
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
+    public static CanAnnotate = true;
     public static Instance: FormattedTextBox;
     public ProseRef?: HTMLDivElement;
     public get EditorView() { return this._editorView; }
@@ -211,6 +212,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.linkOnDeselect.clear();
     }
 
+    @action
+    setupAnchorMenu = () => {
+        AnchorMenu.Instance.Status = "marquee";
+        AnchorMenu.Instance.Highlight = action((color: string) => {
+            this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
+            return undefined;
+        });
+        /**
+         * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
+            e.preventDefault();
+            e.stopPropagation();
+            const targetCreator = () => {
+                const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100);
+                FormattedTextBox.SelectOnLoad = target[Id];
+                return target;
+            }
+
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, {
+                dragComplete: e => {
+                    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.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
+                }
+            });
+        });
+        const coordsT = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
+        const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
+        this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom));
+    }
+
     dispatchTransaction = (tx: Transaction) => {
         let timeStamp;
         clearTimeout(timeStamp);
@@ -254,42 +291,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             let unchanged = true;
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
 
-            if (!this._editorView.state.selection.empty) {
-                runInAction(() => {
-                    AnchorMenu.Instance.Status = "marquee";
-                    AnchorMenu.Instance.Highlight = action((color: string) => {
-                        this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView?.state, this._editorView?.dispatch);
-                        return undefined;
-                    });
-                    /**
-                     * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
-                     * It also initiates a Drag/Drop interaction to place the text annotation.
-                     */
-                    AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
-                        e.preventDefault();
-                        e.stopPropagation();
-                        const targetCreator = () => {
-                            const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100);
-                            FormattedTextBox.SelectOnLoad = target[Id];
-                            return target;
-                        }
-
-                        DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, {
-                            dragComplete: e => {
-                                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.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
-                            }
-                        });
-                    });
-                });
-                const coordsT = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
-                const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
-                AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom));
-            }
+            if (!this._editorView.state.selection.empty && FormattedTextBox.CanAnnotate) this.setupAnchorMenu();
 
             const removeSelection = (json: string | undefined) => {
                 return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
@@ -658,6 +660,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
 
         const uicontrols: ContextMenuProps[] = [];
+        uicontrols.push({ description: `${FormattedTextBox.CanAnnotate ? "Hide" : "Show"} Annotation Bar`, event: () => FormattedTextBox.CanAnnotate = !FormattedTextBox.CanAnnotate, icon: "expand-arrows-alt" });
         uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
         uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
         !Doc.UserDoc().noviceMode && uicontrols.push({ description: `Create TimeStamp When ${this.layoutDoc._timeStampOnEnter ? "Pause" : "Enter"}`, event: () => this.layoutDoc._timeStampOnEnter = !this.layoutDoc._timeStampOnEnter, icon: "expand-arrows-alt" });
@@ -999,7 +1002,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._disposers.selected = reaction(() => this.props.isSelected(),
             action((selected) => {
                 this._recording = false;
-                AnchorMenu.Instance.fadeOut(true);
                 if (RichTextMenu.Instance?.view === this._editorView && !selected) {
                     RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
                 }
@@ -1296,9 +1298,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     componentWillUnmount() {
+        Object.values(this._disposers).forEach(disposer => disposer?.());
         this.endUndoTypingBatch();
         this.unhighlightSearchTerms();
-        Object.values(this._disposers).forEach(disposer => disposer?.());
         this._editorView?.destroy();
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
     }
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index e2bd5a73d..d1fdc6c44 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,7 +1,7 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { ColorState } from "react-color";
 import { Doc, Opt } from "../../../fields/Doc";
@@ -9,6 +9,7 @@ import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from "..
 import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
 import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
 import "./AnchorMenu.scss";
+import { SelectionManager } from "../../util/SelectionManager";
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -58,6 +59,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         AnchorMenu.Instance._canFade = false;
     }
 
+    _disposer: IReactionDisposer | undefined;
+    componentDidMount() {
+        this._disposer = reaction(() => SelectionManager.Views(),
+            selected => {
+                AnchorMenu.Instance.fadeOut(true);
+            });
+    }
+
     pointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, (e: PointerEvent) => {
             this.StartDrag(e, this._commentCont.current!);
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 85dd65901..cd32c2c3a 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -67,8 +67,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     }
 
     componentWillUnmount() {
-        this._brushDisposer && this._brushDisposer();
-        this._reactionDisposer && this._reactionDisposer();
+        this._brushDisposer?.();
+        this._reactionDisposer?.();
     }
 
     @undoBatch
@@ -83,8 +83,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
 
             DocListCast(group.annotations).forEach(anno => anno.delete = true);
         }
-
         AnchorMenu.Instance.fadeOut(true);
+        this.props.select(false);
     }
 
     @undoBatch
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f2052d454..fa97bde3f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -137,7 +137,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 if (!selected) {
                     this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                     this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
-                    AnchorMenu.Instance.fadeOut(true);
                 }
                 (SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
             },
@@ -389,7 +388,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             } else {
                 // clear out old marquees and initialize menu for new selection
                 AnchorMenu.Instance.Status = "marquee";
-                AnchorMenu.Instance.fadeOut(true);
                 this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
                 this._savedAnnotations.clear();
                 this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
-- 
cgit v1.2.3-70-g09d2

