From f8008ec8d21f48591ba54a934022278c63852dba Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 11 Feb 2021 17:07:07 -0500
Subject: now pdf/web/text scroll to the target and then pan the document to be
 in view.

---
 src/client/views/collections/CollectionStackingView.tsx | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingView.tsx')

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9c12d5020..f100247c9 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -168,18 +168,23 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         return this.props.addDocTab(doc, where);
     }
 
-    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
+    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
         Doc.BrushDoc(doc);
-        this.props.focus(this.props.Document, true);  // bcz: want our containing collection to zoom
         Doc.linkFollowHighlight(doc);
 
+        let focusSpeed = 0;
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]);
         if (found) {
             const top = found.getBoundingClientRect().top;
             const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
-            smoothScroll(doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+            if (Math.floor(localTop[1]) !== 0) {
+                smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+            }
         }
-        afterFocus && setTimeout(afterFocus, 500);
+        const endFocus = async (moved: boolean) => afterFocus ? await afterFocus(moved) : false;
+        this.props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) =>
+            new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)));
+
     }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
-- 
cgit v1.2.3-70-g09d2


From d0607264d955d90eca8178d5bfbc6a72f47fa872 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 12 Feb 2021 12:17:44 -0500
Subject: fixed subtle bug with lightbox inadvertently clearing out childDoc
 list of a collection. added shift-double-cllick to open collection in
 lightbox.

---
 src/client/documents/Documents.ts                        |  1 +
 src/client/util/LinkManager.ts                           |  8 ++------
 src/client/views/LightboxView.tsx                        |  2 +-
 src/client/views/collections/CollectionStackingView.tsx  |  2 +-
 src/client/views/collections/CollectionSubView.tsx       |  4 +++-
 .../collectionFreeForm/CollectionFreeFormView.tsx        | 16 ++++++++++------
 src/client/views/nodes/DocumentView.tsx                  |  9 +++------
 src/client/views/nodes/PDFBox.tsx                        |  2 +-
 8 files changed, 22 insertions(+), 22 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d370ca8d..aafa58028 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -56,6 +56,7 @@ import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { DocAfterFocusFunc } from "../views/nodes/DocumentView";
+import { Transform } from "../util/Transform";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index d433605f1..c39a4abda 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -106,12 +106,8 @@ export class LinkManager {
         // open up target if it's not already in view ...
         const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
             const createTabForTarget = (didFocus: boolean) => new Promise<boolean>(res => {
-                const where = StrCast(sourceDoc.followLinkLocation) || followLoc;
-                if (LightboxView.LightboxDoc) {
-                    LightboxView.SetLightboxDoc(doc);
-                } else {
-                    docViewProps.addDocTab(doc, where);
-                }
+                const where = LightboxView.LightboxDoc ? "lightbox" : StrCast(sourceDoc.followLinkLocation) || followLoc;
+                docViewProps.addDocTab(doc, where);
                 setTimeout(() => {
                     const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
                     if (targDocView) {
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index fbfc9672f..529eecacf 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -26,7 +26,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         if (!doc) {
             LightboxView.LightboxFuture = LightboxView.LightboxHistory = [];
         } else if (future) {
-            LightboxView.LightboxFuture = future;
+            LightboxView.LightboxFuture = future.slice();
         }
         return true;
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index f100247c9..6425e1625 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -181,7 +181,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                 smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
             }
         }
-        const endFocus = async (moved: boolean) => afterFocus ? await afterFocus(moved) : false;
+        const endFocus = async (moved: boolean) => afterFocus ? afterFocus(moved) : false;
         this.props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) =>
             new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)));
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 287bc56c2..3cbda37e2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -112,7 +112,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             const docFilters = this.docFilters();
             const docRangeFilters = this.docRangeFilters();
             const searchDocs = this.searchFilterDocs();
-            if (this.props.Document.dontRegisterView || (!docFilters.length && !docRangeFilters.length && !searchDocs.length)) return childDocs;
+            if (this.props.Document.dontRegisterView || (!docFilters.length && !docRangeFilters.length && !searchDocs.length)) {
+                return childDocs;
+            }
 
             const docsforFilter: Doc[] = [];
             childDocs.forEach((d) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 74175c0b2..1a6c6bf0c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -50,6 +50,7 @@ import { StyleProp, StyleLayers } from "../../StyleProvider";
 import { DocumentDecorations } from "../../DocumentDecorations";
 import { FieldViewProps } from "../../nodes/FieldView";
 import { reset } from "colors";
+import { LightboxView } from "../../LightboxView";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -441,7 +442,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             return;
         }
         this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
-        if (e.button === 0 && (!e.shiftKey || this._hitCluster !== -1) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+        if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.active(true)) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
@@ -608,10 +609,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onClick = (e: React.MouseEvent) => {
         if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a double click
-                runInAction(() => DocumentLinksButton.StartLink = DocumentLinksButton.StartLinkView = undefined);
-                this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
-                e.stopPropagation();
-                e.preventDefault();
+                if (e.shiftKey) {
+                    LightboxView.SetLightboxDoc(this.rootDoc, this.childDocs);
+                } else {
+                    this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
             }
             this._lastTap = Date.now();
         }
@@ -918,7 +922,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const layoutdoc = Doc.Layout(doc);
             const savedState = { px: NumCast(this.Document._panX), py: NumCast(this.Document._panY), s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
-            const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup;
+            const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup || this.layoutDoc._lockedTransform;
             const { px, py } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: px, panY: py };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 40dfd1643..66436fa75 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -377,7 +377,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
         const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here      
-        const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? await afterFocus(true) : false;
+        const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? afterFocus(true) : false;
         this.props.focus(docTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
             new Promise<boolean>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0)), docTransform);
 
@@ -408,10 +408,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click");
                     } else if (!Doc.IsSystem(this.props.Document)) {
                         if (this.props.Document.type !== DocumentType.LABEL) {
-                            UndoManager.RunInBatch(() => {
-                                const fullScreenDoc = Cast(this.props.Document._fullScreenView, Doc, null) || this.props.Document;
-                                this.props.addDocTab(fullScreenDoc, "lightbox");
-                            }, "double tap");
+                            UndoManager.RunInBatch(() => this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox"), "double tap");
                             SelectionManager.DeselectAll();
                         }
                         Doc.UnBrushDoc(this.props.Document);
@@ -963,7 +960,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     docViewPathFunc = () => this.docViewPath;
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
-    select = (ctrlPressed: boolean) => SelectionManager.SelectView(this, ctrlPressed);
+    select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
     NativeWidth = () => this.nativeWidth;
     NativeHeight = () => this.nativeHeight;
     PanelWidth = () => this.panelWidth;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f8d222798..c20d958ff 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -80,7 +80,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         }
     }
 
