From b8d4b08716791246847d2a647a9df1f37508b87f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 8 Nov 2022 14:43:47 -0500
Subject: making pasting form pdf work again with backlinks. text fixes for
 equationViews and dashField views -- trying to get docref linkAnchor to work.

---
 src/client/views/nodes/DocumentContentsView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 6d6609317..3ab7cc0bc 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { AclPrivate, Doc, Opt } from '../../../fields/Doc';
 import { ScriptField } from '../../../fields/ScriptField';
-- 
cgit v1.2.3-70-g09d2


From 67316c700980fe653c48840407dc9d66a7ed8a2b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 6 Jan 2023 00:35:43 -0500
Subject: added zoom box highlighting of text anchors in pdf/web pages for
 link/pres following. Added serial/parallel option for presentation group with
 up.  Added direct pinning of text seletions to trails.  fixed 'hide' option
 for preselements to work with hidebefore/after

---
 src/client/util/DocumentManager.ts                 | 67 ++++++++++++-----
 src/client/util/LinkFollower.ts                    |  9 ++-
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/MarqueeAnnotator.tsx              | 42 ++++++-----
 src/client/views/collections/CollectionSubView.tsx |  2 -
 src/client/views/collections/TabDocView.tsx        |  5 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 ++-
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.scss           | 16 +++++
 src/client/views/nodes/DocumentView.tsx            | 38 +++++++++-
 src/client/views/nodes/MapBox/MapBox.tsx           |  9 ---
 src/client/views/nodes/PDFBox.tsx                  |  7 ++
 src/client/views/nodes/WebBox.scss                 |  1 +
 src/client/views/nodes/WebBox.tsx                  | 27 +++++--
 src/client/views/nodes/trails/PresBox.tsx          | 83 ++++++++++++++--------
 src/client/views/nodes/trails/PresElementBox.tsx   |  3 +-
 src/client/views/pdf/PDFViewer.tsx                 | 10 ++-
 src/fields/Doc.ts                                  | 16 +++--
 18 files changed, 241 insertions(+), 106 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d2e9e17b4..70fe7f2c0 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,8 +1,8 @@
-import { action, observable, runInAction } from 'mobx';
-import { Doc, Opt } from '../../fields/Doc';
+import { action, observable, ObservableSet, runInAction } from 'mobx';
+import { AnimationSym, Doc, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
-import { Cast, DocCast } from '../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../fields/Types';
 import { AudioField } from '../../fields/URLField';
 import { returnFalse } from '../../Utils';
 import { DocumentType } from '../documents/DocumentTypes';
@@ -42,7 +42,7 @@ export class DocumentManager {
     callAddViewFuncs = (view: DocumentView) => {
         const callFuncs = this._viewRenderedCbs.filter(vc => vc.doc === view.rootDoc);
         if (callFuncs.length) {
-            this._viewRenderedCbs = this._viewRenderedCbs.filter(vc => callFuncs.includes(vc));
+            this._viewRenderedCbs = this._viewRenderedCbs.filter(vc => !callFuncs.includes(vc));
             const intTimer = setInterval(
                 () => {
                     if (!view.ComponentView?.incrementalRendering?.()) {
@@ -147,14 +147,15 @@ export class DocumentManager {
 
     public getDocumentView(toFind: Doc, preferredCollection?: CollectionView): DocumentView | undefined {
         const found =
-            Array.from(DocumentManager.Instance.DocumentViews).find(
-                dv =>
-                    ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFind.data as any)?.url?.href) ||
-                    ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFind.annotationOn)?.data as any)?.url?.href)
-            )?.rootDoc ?? toFind;
+            // Array.from(DocumentManager.Instance.DocumentViews).find(
+            //     dv =>
+            //         ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFind.data as any)?.url?.href) ||
+            //         ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFind.annotationOn)?.data as any)?.url?.href)
+            // )?.rootDoc ??
+            toFind;
         return this.getDocumentViewById(found[Id], preferredCollection);
     }
-    
+
     public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
         const views: DocumentView[] = [];
         Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
@@ -167,11 +168,12 @@ export class DocumentManager {
     };
     public getDocumentViews(toFindIn: Doc): DocumentView[] {
         const toFind =
-            Array.from(DocumentManager.Instance.DocumentViews).find(
-                dv =>
-                    ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
-                    ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
-            )?.rootDoc ?? toFindIn;
+            // Array.from(DocumentManager.Instance.DocumentViews).find(
+            //     dv =>
+            //         ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
+            //         ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
+            // )?.rootDoc ??
+            toFindIn;
 
         const toReturn: DocumentView[] = [];
         const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
@@ -203,6 +205,11 @@ export class DocumentManager {
         }
     }
 
+    public static removeOverlayViews() {
+        DocumentManager._overlayViews?.forEach(action(view => (view.textHtmlOverlay = undefined)));
+        DocumentManager._overlayViews?.clear();
+    }
+    static _overlayViews = new ObservableSet<DocumentView>();
     static addView = (doc: Doc, finished?: () => void) => {
         CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
         finished?.();
@@ -225,7 +232,7 @@ export class DocumentManager {
                 docView?.props.bringToFront(resolvedTarget);
             });
         }
-        const focusAndFinish = (didFocus: boolean) => {
+        const focusAndFinish = action((didFocus: boolean) => {
             const finalTargetDoc = resolvedTarget;
             if (options.toggleTarget) {
                 if (!didFocus && !wasHidden) {
@@ -236,12 +243,26 @@ export class DocumentManager {
                 finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined);
                 !options.noSelect && docView?.select(false);
             }
+            if (targetDoc.textHtml && options.zoomTextSelections) {
+                const containerView = DocumentManager.Instance.getFirstDocumentView(finalTargetDoc);
+                if (containerView) {
+                    containerView.htmlOverlayEffect = StrCast(options?.effect?.presEffect, StrCast(options?.effect?.followLinkAnimEffect));
+                    containerView.textHtmlOverlay = StrCast(targetDoc.textHtml);
+                    DocumentManager._overlayViews.add(containerView);
+                    if (Doc.UnhighlightTimer) {
+                        Doc.AddUnHighlightWatcher(() => {
+                            DocumentManager.removeOverlayViews();
+                            containerView.htmlOverlayEffect = '';
+                        });
+                    } else setTimeout(() => (containerView.htmlOverlayEffect = ''));
+                }
+            }
             finished?.();
-        };
+        });
         const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && this.getFirstDocumentView(annotatedDoc);
         if (annoContainerView) {
             if (annoContainerView.props.Document.layoutKey === 'layout_icon') {
-                return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30);
+                return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, originalTarget, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30);
             }
             if (!docView && targetDoc.type !== DocumentType.MARKER) {
                 annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
@@ -254,7 +275,15 @@ export class DocumentManager {
         const targetDocContextView = (targetDocContext && this.getFirstDocumentView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
         const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
         if (focusView) {
-            !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, options.effect); //TODO:glr make this a setting in PresBox
+            if (focusView.rootDoc === originalTarget) {
+                if (!options.noSelect) Doc.linkFollowHighlight(focusView.rootDoc, undefined, options.effect); //TODO:glr make this a setting in PresBox
+                else {
+                    focusView.rootDoc[AnimationSym] = options.effect;
+                    if (Doc.UnhighlightTimer) {
+                        Doc.AddUnHighlightWatcher(action(() => (focusView.rootDoc[AnimationSym] = undefined)));
+                    }
+                }
+            }
             if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc);
             const doFocus = (forceDidFocus: boolean) =>
                 focusView.focus(originalTarget, {
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 94badbb44..d5ef9fab6 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,12 +1,10 @@
 import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, Opt, WidthSym } from '../../fields/Doc';
-import { listSpec } from '../../fields/Schema';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { LightboxView } from '../views/LightboxView';
 import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView';
-import { PresEffect, PresEffectDirection } from '../views/nodes/trails';
 import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
 import { UndoManager } from './UndoManager';
@@ -59,7 +57,7 @@ export class LinkFollower {
                 createTabForTarget(false);
             } else {
                 // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-                docViewProps.focus(sourceDoc, { willPan: true, willPanZoom: BoolCast(sourceDoc.followLinkZoom, true), zoomScale: 1, afterFocus: createTabForTarget });
+                docViewProps.focus(sourceDoc, { willPan: true, willPanZoom: BoolCast(sourceDoc.followLinkZoom, true), zoomTime: 1000, zoomScale: 1, afterFocus: createTabForTarget });
             }
         };
         runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value
@@ -70,7 +68,7 @@ export class LinkFollower {
             docViewProps.ContainingCollectionDoc,
             action(() => {
                 batch.end();
-                Doc.AddUnlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false)));
+                Doc.AddUnHighlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false)));
             }),
             altKey ? true : undefined
         );