-    scrollFocus = (doc: Doc, smooth: boolean, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => this._pdfViewer?.scrollFocus(doc, smooth, willZoom, scale, afterFocus);
+    scrollFocus = (doc: Doc, smooth: boolean) => this._pdfViewer?.scrollFocus(doc, smooth);
     getAnchor = () => this.rootDoc;
     componentWillUnmount() { this._selectReactionDisposer?.(); }
     componentDidMount() {
-- 
cgit v1.2.3-70-g09d2


From 4e1df78c6d94fabd949113801261150de8ed686c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 13 Feb 2021 21:14:05 -0500
Subject: fixed bug introduced where stacked documents couldn't be reordered. 
 changed lightbox future a bit.

---
 src/client/util/LinkManager.ts                     |  4 ---
 src/client/views/LightboxView.tsx                  | 30 ++++++++++++----------
 .../views/collections/CollectionStackingView.tsx   | 11 ++++----
 src/client/views/nodes/LinkAnchorBox.tsx           |  3 +--
 src/client/views/nodes/PresBox.tsx                 |  1 +
 5 files changed, 25 insertions(+), 24 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingView.tsx')

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index c39a4abda..1e6e02e55 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -145,12 +145,8 @@ export class LinkManager {
             if (linkDoc) {
                 const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
                     (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
-                const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
-                    doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
-                        (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
                 if (target) {
                     const containerDoc = Cast(target.annotationOn, Doc, null) || target;
-                    targetTimecode !== undefined && (containerDoc._currentTimecode = targetTimecode);
                     const targetContext = Cast(containerDoc?.context, Doc, null);
                     const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
                     DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 7fab88800..d36c3ad4f 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -44,15 +44,15 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             };
         }
         if (future) {
-            LightboxView.LightboxFuture = future.slice();
+            LightboxView.LightboxFuture = future.slice().sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
         }
         LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc;
 
         return true;
     }
     public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView!); }
-    public static LightboxHistory: (Opt<Doc>)[] = [];
-    public static LightboxFuture: (Opt<Doc>)[] = [];
+    public static LightboxHistory: Opt<Doc[]> = [];
+    public static LightboxFuture: Opt<Doc[]> = [];
     public static LightboxDocView: Opt<DocumentView>;
     @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); }
     @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); }
@@ -73,14 +73,18 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     }
     public static AddDocTab = (doc: Doc, location: string) => {
         SelectionManager.DeselectAll();
-        return LightboxView.SetLightboxDoc(doc, [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"])]
-            .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
+        return LightboxView.SetLightboxDoc(doc,
+            [...DocListCast(doc[Doc.LayoutFieldKey(doc)]),
+            ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]),
+            ...(LightboxView.LightboxFuture ?? [])
+            ]
+                .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
     }
     addDocTab = LightboxView.AddDocTab;
 
     fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc;
     render() {
-        if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc);
+        if (LightboxView.LightboxHistory?.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory?.push(LightboxView.LightboxDoc!);
         let downx = 0, downy = 0;
         return !LightboxView.LightboxDoc ? (null) :
             <div className="lightboxView-frame"
@@ -134,28 +138,28 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         renderDepth={0} />
                 </div>
                 {this.navBtn(undefined, "chevron-left",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none",
+                    () => LightboxView.LightboxDoc && LightboxView.LightboxHistory?.length ? "" : "none",
                     action(e => {
                         e.stopPropagation();
-                        const previous = LightboxView.LightboxHistory.pop();
-                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory.lastElement();
+                        const previous = LightboxView.LightboxHistory?.pop();
+                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory?.lastElement();
                         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
                         if (docView && target) {
-                            if (LightboxView.LightboxFuture.lastElement() !== previous) LightboxView.LightboxFuture.push(previous);
+                            if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!);
                             docView.focus(target, true, 0.9);
                         } else {
                             LightboxView.SetLightboxDoc(target);
                         }
                     }))}
                 {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none",
+                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none",
                     action(e => {
                         e.stopPropagation();
-                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture.pop();
+                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop();
                         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
                         if (docView && target) {
                             docView.focus(target, true, 0.9);
-                            if (LightboxView.LightboxHistory.lastElement() !== target) LightboxView.LightboxHistory.push(target);
+                            if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target);
                         } else {
                             LightboxView.SetLightboxDoc(target);
                         }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6425e1625..2d03c5279 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -44,7 +44,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     _draggerRef = React.createRef<HTMLDivElement>();
     _pivotFieldDisposer?: IReactionDisposer;
     _autoHeightDisposer?: IReactionDisposer;
-    _docXfs: any[] = [];
+    _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = [];
     _columnStart: number = 0;
     @observable _heightMap = new Map<string, number>();
     @observable _cursor: CursorProperty = "grab";
@@ -204,6 +204,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
 
         let dref: Opt<HTMLDivElement>;
         const stackedDocTransform = () => this.getDocTransform(doc, dref);
+        this._docXfs.push({ stackedDocTransform, width, height });
         return <DocumentView ref={r => dref = r?.ContentDiv ? r.ContentDiv : undefined}
             Document={doc}
             DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
@@ -295,8 +296,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         let dropAfter = 0;
         if (de.complete.docDragData) {
             this._docXfs.map((cd, i) => {
-                const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
-                const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
+                const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+                const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height());
                 if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) {
                     dropInd = i;
                     const axis = this.Document._viewType === CollectionViewType.Masonry ? 0 : 1;
@@ -327,8 +328,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         const where = [e.clientX, e.clientY];
         let targInd = -1;
         this._docXfs.map((cd, i) => {
-            const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
-            const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
+            const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+            const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height());
             if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
                 targInd = i;
             }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index db5414069..d76b61502 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -124,8 +124,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
         const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
 
-        const timecode = this.dataDoc[anchor + "_timecode"];
-        const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title) + (timecode !== undefined ? ":" + timecode : "");
+        const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
         const flyout = (
             <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.rootDoc)}>
                 <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => { })} />
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 77b050abe..b9480fa74 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -70,6 +70,7 @@ export class PinProps {
     audioRange?: boolean;
     unpin?: boolean;
     setPosition?: boolean;
+    hidePresBox?: boolean;
 }
 
 type PresBoxSchema = makeInterface<[typeof documentSchema]>;
-- 
cgit v1.2.3-70-g09d2


From 6fcef3d8dfdbe373724d2f11c9df8f350bd8950c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 14 Feb 2021 18:29:00 -0500
Subject: fixes to focus()'s api and extension to allow focusing on a Tab's
 contents.

---
 src/client/documents/Documents.ts                  |  7 +--
 src/client/util/DocumentManager.ts                 | 38 ++++++------
 src/client/util/LinkManager.ts                     | 28 +++++----
 src/client/views/LightboxView.tsx                  |  5 +-
 .../collections/CollectionStackedTimeline.tsx      |  4 +-
 .../views/collections/CollectionStackingView.tsx   | 12 ++--
 src/client/views/collections/TabDocView.tsx        | 29 +++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 71 ++++++++++++----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 35 +++++++----
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 13 files changed, 139 insertions(+), 98 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d72efb1b4..fee2679b6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -35,6 +35,7 @@ import { AudioBox } from "../views/nodes/AudioBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { ComparisonBox } from "../views/nodes/ComparisonBox";
 import { DocHolderBox } from "../views/nodes/DocHolderBox";
+import { DocFocusOptions } from "../views/nodes/DocumentView";
 import { FilterBox } from "../views/nodes/FilterBox";
 import { FontIconBox } from "../views/nodes/FontIconBox";
 import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
@@ -55,8 +56,6 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
-import { DocAfterFocusFunc } from "../views/nodes/DocumentView";
-import { Transform } from "../util/Transform";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -1020,8 +1019,8 @@ export namespace DocUtils {
         });
     }
 
-    export function DefaultFocus(doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) {
-        afterFocus?.(false);
+    export function DefaultFocus(doc: Doc, options?: DocFocusOptions) {
+        options?.afterFocus?.(false);
     }
 
     export let ActiveRecordings: AudioBox[] = [];
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d2251583c..dd59efa41 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -139,9 +139,11 @@ export class DocumentManager {
         closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
         originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
         finished?: () => void,
+        originalTarget?: Doc
     ): Promise<void> => {
+        originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
-        let docView = getFirstDocView(targetDoc, originatingDoc);
+        const docView = getFirstDocView(targetDoc, originatingDoc);
         const highlight = () => {
             const finalDocView = getFirstDocView(targetDoc);
             finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc);
@@ -166,15 +168,16 @@ export class DocumentManager {
         const targetDocContext = contextDoc || annotatedDoc;
         const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
         if (!docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView) {
-            rtfView.focus(targetDoc, false);
+            rtfView.focus(targetDoc);
         }
         else if (docView) {
-            docView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) =>
-                new Promise<boolean>(res => {
-                    focusAndFinish(didFocus);
-                    res();
-                })
-            );
+            docView.props.focus(targetDoc, {
+                originalTarget, willZoom, afterFocus: (didFocus: boolean) =>
+                    new Promise<boolean>(res => {
+                        focusAndFinish(didFocus);
+                        res();
+                    })
+            });
         } else {
             if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
                 createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
@@ -182,7 +185,7 @@ export class DocumentManager {
             } else {  // otherwise try to get a view of the context of the target
                 if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
                     targetDocContext._viewTransition = "transform 500ms";
-                    targetDocContextView.props.focus(targetDocContextView.rootDoc, willZoom);
+                    targetDocContextView.props.focus(targetDocContextView.rootDoc, { willZoom });
 
                     // now find the target document within the context
                     if (targetDoc._timecodeToShow) {  // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
@@ -192,12 +195,13 @@ export class DocumentManager {
                         const findView = (delay: number) => {
                             const retryDocView = getFirstDocView(targetDoc);  // test again for the target view snce we presumably created the context above by focusing on it
                             if (retryDocView) {   // we found the target in the context
-                                retryDocView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) =>
-                                    new Promise<boolean>(res => {
-                                        focusAndFinish(didFocus);
-                                        res();
-                                    })
-                                ); // focus on the target in the context
+                                retryDocView.props.focus(targetDoc, {
+                                    willZoom, afterFocus: (didFocus: boolean) =>
+                                        new Promise<boolean>(res => {
+                                            focusAndFinish(didFocus);
+                                            res();
+                                        })
+                                }); // focus on the target in the context
                                 highlight();
                             } else if (delay > 1500) {
                                 // we didn't find the target, so it must have moved out of the context.  Go back to just creating it.
@@ -213,7 +217,7 @@ export class DocumentManager {
                         findView(0);
                     }
                 } else {  // there's no context view so we need to create one first and try again when that finishes
-                    const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished);
+                    const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget);
                     createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
                         finishFunc);
                 }
@@ -222,4 +226,4 @@ export class DocumentManager {
     }
 
 }
-Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); });
\ No newline at end of file
+Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, { willZoom: true })); });
\ No newline at end of file
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 1e6e02e55..f741d9c2c 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -111,10 +111,12 @@ export class LinkManager {
                 setTimeout(() => {
                     const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
                     if (targDocView) {
-                        targDocView.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, (didFocus: boolean) => {
-                            finished?.();
-                            res(true);
-                            return new Promise<boolean>(res2 => res2());
+                        targDocView.props.focus(doc, {
+                            willZoom: BoolCast(sourceDoc.followLinkZoom, false), afterFocus: (didFocus: boolean) => {
+                                finished?.();
+                                res(true);
+                                return new Promise<boolean>(res2 => res2());
+                            }
                         });
                     } else {
                         res(where !== "inPlace"); // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
@@ -126,30 +128,30 @@ export class LinkManager {
                 createTabForTarget(false);
             } else {
                 // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-                docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, createTabForTarget);
+                docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
             }
         };
         LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
 
-    public static traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
-        const linkDocs = link ? [link] : DocListCast(doc.links);
-        const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
-        const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
+    public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+        const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
+        const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
+        const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
         const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
         const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
         const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
         const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
-        const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
+        const followLinks = linkDocList.length ? (sourceDoc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
         followLinks.forEach(async linkDoc => {
             if (linkDoc) {
-                const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
-                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
+                const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 :
+                    (Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
                 if (target) {
                     const containerDoc = Cast(target.annotationOn, Doc, null) || target;
                     const targetContext = Cast(containerDoc?.context, Doc, null);
                     const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished);
                 } else {
                     finished?.();
                 }
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index eb3d62779..a2088a8fc 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -89,7 +89,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop();
         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (docView && target) {
-            docView.focus(target, true, 0.9);
+            docView.focus(target, { willZoom: true, scale: 0.9 });
             if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target);
         } else {
             if (!target && LightboxView.path.length) {
@@ -120,7 +120,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (docView && target) {
             if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!);
-            docView.focus(target, true, 0.9);
+            docView.focus(target, { willZoom: true, scale: 0.9 });
         } else {
             LightboxView.SetLightboxDoc(target);
         }
@@ -138,6 +138,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         if (coll) {
             const fieldKey = Doc.LayoutFieldKey(coll);
             LightboxView.SetLightboxDoc(coll, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
+            TabDocView.PinDoc(coll, { hidePresBox: true });
         }
         setTimeout(() => this.stepForward());
     }
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index efbab3ede..e3e753902 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -259,8 +259,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         const anchor = observable({ view: undefined as any });
         const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: (notFocused: boolean) => Promise<boolean>, docTransform?: Transform) => {
             this.props.playLink(mark);
-            this.props.focus(doc, willZoom, scale, afterFocus, docTransform);
-        }
+            this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
+        };
         return {
             anchor, view: <DocumentView key="view"  {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                 ref={action((r: DocumentView | null) => anchor.view = r)}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2d03c5279..1d6112745 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -21,7 +21,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentView, DocAfterFocusFunc, DocumentViewProps } from "../nodes/DocumentView";
+import { DocumentView, DocumentViewProps, DocFocusOptions } from "../nodes/DocumentView";
 import { FieldViewProps } from "../nodes/FieldView";
 import { StyleProp } from "../StyleProvider";
 import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
@@ -168,7 +168,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         return this.props.addDocTab(doc, where);
     }
 
-    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+    focusDocument = (doc: Doc, options?: DocFocusOptions) => {
         Doc.BrushDoc(doc);
         Doc.linkFollowHighlight(doc);
 
@@ -181,9 +181,11 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                 smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
             }
         }
-        const endFocus = async (moved: boolean) => afterFocus ? afterFocus(moved) : false;
-        this.props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) =>
-            new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)));
+        const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : false;
+        this.props.focus(this.rootDoc, {
+            willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
+                new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
+        });
 
     }
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index d62b8b6f6..4c792186a 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -14,6 +14,7 @@ import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
+import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -23,7 +24,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { LightboxView } from '../LightboxView';
-import { DocAfterFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
+import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import { FieldViewProps } from '../nodes/FieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/PresBox';
 import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
@@ -34,7 +35,6 @@ import { CollectionViewType } from './CollectionView';
 import "./TabDocView.scss";
 import React = require("react");
 import Color = require('color');
-import { DocUtils } from '../../documents/Documents';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
@@ -209,7 +209,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             const tabs = Cast(sublists[0], Doc, null);
             const tabdocs = await DocListCastAsync(tabs.data);
             runInAction(() => {
-                if (!tabdocs?.includes(curPres)) {
+                if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) {
                     tabdocs?.push(curPres);  // bcz: Argh! this is annoying.  if multiple documents are pinned, this will get called multiple times before the presentation view is drawn.  Thus it won't be in the tabdocs list and it will get created multple times.  so need to explicilty add the presbox to the list of open tabs
                     CollectionDockingView.AddSplit(curPres, "right");
                 }
@@ -279,7 +279,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
             case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
-            case "lightbox": return LightboxView.AddDocTab(doc, location);
+            case "lightbox": {
+                // TabDocView.PinDoc(doc, { hidePresBox: true });
+                return LightboxView.AddDocTab(doc, location);
+            }
             case "inPlace":
             case "add":
             default:
@@ -357,11 +360,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             </Tooltip>
         </>;
     }
-    focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
+    @action
+    focusFunc = (doc: Doc, options?: DocFocusOptions) => {
+        const vals = (!options?.originalTarget || options?.originalTarget === this._document) && this.view?.ComponentView?.freeformData?.(true);
+        if (vals && this._document) {
+            const focusSpeed = 1000;
+            this._document._panX = vals.panX;
+            this._document._panY = vals.panY;
+            this._document._viewScale = vals.scale;
+            this._document._viewTrasition = `transform ${focusSpeed}ms`;
+            setTimeout(action(() => {
+                this._document!._viewTransition = undefined;
+                options?.afterFocus?.(false);
+            }), focusSpeed);
+        } else {
+            options?.afterFocus?.(false);
+        }
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
         }
-        afterFocus?.(false);
     }
     active = () => this._isActive;
     ScreenToLocalTransform = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 954b6478f..92916b5db 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,22 +1,23 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, StrListCast } from "../../../../fields/Doc";