@@ -110,6 +108,7 @@ export class LinkFollower {
                         easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
                         effect: sourceDoc,
                         originatingDoc: sourceDoc,
+                        zoomTextSelections: false,
                     };
                     if (target.TourMap) {
                         const fieldKey = Doc.LayoutFieldKey(target);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 42e259eed..39cc9ba8e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -482,6 +482,7 @@ export class MainView extends React.Component {
     }
 
     globalPointerDown = action((e: PointerEvent) => {
+        DocumentManager.removeOverlayViews();
         Doc.linkFollowUnhighlight();
         AudioBox.Enabled = true;
         const targets = document.elementsFromPoint(e.x, e.y);
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 2fdb59361..bf1242346 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -18,7 +18,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 
 export interface MarqueeAnnotatorProps {
     rootDoc: Doc;
-    down: number[];
+    down?: number[];
     iframe?: () => undefined | HTMLIFrameElement;
     scrollTop: number;
     scaling?: () => number;
@@ -45,6 +45,17 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     @observable private _width: number = 0;
     @observable private _height: number = 0;
 
+    constructor(props: any) {
+        super(props);
+
+        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true);
+        AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true));
+        AnchorMenu.Instance.OnAudio = unimplementedFunction;
+        AnchorMenu.Instance.Highlight = this.highlight;
+        AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations);
+        AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor;
+    }
+
     @action
     static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) {
         AnchorMenu.Instance.Status = 'marquee';
@@ -54,24 +65,21 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         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.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;
+    @action gotDownPoint() {
+        if (!this._width && !this._height) {
+            const downPt = this.props.down!;
+            // set marquee x and y positions to the spatially transformed position
+            const boundingRect = this.props.mainCont.getBoundingClientRect();
+            this._startX = this._left = (downPt[0] - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
+            this._startY = this._top = (downPt[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
+        }
 
         const doc = this.props.iframe?.()?.contentDocument ?? document;
+        doc.removeEventListener('pointermove', this.onSelectMove);
+        doc.removeEventListener('pointerup', this.onSelectEnd);
         doc.addEventListener('pointermove', this.onSelectMove);
         doc.addEventListener('pointerup', this.onSelectEnd);
 
-        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true);
-        AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true));
-        AnchorMenu.Instance.OnAudio = unimplementedFunction;
-        AnchorMenu.Instance.Highlight = this.highlight;
-        AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations);
-        AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor;
-
         /**
          * 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.
@@ -125,7 +133,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                   });
               });
     }
-    componentWillUnmount() {
+    releaseDownPt() {
         const doc = this.props.iframe?.()?.contentDocument ?? document;
         doc.removeEventListener('pointermove', this.onSelectMove);
         doc.removeEventListener('pointerup', this.onSelectEnd);
@@ -259,6 +267,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                 this.highlight('rgba(245, 230, 95, 0.75)', false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color)
             }
             this.props.finishMarquee(undefined, undefined, e);
+            runInAction(() => (this._width = this._height = 0));
         } else {
             runInAction(() => (this._width = this._height = 0));
             this.props.finishMarquee(cliX, cliY, e);
@@ -266,8 +275,9 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     };
 
     render() {
-        return (
+        return !this.props.down ? null : (
             <div
+                ref={r => (r ? this.gotDownPoint() : this.releaseDownPt())}
                 className="marqueeAnnotator-dragBox"
                 style={{
                     left: `${this._left}px`,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b66dc0aa2..4f3f21cea 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -172,8 +172,6 @@ export function CollectionSubView<X>(moreProps?: X) {
                 // The following conditional detects a recurring bug we've seen on the server
                 if (proto[Id] === Docs.Prototypes.get(DocumentType.COL)[Id]) {
                     alert('COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...');
-                    console.log(doc);
-                    console.log(proto);
                     throw new Error(`AHA! You were trying to set a cursor on a collection's proto, which is the original collection proto! Look at the two previously printed lines for document values!`);
                 }
                 let cursors = Cast(proto.cursors, listSpec(CursorField));
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e45e2a1cb..cd58319cb 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -247,8 +247,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 alert('Cannot pin presentation document to itself');
                 return;
             }
-            const pinDoc = Doc.MakeAlias(doc);
-            pinDoc.presentationTargetDoc = doc;
+            const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.();
+            const pinDoc = Doc.MakeAlias(anchorDoc ?? doc);
+            pinDoc.presentationTargetDoc = anchorDoc ?? doc;
             pinDoc.title = doc.title + ' - Slide';
             pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children.  the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
             pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82b97dff0..dc0eb69f3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1146,6 +1146,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset
     }
 
+    _focusCount = 0;
     focusDocument = (doc: Doc, options: DocFocusOptions) => {
         const state = HistoryUtil.getState();
 
@@ -1187,6 +1188,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             }
 
+            const focusCount = ++this._focusCount;
             const startTime = Date.now();
             // focus on this collection within its parent view.  the parent view after focusing determines whether to reset the view change within the collection
             const endFocus = async (moved: boolean) => {
@@ -1200,7 +1202,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         this.Document[this.scaleFieldKey] = restoreState.scale;
                     }
                 }
-                runInAction(() => (this._panZoomTransition = 0));
+                this._focusCount === focusCount && didMove && runInAction(() => (this._panZoomTransition = 0));
                 return resetView;
             };
             const xf = !cantTransform
@@ -2284,5 +2286,8 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
     runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
 });
 ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) {
-    !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) }));
+    !readOnly &&
+        SelectionManager.Views().forEach(view =>
+            TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })
+        );
 });
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 3ab7cc0bc..569579996 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -59,7 +59,7 @@ class ObserverJsxParser1 extends JsxParser {
     }
 }
 
-const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
+export const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 
 interface HTMLtagProps {
     Document: Doc;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index abf6e37ab..453bdac8e 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -51,6 +51,22 @@
         height: calc(100% - 20px);
     }
 
+    .documentView-htmlOverlay {
+        position: absolute;
+        display: flex;
+        top: 0;
+        height: 100%;
+        width: 100%;
+        .documentView-htmlOverlayInner {
+            box-shadow: black 0.2vw 0.2vw 0.8vw;
+            background: rgb(255, 255, 255);
+            overflow: auto;
+            position: relative;
+            margin: auto;
+            padding: 20px;
+        }
+    }
+
     .documentView-linkAnchorBoxAnchor {
         display: flex;
         overflow: hidden;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 76cc6800a..95cf08289 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,6 +3,7 @@ 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 { ObserverJsxParser } from './DocumentContentsView';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
 import { Document } from '../../../fields/documentSchemas';
@@ -110,6 +111,7 @@ export interface DocFocusOptions {
     effect?: Doc; // animation effect for focus
     noSelect?: boolean; // whether target should be selected after focusing
     playAudio?: boolean; // whether to play audio annotation on focus
+    zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
     toggleTarget?: boolean; // whether to toggle target on and off
     originatingDoc?: Doc; // document that triggered the focus
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
@@ -590,9 +592,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
         const focusSpeed = this._componentView?.scrollFocus?.(anchor, { ...options, instant: options?.instant || LinkDocPreview.LinkInfo ? true : false });
         const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing;
+        const startTime = Date.now();
         this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options,
-            afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus || focusSpeed !== undefined) : ViewAdjustment.doNothing), focusSpeed ?? 0)),
+            afterFocus: (didFocus: boolean) =>
+                new Promise<ViewAdjustment>(async res =>
+                    setTimeout(
+                        async () => res(endFocus ? await endFocus(didFocus || focusSpeed !== undefined) : ViewAdjustment.doNothing), //
+                        didFocus ? Math.max(0, (options.zoomTime ?? 500) - (Date.now() - startTime)) : 0
+                    )
+                ),
         });
     };
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
@@ -1098,11 +1107,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
     @observable _retryThumb = 1;
     thumbShown = () => {
+        const childHighlighted = () =>
+            Array.from(Doc.highlightedDocs.keys())
+                .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
+                .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
+        const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
         return !this.props.isSelected() &&
             LightboxView.LightboxDoc !== this.rootDoc &&
             this.thumb &&
             !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
-            (!Doc.isBrushedHighlightedDegree(this.props.Document) || this.rootDoc._viewType === CollectionViewType.Docking) &&
+            ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._viewType === CollectionViewType.Docking) &&
             !this._componentView?.isAnyChildContentActive?.()
             ? true
             : false;
@@ -1754,6 +1768,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
 
+    @observable textHtmlOverlay: Opt<string>;
     @computed get anchorViewDoc() {
         return this.props.LayoutTemplateString?.includes('anchor2') ? DocCast(this.rootDoc['anchor2']) : this.props.LayoutTemplateString?.includes('anchor1') ? DocCast(this.rootDoc['anchor1']) : undefined;
     }
@@ -1793,6 +1808,24 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     isHovering = () => this._isHovering;
     @observable _isHovering = false;
 
+    htmlOverlayEffect = '';
+    @computed get htmlOverlay() {
+        const effectProps = {
+            delay: 0,
+            duration: 500,
+        };
+        const highlight = (
+            <div className="webBox-textHighlight">
+                <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
+            </div>
+        );
+        return !this.textHtmlOverlay ? null : (
+            <div className="documentView-htmlOverlay">
+                <div className="documentView-htmlOverlayInner">{<Fade {...effectProps}>{DocumentViewInternal.AnimationEffect(highlight, { presEffect: this.htmlOverlayEffect ?? 'Zoom' } as any as Doc, this.rootDoc)} </Fade>}</div>
+            </div>
+        );
+    }
+
     render() {
         TraceMobx();
         const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
@@ -1841,6 +1874,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                             focus={this.props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
                         />
+                        {this.htmlOverlay}
                     </div>
                 )}
 
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index b0f6f8358..c8d5b0154 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -322,7 +322,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      */
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
         console.log('print all sidebar Docs');
-        console.log(this.allSidebarDocs);
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
         const docs = doc instanceof Doc ? [doc] : doc;
         docs.forEach(doc => {
@@ -337,8 +336,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }
             }
         }); //add to annotation list
-        console.log('sidebaraddDocument');
-        console.log(doc);
 
         return this.addDocument(doc, sidebarKey); // add to sidebar list
     };
@@ -352,10 +349,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
         if (this.layoutDoc._showSidebar) this.toggleSidebar();
         const docs = doc instanceof Doc ? [doc] : doc;
-        docs.forEach(doc => {
-            console.log(this.allMapMarkers);
-            console.log(this.allSidebarDocs);
-        });
         return this.removeDocument(doc, sidebarKey);
     };
 
@@ -405,7 +398,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      */
     @action
     private handlePlaceChanged = () => {
-        console.log(this.searchBox);
         const place = this.searchBox.getPlace();
 
         if (!place.geometry || !place.geometry.location) {
@@ -416,7 +408,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         // zoom in on the location of the search result
         if (place.geometry.viewport) {
-            console.log(this._map);
             this._map.fitBounds(place.geometry.viewport);
         } else {
             this._map.setCenter(place.geometry.location);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 88aac67a7..e22ee5021 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -213,6 +213,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), options) ?? (didToggle ? 1 : undefined);
     };
     getAnchor = () => {
+        let ele: Opt<HTMLDivElement> = undefined;
+        if (this._pdfViewer?.selectionContent()) {
+            ele = document.createElement('div');
+            ele.append(this._pdfViewer.selectionContent()!);
+        }
         const docAnchor = () => {
             const anchor = Docs.Create.TextanchorDocument({
                 title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)),
@@ -222,6 +227,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return anchor;
         };
         const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? docAnchor();
+        anchor.text = ele?.textContent ?? '';
+        anchor.textHtml = ele?.innerHTML;
         this.addDocument(anchor);
         return anchor;
     };
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index a41f66ef0..6f578a9fc 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -7,6 +7,7 @@
     left: 0;
     position: relative;
     display: flex;