+import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
 import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
 import { Id } from "../../../../fields/FieldSymbols";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
 import { RichTextField } from "../../../../fields/RichTextField";
-import { createSchema, makeInterface, listSpec } from "../../../../fields/Schema";
+import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
 import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnVal, returnTrue } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
 import { DocumentType } from "../../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
@@ -30,13 +31,16 @@ import { undoBatch } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
 import { Timeline } from "../../animationtimeline/Timeline";
 import { ContextMenu } from "../../ContextMenu";
+import { DocumentDecorations } from "../../DocumentDecorations";
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
-import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewProps } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
-import { DocumentViewProps, DocAfterFocusFunc, DocumentView } from "../../nodes/DocumentView";
+import { LightboxView } from "../../LightboxView";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { DocFocusOptions, DocumentView, DocumentViewProps } from "../../nodes/DocumentView";
+import { FieldViewProps } from "../../nodes/FieldView";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import { PresBox } from "../../nodes/PresBox";
+import { StyleLayers, StyleProp } from "../../StyleProvider";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
 import { CollectionViewType } from "../CollectionView";
@@ -45,12 +49,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { StyleProp, StyleLayers } from "../../StyleProvider";
-import { DocumentDecorations } from "../../DocumentDecorations";
-import { FieldViewProps } from "../../nodes/FieldView";
-import { reset } from "colors";
-import { LightboxView } from "../../LightboxView";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -118,8 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentVals() {
-        return this.fitToContent &&
-        {
+        return {
             panX: (this.contentBounds.x + this.contentBounds.r) / 2,
             panY: (this.contentBounds.y + this.contentBounds.b) / 2,
             scale: !this.childDocs.length ? 1 :
@@ -135,9 +132,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private panX = () => this.fitToContentVals?.panX ?? NumCast(this.Document._panX);
-    private panY = () => this.fitToContentVals?.panY ?? NumCast(this.Document._panY);
-    private zoomScaling = () => (this.fitToContentVals?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling;
+    private panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX);
+    private panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY);
+    private zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling;
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
         return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / this.parentScaling / scaling;  // shift so pan position is at center of window for non-overlay collections
@@ -860,7 +857,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
             }
         }
-        if (!this.layoutDoc._lockedTransform || CurrentUserUtils.OverlayDocs.includes(this.Document)) {
+        if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || CurrentUserUtils.OverlayDocs.includes(this.Document)) {
             this.Document._viewTransition = panType;
             const scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -899,7 +896,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
     }
 
-    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+    focusDocument = (doc: Doc, options?: DocFocusOptions) => {
         const state = HistoryUtil.getState();
 
         // TODO This technically isn't correct if type !== "doc", as
@@ -918,23 +915,26 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const LightboxState = LightboxView.LightboxDoc === this.props.Document && LightboxView.SavedState ? LightboxView.SavedState : undefined;
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
-            this.props.focus(doc, willZoom, scale, afterFocus);
+            this.props.focus(doc, options);
         } else {
-            const xfToCollection = docTransform ?? Transform.Identity();
+            const xfToCollection = options?.docTransform ?? Transform.Identity();
             const layoutdoc = Doc.Layout(doc);
             const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
-            const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup || this.layoutDoc._lockedTransform;
-            const { panX, panY } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .75 : undefined);
+            const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+            const { panX, panY, scale } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
                 HistoryUtil.pushState(newState);
             }
             // focus on the document in the collection
-            const didMove = !doc.z && (panX !== savedState.panX || panY !== savedState.panY);
+            const didMove = !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined);
             const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
-            if (didMove) this.setPan(panX, panY, `transform ${focusSpeed}ms`, 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
+            if (didMove) {
+                this.setPan(panX, panY, `transform ${focusSpeed}ms`, 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
+                options?.willZoom && (this.Document[this.scaleFieldKey] = scale);
+            }
             Doc.BrushDoc(this.rootDoc);
             !doc.hidden && Doc.linkFollowHighlight(doc);
 
@@ -942,7 +942,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             // 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) => {
                 doc.hidden && Doc.UnHighlightDoc(doc);
-                const resetView = afterFocus ? await afterFocus(moved) : false;
+                const resetView = options?.afterFocus ? await options?.afterFocus(moved) : false;
                 if (resetView) {
                     const restoreState = LightboxView.LightboxDoc !== this.props.Document && LightboxState ? LightboxState : savedState;
                     this.Document._panX = restoreState.panX;
@@ -959,8 +959,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX),
                         NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
 
-            this.props.focus(cantTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
-                new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), xf);
+            this.props.focus(cantTransform ? doc : this.rootDoc, {
+                ...options, docTransform: xf,
+                afterFocus: (didFocus: boolean) => new Promise<boolean>(res =>
+                    setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime))))
+            });
         }
     }
 
@@ -973,8 +976,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
         if (scale) {
             const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
-            this.Document[this.scaleFieldKey] = Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])));
-            return { panX: (bounds.left + bounds.right) / 2, panY: (bounds.top + bounds.bot) / 2 };
+
+            return {
+                panX: (bounds.left + bounds.right) / 2,
+                panY: (bounds.top + bounds.bot) / 2,
+                scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])))
+            };
         } else {
             const cx = NumCast(this.layoutDoc._panX);
             const cy = NumCast(this.layoutDoc._panY);
@@ -1188,7 +1195,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
     }