+    overflow: hidden;
 
     .webBox-sideResizer {
         position: absolute;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a3e83f047..8be4884ce 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -29,7 +29,7 @@ import { AnchorMenu } from '../pdf/AnchorMenu';
 import { Annotation } from '../pdf/Annotation';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentViewProps } from './DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternal, DocumentViewProps } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { LinkDocPreview } from './LinkDocPreview';
 import { VideoBox } from './VideoBox';
@@ -54,10 +54,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _keyInput = React.createRef<HTMLInputElement>();
     private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop));
-    private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _searchRef = React.createRef<HTMLInputElement>();
     private _searchString = '';
+
+    private get _getAnchor() {
+        return AnchorMenu.Instance?.GetAnchor;
+    }
     @observable private _webUrl = ''; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render.
     @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe.  so thisforces the ifrmae to be rebuilt when allowScripts is toggled
     @observable private _searching: boolean = false;
@@ -285,7 +288,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
     brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
     scrollFocus = (doc: Doc, options: DocFocusOptions) => {
-        if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), options.instant);
+        if (this._url && StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), options.instant);
         if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
             this.toggleSidebar(options.instant);
         }
@@ -305,6 +308,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     getAnchor = () => {
+        let ele: Opt<HTMLDivElement> = undefined;
+        try {
+            const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents();
+            if (contents) {
+                ele = document.createElement('div');
+                ele.append(contents);
+            }
+        } catch (e) {}
         const anchor =
             this._getAnchor(this._savedAnnotations) ??
             Docs.Create.WebanchorDocument(this._url, {
@@ -312,6 +323,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 y: NumCast(this.layoutDoc._scrollTop),
                 unrendered: true,
             });
+        anchor.text = ele?.textContent ?? '';
+        anchor.textHtml = ele?.innerHTML;
         this.addDocumentWrapper(anchor);
         return anchor;
     };