-    freeformData = () => { return this.fitToContentVals; }
+    freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
     @action
     componentDidMount() {
         super.componentDidMount?.();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 4b0422ed3..e93d7ff72 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -153,7 +153,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
     panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
     screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
-    focusDoc = (doc: Doc) => this.props.focus(doc, false);
+    focusDoc = (doc: Doc) => this.props.focus(doc);
     returnThis = () => this;
     render() {
         TraceMobx();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9801de1a2..9d91311f6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, HeightSym } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -37,13 +37,20 @@ import { DocumentLinksButton } from './DocumentLinksButton';
 import "./DocumentView.scss";
 import { FieldViewProps } from "./FieldView";
 import { LinkAnchorBox } from './LinkAnchorBox';
+import { LinkDocPreview } from "./LinkDocPreview";
 import { PresBox } from './PresBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
-import { LinkDocPreview } from "./LinkDocPreview";
 
+export interface DocFocusOptions {
+    originalTarget?: Doc;
+    willZoom?: boolean;
+    scale?: number;
+    afterFocus?: DocAfterFocusFunc;
+    docTransform?: Transform;
+}
 export type DocAfterFocusFunc = (notFocused: boolean) => Promise<boolean>;
-export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => void;
+export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     getAnchor?: () => Doc;
@@ -52,7 +59,7 @@ export interface DocComponentView {
     forward?: () => boolean;
     url?: () => string;
     submitURL?: (url: string) => boolean;
-    freeformData?: () => Opt<{ panX: number, panY: number, scale: number }>;
+    freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number }>;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
@@ -376,11 +383,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
-    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+    focus = (doc: Doc, options?: DocFocusOptions) => {
         const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here      
-        const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? afterFocus(true) : false;
-        this.props.focus(docTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
-            new Promise<boolean>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0)), docTransform);
+        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : false;
+        this.props.focus(options?.docTransform ? doc : this.rootDoc, {
+            ...options, afterFocus: (didFocus: boolean) =>
+                new Promise<boolean>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0))
+        });
 
     }
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
@@ -439,7 +448,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 } else clickFunc();
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
                 this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right");
-            } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+            } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey && !(e.nativeEvent as any).formattedHandled) {
                 this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
             } else {
                 if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
@@ -575,7 +584,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         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();
-            const dropDoc = this._componentView?.getAnchor() || this.rootDoc;
+            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]);
         }
@@ -718,7 +727,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
-    setContentView = (view: { getAnchor: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
+    setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
     @observable contentsActive: () => boolean = returnFalse;
     @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive;
     @computed get contents() {
@@ -918,8 +927,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
     contentsActive = () => this.docView?.contentsActive();
-    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
-        return this.docView?.focus(doc, willZoom, scale, afterFocus);
+    focus = (doc: Doc, options?: DocFocusOptions) => {
+        return this.docView?.focus(doc, options);
     }
     getBounds = () => {
         if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 441d6232a..74bff2bfe 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -152,7 +152,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         });
     }
     @undoBatch
-    resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes;
+    resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes
 
     @undoBatch
     rotate = action(() => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b941c07f6..33147e7f3 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -138,7 +138,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
             this.scrollFocus(this.initialScrollTarget, false);
             this.initialScrollTarget = undefined;
         }
-    };
+    }
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
 
     settingsPanel() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9f9f11ab5..a63e0daef 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1456,7 +1456,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
         }
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
-        (e.nativeEvent as any).formattedHandled = true;
+        this.props.isSelected(true) && ((e.nativeEvent as any).formattedHandled = true);
 
         if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
             // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
-- 
cgit v1.2.3-70-g09d2


From 4ef4d06413169971a3919aa0c935eb321be7288b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 14 Feb 2021 21:48:55 -0500
Subject: fixed freeformview focus to not pan annotation documents or
 layoutEngine freeform views. fixed some exceptions.  cleaned up focus()
 return value.

---
 src/client/util/DocumentManager.ts                  |  6 +++---
 src/client/util/LinkManager.ts                      | 13 +++++++------
 .../views/collections/CollectionDockingView.tsx     |  8 ++++----
 .../views/collections/CollectionStackedTimeline.tsx |  4 ++--
 .../views/collections/CollectionStackingView.tsx    |  6 +++---
 src/client/views/collections/TabDocView.tsx         |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 10 +++++-----
 src/client/views/nodes/DocumentView.tsx             | 21 +++++++++++++--------
 8 files changed, 38 insertions(+), 32 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index dd59efa41..6013a2ef3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -7,7 +7,7 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionView } from '../views/collections/CollectionView';
 import { LightboxView } from '../views/LightboxView';