@@ -553,8 +566,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     static urlHash = (s: string) => {
+        const split = s.split('');
         return Math.abs(
-            s.split('').reduce((a: any, b: any) => {
+            split.reduce((a: any, b: any) => {
                 a = (a << 5) - a + b.charCodeAt(0);
                 return a & a;
             }, 0)
@@ -686,7 +700,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
     @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
-        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this._marqueeing = undefined;
         this._isAnnotating = false;
         this._iframeClick = undefined;
@@ -1006,7 +1019,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 selectionText={returnEmptyString}
                                 annotationLayer={this._annotationLayer.current}
                                 mainCont={this._mainCont.current}
-                            />{' '}
+                            />
                         </div>
                     )}
                 </div>
@@ -1043,5 +1056,5 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 }
 ScriptingGlobals.add(function urlHash(url: string) {
-    return WebBox.urlHash(url);
+    return url ? WebBox.urlHash(url) : 0;
 });
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index b26f485be..2e3135416 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -65,7 +65,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     private _disposers: { [name: string]: IReactionDisposer } = {};
-    private _obDisposers: { [name: string]: any } = {};
     public selectedArray = new ObservableSet<Doc>();
 
     @observable public static Instance: PresBox;
@@ -142,20 +141,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // Turn of progressivize editors
         this.turnOffEdit(true);
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        Object.values(this._obDisposers).forEach(disposer => disposer?.());
     }
 
     @action
     componentDidMount() {
-        this._obDisposers.anim = observe(
-            this,
-            'activeItem',
-            change => {
-                change.oldValue && (DocCast((change.oldValue as Doc).presentationTargetDoc)[AnimationSym] = undefined);
-                change.newValue && (DocCast((change.newValue as Doc).presentationTargetDoc)[AnimationSym] = change.newValue as Doc);
-            },
-            true
-        );
         this._disposers.keyboard = reaction(
             () => this.selectedDoc,
             selected => {
@@ -217,16 +206,37 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
     // No more frames in current doc and next slide is defined, therefore move to next slide
     nextSlide = (slideNum?: number) => {
-        CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => DocumentManager.Instance.getDocumentView(clip)?.ComponentView?.Pause?.());
-        let nextSelected = slideNum ?? this.itemIndex + 1;
-        this.gotoDocument(nextSelected, this.activeItem);
-        for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) {
-            if (this.childDocs[nextSelected].groupWithUp) {
-                this.gotoDocument(nextSelected, this.activeItem, true);
-            } else {
-                break;
+        const nextSlideInd = slideNum ?? this.itemIndex + 1;
+        let curSlideInd = nextSlideInd;
+        const resetSelection = action(() => {
+            this.clearSelectedArray();
+            for (let i = nextSlideInd; i <= curSlideInd; i++) {
+                this.addToSelectedArray(this.childDocs[i]);
             }
-        }
+        });
+        CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => DocumentManager.Instance.getDocumentView(clip)?.ComponentView?.Pause?.());
+        this.clearSelectedArray();
+        const doGroupWithUp =
+            (nextSelected: number, force = false) =>
+            () => {
+                if (nextSelected < this.childDocs.length) {
+                    if (force || this.childDocs[nextSelected].groupWithUp) {
+                        const serial = nextSelected + 1 < this.childDocs.length && NumCast(this.childDocs[nextSelected + 1].groupWithUp) > 1;
+                        if (serial) {
+                            this.gotoDocument(nextSelected, this.activeItem, true, async () => {
+                                const waitTime = NumCast(this.activeItem.presDuration) - NumCast(this.activeItem.presTransition);
+                                await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime)));
+                                doGroupWithUp(nextSelected + 1)();
+                            });
+                        } else {
+                            this.gotoDocument(nextSelected, this.activeItem, undefined, resetSelection);
+                            curSlideInd = this.itemIndex;
+                            doGroupWithUp(nextSelected + 1)();
+                        }
+                    }
+                }
+            };
+        doGroupWithUp(curSlideInd, true)();
     };
 
     // Called when the user activates 'next' - to move to the next part of the pres. trail