-import { DocumentView } from '../views/nodes/DocumentView';
+import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView';
 import { Scripting } from './Scripting';
 
 export class DocumentManager {
@@ -173,7 +173,7 @@ export class DocumentManager {
         else if (docView) {
             docView.props.focus(targetDoc, {
                 originalTarget, willZoom, afterFocus: (didFocus: boolean) =>
-                    new Promise<boolean>(res => {
+                    new Promise<ViewAdjustment>(res => {
                         focusAndFinish(didFocus);
                         res();
                     })
@@ -197,7 +197,7 @@ export class DocumentManager {
                             if (retryDocView) {   // we found the target in the context
                                 retryDocView.props.focus(targetDoc, {
                                     willZoom, afterFocus: (didFocus: boolean) =>
-                                        new Promise<boolean>(res => {
+                                        new Promise<ViewAdjustment>(res => {
                                             focusAndFinish(didFocus);
                                             res();
                                         })
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index f741d9c2c..402cbdd68 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -3,7 +3,7 @@ import { computedFn } from "mobx-utils";
 import { Doc, DocListCast, Opt } from "../../fields/Doc";
 import { BoolCast, Cast, StrCast } from "../../fields/Types";
 import { LightboxView } from "../views/LightboxView";
-import { DocumentViewSharedProps } from "../views/nodes/DocumentView";
+import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
 import { DocumentManager } from "./DocumentManager";
 import { SharingManager } from "./SharingManager";
 import { UndoManager } from "./UndoManager";
@@ -105,21 +105,22 @@ export class LinkManager {
         const batch = UndoManager.StartBatch("follow link click");
         // open up target if it's not already in view ...
         const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
-            const createTabForTarget = (didFocus: boolean) => new Promise<boolean>(res => {
+            const createTabForTarget = (didFocus: boolean) => new Promise<ViewAdjustment>(res => {
                 const where = LightboxView.LightboxDoc ? "lightbox" : StrCast(sourceDoc.followLinkLocation) || followLoc;
                 docViewProps.addDocTab(doc, where);
                 setTimeout(() => {
                     const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
                     if (targDocView) {
                         targDocView.props.focus(doc, {
-                            willZoom: BoolCast(sourceDoc.followLinkZoom, false), afterFocus: (didFocus: boolean) => {
+                            willZoom: BoolCast(sourceDoc.followLinkZoom, false),
+                            afterFocus: (didFocus: boolean) => {
                                 finished?.();
-                                res(true);
-                                return new Promise<boolean>(res2 => res2());
+                                res(ViewAdjustment.resetView);
+                                return new Promise<ViewAdjustment>(res2 => res2());
                             }
                         });
                     } else {
-                        res(where !== "inPlace"); // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
+                        res(where !== "inPlace" ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace'  resetting the initial focus&zoom would negate the zoom into the target 
                     }
                 });
             });
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index eee939c07..47c5746d4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -375,13 +375,13 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             const sublists = DocListCast(this.props.Document[this.props.fieldKey]);
             const tabs = Cast(sublists[0], Doc, null);
             const other = Cast(sublists[1], Doc, null);
-            const tabdocs = await DocListCastAsync(tabs.data);
-            const otherdocs = await DocListCastAsync(other.data);
-            Doc.GetProto(tabs).data = new List<Doc>(docs);
+            const tabdocs = await DocListCastAsync(tabs?.data);
+            const otherdocs = await DocListCastAsync(other?.data);
+            tabs && (Doc.GetProto(tabs).data = new List<Doc>(docs));
             const otherSet = new Set<Doc>();
             otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
             tabdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
-            Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values()));
+            other && (Doc.GetProto(other).data = new List<Doc>(Array.from(otherSet.values())));
         }, 0);
     }
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index e3e753902..16a1c02f7 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -14,7 +14,7 @@ import { Scripting } from "../../util/Scripting";
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
 import { CollectionSubView } from "../collections/CollectionSubView";
-import { DocumentView } from "../nodes/DocumentView";
+import { DocumentView, DocAfterFocusFunc } from "../nodes/DocumentView";
 import { LabelBox } from "../nodes/LabelBox";
 import "./CollectionStackedTimeline.scss";
 import { Transform } from "../../util/Transform";
@@ -257,7 +257,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
 
     renderInner = computedFn(function (this: CollectionStackedTimeline, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
         const anchor = observable({ view: undefined as any });
-        const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: (notFocused: boolean) => Promise<boolean>, docTransform?: Transform) => {
+        const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
             this.props.playLink(mark);
             this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
         };
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 1d6112745..0ac1cdb28 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -21,7 +21,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentView, DocumentViewProps, DocFocusOptions } from "../nodes/DocumentView";
+import { DocumentView, DocumentViewProps, DocFocusOptions, ViewAdjustment } from "../nodes/DocumentView";
 import { FieldViewProps } from "../nodes/FieldView";
 import { StyleProp } from "../StyleProvider";
 import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
@@ -181,10 +181,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                 smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
             }
         }
-        const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : false;
+        const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing;
         this.props.focus(this.rootDoc, {
             willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
-                new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
+                new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
         });
 
     }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4c792186a..d2a83b1dd 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -207,7 +207,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             const fieldKey = CollectionDockingView.Instance.props.fieldKey;
             const sublists = DocListCast(dview[fieldKey]);
             const tabs = Cast(sublists[0], Doc, null);
-            const tabdocs = await DocListCastAsync(tabs.data);
+            const tabdocs = await DocListCastAsync(tabs?.data);
             runInAction(() => {
                 if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) {
                     tabdocs?.push(curPres);  // bcz: Argh! this is annoying.  if multiple documents are pinned, this will get called multiple times before the presentation view is drawn.  Thus it won't be in the tabdocs list and it will get created multple times.  so need to explicilty add the presbox to the list of open tabs
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 92916b5db..038fe8272 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -35,7 +35,7 @@ import { DocumentDecorations } from "../../DocumentDecorations";
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
 import { LightboxView } from "../../LightboxView";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocFocusOptions, DocumentView, DocumentViewProps } from "../../nodes/DocumentView";
+import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../../nodes/DocumentView";
 import { FieldViewProps } from "../../nodes/FieldView";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
@@ -921,14 +921,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const layoutdoc = Doc.Layout(doc);
             const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
-            const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+            const cantTransform = this.props.layoutEngine?.() || this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
             const { panX, panY, scale } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
                 HistoryUtil.pushState(newState);
             }
             // focus on the document in the collection
-            const didMove = !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined);
+            const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined);
             const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
             if (didMove) {
@@ -942,7 +942,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             // 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) => {
                 doc.hidden && Doc.UnHighlightDoc(doc);
-                const resetView = options?.afterFocus ? await options?.afterFocus(moved) : false;
+                const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing;
                 if (resetView) {
                     const restoreState = LightboxView.LightboxDoc !== this.props.Document && LightboxState ? LightboxState : savedState;
                     this.Document._panX = restoreState.panX;
@@ -961,7 +961,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
             this.props.focus(cantTransform ? doc : this.rootDoc, {
                 ...options, docTransform: xf,
-                afterFocus: (didFocus: boolean) => new Promise<boolean>(res =>
+                afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res =>
                     setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime))))
             });
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9d91311f6..7dea3784e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,14 +42,19 @@ import { PresBox } from './PresBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
 
+export enum ViewAdjustment {
+    resetView = 1,
+    doNothing = 0
+
+}
 export interface DocFocusOptions {
-    originalTarget?: Doc;
-    willZoom?: boolean;
-    scale?: number;
-    afterFocus?: DocAfterFocusFunc;
-    docTransform?: Transform;
+    originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
+    willZoom?: boolean;   // determines whether to zoom in on target document
+    scale?: number;       // percent of containing frame to zoom into document
+    afterFocus?: DocAfterFocusFunc;  // function to call after focusing on a document
+    docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom.  when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
 }
-export type DocAfterFocusFunc = (notFocused: boolean) => Promise<boolean>;
+export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>;
 export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
@@ -385,10 +390,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     focus = (doc: Doc, options?: DocFocusOptions) => {
         const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here      
-        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : false;
+        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
         this.props.focus(options?.docTransform ? doc : this.rootDoc, {
             ...options, afterFocus: (didFocus: boolean) =>
-                new Promise<boolean>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0))
+                new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
         });
 
     }
-- 
cgit v1.2.3-70-g09d2


From 05a9a3eed9a1afb55ec0ca55845b33cc745289e0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 16 Feb 2021 00:59:28 -0500
Subject: updated pivot views to have a getAnchor() method to save the
 pivotField when linking.  added an engineProp to layoutEngiens so that you
 can preview a change in a LinkDocPreview without modifying the document.

---
 src/client/util/DocumentManager.ts                 |  16 +--
 src/client/views/StyleProvider.tsx                 |   3 +-
 .../views/collections/CollectionDockingView.tsx    |   6 +-
 .../views/collections/CollectionStackingView.tsx   |   2 -
 .../views/collections/CollectionTimeView.scss      |   2 +-
 .../views/collections/CollectionTimeView.tsx       | 115 ++++++++++++++++-----
 .../CollectionFreeFormLayoutEngines.tsx            |  14 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  25 +++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |   8 +-
 .../formattedText/FormattedTextBoxComment.tsx      |   2 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   3 +-
 11 files changed, 128 insertions(+), 68 deletions(-)

(limited to 'src/client/views/collections/CollectionStackingView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 6013a2ef3..d028258b2 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -144,10 +144,6 @@ export class DocumentManager {
         originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
         const docView = getFirstDocView(targetDoc, originatingDoc);
-        const highlight = () => {
-            const finalDocView = getFirstDocView(targetDoc);
-            finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc);
-        };
         const focusAndFinish = (didFocus: boolean) => {
             if (originatingDoc?.isPushpin) {
                 if (!didFocus || targetDoc.hidden) {
@@ -157,7 +153,6 @@ export class DocumentManager {
                 targetDoc.hidden && (targetDoc.hidden = undefined);
                 docView?.select(false);
             }
-            highlight();
             finished?.();
             return false;
         };
@@ -167,11 +162,10 @@ export class DocumentManager {
         const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
         const targetDocContext = contextDoc || annotatedDoc;
         const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
-        if (!docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView) {
-            rtfView.focus(targetDoc);
-        }
-        else if (docView) {
-            docView.props.focus(targetDoc, {
+        const focusView = !docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView ? rtfView : docView;
+        if (focusView) {
+            focusView && Doc.linkFollowHighlight(focusView.rootDoc);
+            focusView.focus(targetDoc, {
                 originalTarget, willZoom, afterFocus: (didFocus: boolean) =>
                     new Promise<ViewAdjustment>(res => {
                         focusAndFinish(didFocus);
@@ -181,7 +175,6 @@ export class DocumentManager {
         } else {
             if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
                 createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
-                highlight();
             } else {  // otherwise try to get a view of the context of the target
                 if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
                     targetDocContext._viewTransition = "transform 500ms";
@@ -202,7 +195,6 @@ export class DocumentManager {
                                             res();
                                         })
                                 }); // focus on the target in the context
-                                highlight();
                             } else if (delay > 1500) {
                                 // we didn't find the target, so it must have moved out of the context.  Go back to just creating it.
                                 if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 0ab8c5dfb..73a82d7ab 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -76,8 +76,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
         case StyleProp.HideLinkButton: return isAnchor || props?.dontRegisterView || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
         case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle,
             !Doc.IsSystem(doc) && doc.type === DocumentType.RTF ?
-                (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") :
-                undefined);
+                (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") : "") || "";
         case StyleProp.Color:
             if (isCaption) return "white";
             const backColor = backgroundCol() || "black";
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 47c5746d4..7130ec29e 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -21,9 +21,9 @@ import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import { CollectionViewType } from './CollectionView';
 import { TabDocView } from './TabDocView';
 import React = require("react");
-import { stat } from 'fs';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { listSpec } from '../../../fields/Schema';
+import { LightboxView } from '../LightboxView';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -435,6 +435,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 }
 
+Scripting.addGlobal(function openInLightbox(doc: any) { LightboxView.AddDocTab(doc, "lightbox"); },
+    "opens up document in a lightbox", "(doc: any)");
 Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddSplit(doc, "right"); },
-    "opens up the inputted document on the right side of the screen", "(doc: any)");
+    "opens up document in tab on right side of the screen", "(doc: any)");
 Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0ac1cdb28..77046f5ea 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -170,7 +170,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
 
     focusDocument = (doc: Doc, options?: DocFocusOptions) => {
         Doc.BrushDoc(doc);
-        Doc.linkFollowHighlight(doc);
 
         let focusSpeed = 0;
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]);
@@ -186,7 +185,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
                 new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
         });
-
     }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index fa7c87f4e..d995cbcd2 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -75,7 +75,7 @@
 
     .collectionTimeView-innards {
         display: inline-block;
-        width: calc(100% - 200px);
+        width: 100%;
         height: 100%;
     }
 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 869e01fea..8c3e53dfd 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocCastAsync } from "../../../fields/Doc";
+import { Doc, Opt, DocCastAsync, StrListCast } from "../../../fields/Doc";
 import { List } from "../../../fields/List";
 import { ObjectField } from "../../../fields/ObjectField";
 import { RichTextField } from "../../../fields/RichTextField";
@@ -19,7 +19,12 @@ const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
-import { DocUtils } from "../../documents/Documents";
+import { DocUtils, Docs } from "../../documents/Documents";
+import { toUpper } from "lodash";
+import { DocumentManager } from "../../util/DocumentManager";
+import { listSpec } from "../../../fields/Schema";
+import { LinkManager } from "../../util/LinkManager";
+import { LinkDocPreview } from "../nodes/LinkDocPreview";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -28,15 +33,56 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _collapsed: boolean = false;
     @observable _childClickedScript: Opt<ScriptField>;
     @observable _viewDefDivClick: Opt<ScriptField>;
+
+    getAnchor = () => {
+        const anchor = Docs.Create.TextanchorDocument({
+            title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
+            useLinkSmallAnchor: true,
+            hideLinkButton: true,
+            annotationOn: this.rootDoc
+        });
+        Doc.GetProto(anchor).pivotField = this.pivotField;
+        Doc.GetProto(anchor).docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField);
+        Doc.GetProto(anchor)._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField);
+        if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) {
+            Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor);
+        } else {
+            this.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>([anchor]);
+        }
+        return anchor;
+    }
+
     async componentDidMount() {
-        const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.props.Document.type), "");
-        const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+        this.props.setContentView?.(this);
+        const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
+        ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+        const childText = "openInLightbox(self, shiftKey); ";
         runInAction(() => {
             this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
             this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
         });
     }
 