@@ -270,9 +280,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //The function that is called when a document is clicked or reached through next or back.
     //it'll also execute the necessary actions if presentation is playing.
     @undoBatch
-    public gotoDocument = action((index: number, from?: Doc, group?: boolean) => {
+    public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
         Doc.UnBrushAllDocs();
-
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
             const activeItem: Doc = this.activeItem;
@@ -280,10 +289,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const activeFrame = activeItem.presActiveFrame ?? activeItem.presCurrentFrame;
             if (activeFrame !== undefined) {
                 const transTime = NumCast(activeItem.presTransition, 500);
-                const context = activeItem.presActiveFrame !== undefined ? DocCast(DocCast(activeItem.presentationTargetDoc).context) : DocCast(activeItem.presentationTargetDoc);
+                const acontext = activeItem.presActiveFrame !== undefined ? DocCast(DocCast(activeItem.presentationTargetDoc).context) : DocCast(activeItem.presentationTargetDoc);
+                const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
                 if (context) {
                     const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView;
-                    if (ffview) {
+                    if (ffview?.childDocs) {
                         this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, ffview.childDocs.slice(), transTime);
                         context._currentFrame = NumCast(activeFrame);
                     }
@@ -302,7 +312,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (!group) this.clearSelectedArray();
             this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
             this.turnOffEdit();
-            this.navigateToActiveItem(); //Handles movement to element only when presTrail is list
+            this.navigateToActiveItem(finished); //Handles movement to element only when presTrail is list
             this.onHideDocument(); //Handles hide after/before
         }
     });
@@ -489,13 +499,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * a new tab. If presCollection is undefined it will open the document
      * on the right.
      */
-    navigateToActiveItem = () => {
+    navigateToActiveItem = (afterNav?: () => void) => {
         const activeItem: Doc = this.activeItem;
         const targetDoc: Doc = this.targetDoc;
+        const finished = () => {
+            afterNav?.();
+            console.log('Finish Slide Nav: ' + targetDoc.title);
+            targetDoc[AnimationSym] = undefined;
+        };
         const srcContext = Cast(targetDoc.context, Doc, null) ?? Cast(Cast(targetDoc.annotationOn, Doc, null)?.context, Doc, null);
         const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
         const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined;
-        const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc);
+        const includesDoc: boolean = DocumentManager.Instance.getDocumentView(targetDoc) ? true : false; //   DocListCast(presCollection?.data).includes(targetDoc);
         const tabMap = CollectionDockingView.Instance?.tabMap;
         const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc === srcContext || tab.DashDoc === targetDoc);
         // Handles the setting of presCollection
@@ -517,13 +532,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             selViewCache.forEach(doc => this.addToSelectedArray(doc));
             this._dragArray.splice(0, this._dragArray.length, ...dragViewCache);
             this._eleArray.splice(0, this._eleArray.length, ...eleViewCache);
+            finished();
         });
         const createDocView = (doc: Doc, finished?: () => void) => {
             DocumentManager.Instance.AddViewRenderedCb(doc, () => finished?.());
             (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.lightbox);
             this.layoutDoc.presCollection = targetDoc;
         };
-        PresBox.NavigateToTarget(targetDoc, activeItem, createDocView, srcContext, includesDoc || tab ? undefined : resetSelection);
+        PresBox.NavigateToTarget(targetDoc, activeItem, createDocView, srcContext, includesDoc || tab ? finished : resetSelection);
     };
 
     static NavigateToTarget(targetDoc: Doc, activeItem: Doc, createDocView: any, srcContext: Doc, finished?: () => void) {
@@ -552,11 +568,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     willPanZoom: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump || activeItem.presMovement === PresMovement.Center,
                     zoomScale: activeItem.presMovement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1),
                     zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500),
+                    effect: activeItem,
                     noSelect: true,
                     originatingDoc: activeItem,
                     easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
+                    zoomTextSelections: true
                 };
-
+                if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[AnimationSym] = undefined;
                 var containerDocContext = srcContext ? [srcContext] : [];
                 while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
                     containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