+    @observable _scrollFilters: Opt<string[]>;
+    @observable _scrollPivotField: Opt<string>;
+    timeDocFilters = () => this._scrollFilters || ([] as string[]);
+    get pivotField() { return this._scrollPivotField || StrCast(this.layoutDoc._pivotField); }
+    @action
+    scrollFocus = (anchor: Doc, smooth: boolean) => {
+        if (!LinkDocPreview.LinkInfo) {
+            if (anchor.pivotField !== undefined) {
+                this.layoutDoc._prevFilterIndex = 1;
+                this.layoutDoc._pivotField = StrCast(anchor.pivotField);
+                this.layoutDoc._docFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docFilters as ObjectField);
+                this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docRangeFilters as ObjectField);
+            }
+        } else {
+            this._scrollPivotField = StrCast(anchor.pivotField);
+            this._scrollFilters = StrListCast(Doc.GetProto(anchor).docFilters);
+        }
+        return 0;
+    }
+
     layoutEngine = () => this._layoutEngine;
     toggleVisibility = action(() => this._collapsed = !this._collapsed);
 
@@ -69,24 +115,30 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }), returnFalse, emptyFunction);
     }
 
-    contentsDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
-            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
-            if (prevFilterIndex > 0) {
-                prevFilterIndex--;
-                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
-                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
-                this.props.Document._prevFilterIndex = prevFilterIndex;
-            } else {
-                this.props.Document._docFilters = new List([]);
-            }
-        }), false);
+    goTo = (prevFilterIndex: number) => {
+        this.layoutDoc._pivotField = this.layoutDoc["_prevPivotFields" + prevFilterIndex];
+        this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocFilter" + prevFilterIndex] as ObjectField);
+        this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+        this.layoutDoc._prevFilterIndex = prevFilterIndex;
+    }
+
+    @action
+    contentsDown = (e: React.MouseEvent) => {
+        let prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
+        if (prevFilterIndex > 0) {
+            this.goTo(prevFilterIndex - 1);
+        } else {
+            this.layoutDoc._docFilters = new List([]);
+        }
     }
 
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.active() ? undefined : "none" }}
+            onClick={this.contentsDown}>
             <CollectionFreeFormView {...this.props}
+                engineProps={{ pivotField: this.pivotField, docFilters: this.docFilters }}
                 fitContentsToDoc={returnTrue}
+                docFilters={this.timeDocFilters}
                 childClickScript={this._childClickedScript}
                 viewDefDivClick={this._viewDefDivClick}
                 childFreezeDimensions={true}
@@ -100,7 +152,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     }
     specificMenu = (e: React.MouseEvent) => {
         const layoutItems: ContextMenuProps[] = [];
-        const doc = this.props.Document;
+        const doc = this.layoutDoc;
 
         layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline"; }, icon: "compress-arrows-alt" });
         layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot"; }, icon: "compress-arrows-alt" });
@@ -123,10 +175,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
             pair.layout[fieldKey] instanceof RichTextField ||
             typeof (pair.layout[fieldKey]) === "number" ||
-            typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
+            typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] === "#" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
         Array.from(keySet).map(fieldKey =>
-            docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
-        docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" });
+            docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
+        docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
         ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
         const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         ContextMenu.Instance.displayMenu(x, y, ":");
@@ -137,13 +189,13 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             GetValue: () => "",
             SetValue: (value: any) => {
                 if (value?.length) {
-                    this.props.Document._pivotField = value;
+                    this.layoutDoc._pivotField = value;
                     return true;
                 }
                 return false;
             },
             showMenuOnLoad: true,
-            contents: ":" + StrCast(this.props.Document._pivotField),
+            contents: ":" + StrCast(this.layoutDoc._pivotField),
             toggle: this.toggleVisibility,
             color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
         };
@@ -155,12 +207,12 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     render() {
         let nonNumbers = 0;
         this.childDocs.map(doc => {
-            const num = NumCast(doc[StrCast(this.props.Document._pivotField)], Number(StrCast(doc[StrCast(this.props.Document._pivotField)])));
+            const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
             if (Number.isNaN(num)) {
                 nonNumbers++;
             }
         });
-        const forceLayout = StrCast(this.props.Document._forceRenderEngine);
+        const forceLayout = StrCast(this.layoutDoc._forceRenderEngine);
         const doTimeline = forceLayout ? (forceLayout === "timeline") : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
         if (doTimeline !== (this._layoutEngine === "timeline")) {
             if (!this._changing) {
@@ -173,7 +225,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }
 
         return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
-            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.layoutDoc._chromeStatus === "enabled" ? 51 : 0}px)` }}>
             {this.pivotKeyUI}
             {this.contents}
             {!this.props.isSelected() || !doTimeline ? (null) : <>
@@ -189,10 +241,17 @@ Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBoun
     let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
     pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
     pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
+    pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField;
     pivotDoc._prevFilterIndex = ++prevFilterIndex;
     runInAction(() => {
         pivotDoc._docFilters = new List();
-        (bounds.payload as string[]).map(filterVal =>
-            Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+        const filterVals = (bounds.payload as string[]);
+        filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+        const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
+        if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
+            if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
+                pivotDoc._pivotField = filterVals[0];
+            }
+        }
     });
 });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 5cf758e9b..272773d87 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -85,7 +85,8 @@ export function computerPassLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps: any
 ) {
     const docMap = new Map<string, PoolData>();
     childPairs.forEach(({ layout, data }, i) => {
@@ -106,7 +107,8 @@ export function computerStarburstLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps: any
 ) {
     const docMap = new Map<string, PoolData>();
     const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
@@ -133,14 +135,15 @@ export function computePivotLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps: any
 ) {
     const docMap = new Map<string, PoolData>();
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     let nonNumbers = 0;
-    const pivotFieldKey = toLabel(pivotDoc._pivotField);
+    const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField);
     childPairs.map(pair => {
         const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
             Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
@@ -274,7 +277,8 @@ export function computeTimelineLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps?: any
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 038fe8272..b2ee6bbd0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -78,6 +78,7 @@ export type collectionFreeformViewProps = {
     parentActive: (outsideReaction: boolean) => boolean;
     scaleField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
+    engineProps?: any;
 };
 
 @observer
@@ -921,7 +922,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const layoutdoc = Doc.Layout(doc);
             const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
-            const cantTransform = this.props.layoutEngine?.() || this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+            const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
             const { panX, panY, scale } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
@@ -933,10 +934,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
             if (didMove) {
                 this.setPan(panX, panY, `transform ${focusSpeed}ms`, 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
-                options?.willZoom && (this.Document[this.scaleFieldKey] = scale);
+                scale && (this.Document[this.scaleFieldKey] = scale);
             }
-            Doc.BrushDoc(this.rootDoc);
-            !doc.hidden && Doc.linkFollowHighlight(doc);
+            Doc.BrushDoc(doc);
 
             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
@@ -973,6 +973,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
         const pt2 = xf.transformPoint(NumCast(doc.x) + doc[WidthSym](), NumCast(doc.y) + doc[HeightSym]());
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] };
+        const cx = NumCast(this.layoutDoc._panX);
+        const cy = NumCast(this.layoutDoc._panY);
+        const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
 
         if (scale) {
             const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
@@ -982,10 +985,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 panY: (bounds.top + bounds.bot) / 2,
                 scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])))
             };
+        } else if ((screen.right - screen.left) < (bounds.right - bounds.left)) {
+            return {
+                panX: (bounds.left + bounds.right) / 2,
+                panY: (bounds.top + bounds.bot) / 2,
+                scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1
+            };
         } else {
-            const cx = NumCast(this.layoutDoc._panX);
-            const cy = NumCast(this.layoutDoc._panY);
-            const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
             return {
                 panX: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
                 panY: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot),
@@ -1116,9 +1122,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             pivotDoc: Doc,
             childPairs: { layout: Doc, data?: Doc }[],
             panelDim: number[],
-            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]
+            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[]),
+            engineProps: any) => ViewDefResult[]
     ) {
-        return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+        return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps);
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 779da91e6..da7ed9ac7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -887,12 +887,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
                 }
                 editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
-                const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
+                const escAnchorId = anchorId[0] >= '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
                 addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" });
-                setTimeout(() => {
-                    clearStyleSheetRules(FormattedTextBox._highlightStyleSheet);
-                    this.focusSpeed = undefined;
-                }, this.focusSpeed);
+                setTimeout(() => this.focusSpeed = undefined, this.focusSpeed);
+                setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this.focusSpeed || 0, 1500));
             }
         }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 900b702c6..15c669338 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -115,7 +115,7 @@ export class FormattedTextBoxComment {
             nbef && naft && LinkDocPreview.SetLinkInfo({
                 docProps: textBox.props,
                 linkSrc: textBox.rootDoc,
-                location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
+                location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
                 hrefs,
                 showHeader: true
             });
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 9501d8021..e5d924f42 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -262,9 +262,10 @@ export class RichTextRules {
                 }),
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document 
-            // [[ <fieldKey> : <Doc>]]   
+            // [[<fieldKey> : <Doc>]]   
             // [[:Doc]] => hyperlink   
             // [[fieldKey]] => show field   
+            // [[fieldKey=value]] => show field and also set its value
             // [[fieldKey:Doc]] => show field of doc
             new InputRule(
                 new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
-- 
cgit v1.2.3-70-g09d2