@@ -577,12 +595,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.childDocs.forEach((doc, index) => {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
-            //if (tagDoc) tagDoc.opacity = 1;
             const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
-            const curInd: number = itemIndexes.indexOf(index);
             if (tagDoc === this.layoutDoc.presCollection) {
                 tagDoc.opacity = 1;
             } else {
+                if (curDoc.presHide) {
+                    if (index !== this.itemIndex) {
+                        tagDoc.opacity = 1;
+                    }
+                }
                 const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex);
                 if (curDoc.presHideBefore && index === hidingIndBef) {
                     if (index > this.itemIndex) {
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 4469be488..5e1474b89 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -507,11 +507,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 <Tooltip key="arrow" title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}>
                     <div
                         className="slideButton"
-                        onClick={() => (activeItem.groupWithUp = !activeItem.groupWithUp)}
+                        onClick={() => (activeItem.groupWithUp = (NumCast(activeItem.groupWithUp) + 1) % 3)}
                         style={{
                             zIndex: 1000 - this.indexInPres,
                             fontWeight: 700,
                             backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
+                            outline: NumCast(activeItem.groupWithUp) > 1 ? 'solid black 1px' : undefined,
                             height: activeItem.groupWithUp ? 53 : 18,
                             transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined,
                         }}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 906dff377..f95d5ac2e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -68,9 +68,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
-    public _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _selectionText: string = '';
+    private _selectionContent: DocumentFragment | undefined;
     private _downX: number = 0;
     private _downY: number = 0;
     private _lastSearch = false;
@@ -78,8 +78,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _ignoreScroll = false;
     private _initialScroll: { loc: Opt<number>; easeFunc: 'linear' | 'ease' | undefined } | undefined;
     private _forcedScroll = true;
+    get _getAnchor() {
+        return AnchorMenu.Instance?.GetAnchor;
+    }
 
     selectionText = () => this._selectionText;
+    selectionContent = () => this._selectionContent;
 
     @observable isAnnotating = false;
     // key where data is stored
@@ -392,7 +396,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     finishMarquee = (x?: number, y?: number) => {
-        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
         this._marqueeing = undefined;
         this._textSelecting = true;
@@ -437,7 +440,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 }
             }
         }
-        this._selectionText = selRange.cloneContents().textContent || '';
+        this._selectionContent = selRange.cloneContents();
+        this._selectionText = this._selectionContent?.textContent || '';
 
         // clear selection
         if (sel.empty) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6762665a2..19ffc5005 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,6 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
-import { action, computed, observable, ObservableMap, runInAction } from 'mobx';
+import { action, computed, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { alias, map, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
@@ -343,6 +343,10 @@ export class Doc extends RefField {
     @observable public [DirectLinksSym]: Set<Doc> = new Set();
     @observable public [AnimationSym]: Opt<Doc>;
     @observable public [HighlightSym]: boolean = false;
+    static __Anim(Doc: Doc) {
+        // for debugging to print AnimationSym field easily.
+        return Doc[AnimationSym];
+    }
 
     private [UpdatingFromServer]: boolean = false;
     private [ForceServerWrite]: boolean = false;
@@ -1131,7 +1135,7 @@ export namespace Doc {
         BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
         SearchMatchDoc: ObservableMap<Doc, { searchMatch: number }> = new ObservableMap();
     }
-    const brushManager = new DocBrush();
+    export const brushManager = new DocBrush();
 
     export class DocData {
         @observable _user_doc: Doc = undefined!;
@@ -1284,8 +1288,8 @@ export namespace Doc {
     }
 
     let UnhighlightWatchers: (() => void)[] = [];
-    let UnhighlightTimer: any;
-    export function AddUnlightWatcher(watcher: () => void) {
+    export let UnhighlightTimer: any;
+    export function AddUnHighlightWatcher(watcher: () => void) {
         if (UnhighlightTimer) {
             UnhighlightWatchers.push(watcher);
         } else watcher();
@@ -1302,16 +1306,16 @@ export namespace Doc {
         }, 5000);
     }
 
-    var highlightedDocs = new Set<Doc>();
+    export var highlightedDocs = new ObservableSet<Doc>();
     export function IsHighlighted(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
         return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym];
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) {
         runInAction(() => {
-            doc[AnimationSym] = presEffect;
             highlightedDocs.add(doc);
             doc[HighlightSym] = true;
+            doc[AnimationSym] = presEffect;
             if (dataAndDisplayDocs) {
                 highlightedDocs.add(Doc.GetProto(doc));
                 Doc.GetProto(doc)[HighlightSym] = true;
-- 
cgit v1.2.3-70-g09d2

