From f499698f8d8dd10c020f73528918fce41f37b4ff Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 23 Mar 2023 08:57:27 -0400
Subject: fixed pointer events for doc contents with onClickHanlders to be none
 when document or contents is selected.  fixed stackingView text boxes that
 are focused to not scroll stackingView.

---
 src/client/views/collections/CollectionStackingView.tsx | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 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 4805a748b..33d468950 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -201,6 +201,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     componentDidMount() {
         super.componentDidMount?.();
+        this.props.setContentView?.(this);
 
         // reset section headers when a new filter is inputted
         this._pivotFieldDisposer = reaction(
@@ -226,6 +227,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         this._autoHeightDisposer?.();
     }
 
+    isAnyChildContentActive = () => this.props.isAnyChildContentActive();
+
     @action
     moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
         return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
@@ -651,8 +654,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     <DocumentView
                         Document={menuDoc}
                         DataDoc={menuDoc}
-                        isContentActive={this.props.isContentActive}
-                        isDocumentActive={returnTrue}
+                        isContentActive={this.isContentActive}
+                        isDocumentActive={this.isContentActive}
                         addDocument={this.props.addDocument}
                         moveDocument={this.props.moveDocument}
                         addDocTab={this.props.addDocTab}
@@ -716,14 +719,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
                         ref={this.createRef}
                         style={{
-                            overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
+                            overflowY: this.isContentActive() ? 'auto' : 'hidden',
                             background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
-                            pointerEvents: this.backgroundEvents ? 'all' : undefined,
+                            pointerEvents: (this.props.pointerEvents?.() as any) ?? (this.backgroundEvents ? 'all' : undefined),
                         }}
                         onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
                         onDrop={this.onExternalDrop.bind(this)}
                         onContextMenu={this.onContextMenu}
-                        onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}>
+                        onWheel={e => this.isContentActive() && e.stopPropagation()}>
                         {this.renderedSections}
                         {!this.showAddAGroup ? null : (
                             <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
-- 
cgit v1.2.3-70-g09d2


From dabac69d26c8e8e69bd55466ce221d9e33e36638 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Mar 2023 14:23:17 -0400
Subject: numerous changes to try to simplify event handling in DocumentView -
 got rid of isContentActive in DocComponent since it's in DocumentView.  
 Including adding 'enableDragWhenActive' , 'onClickScriptDisable',

---
 src/Utils.ts                                       |  17 +-
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/CurrentUserUtils.ts                |  12 +-
 src/client/views/DocComponent.tsx                  |  13 --
 src/client/views/DocumentDecorations.tsx           |   8 +-
 src/client/views/InkingStroke.tsx                  |   2 +-
 src/client/views/MainView.tsx                      |   8 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +
 src/client/views/collections/TabDocView.tsx        |   4 +-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 100 +--------
 .../collectionLinear/CollectionLinearView.scss     |  10 +-
 .../collectionLinear/CollectionLinearView.tsx      |  18 +-
 .../collectionSchema/CollectionSchemaView.scss     |   3 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  46 ++--
 .../collections/collectionSchema/SchemaRowBox.tsx  |   8 +-
 src/client/views/global/globalCssVariables.scss    |   3 +-
 .../views/global/globalCssVariables.scss.d.ts      |   2 +-
 src/client/views/nodes/ColorBox.tsx                |   2 +-
 src/client/views/nodes/DocumentView.tsx            | 238 ++++-----------------
 src/client/views/nodes/FunctionPlotBox.tsx         |   2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  15 +-
 src/client/views/nodes/KeyValuePair.tsx            |   9 +-
 src/client/views/nodes/LinkBox.tsx                 |   9 +-
 src/client/views/nodes/ScriptingBox.tsx            |   4 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 src/client/views/nodes/button/FontIconBox.scss     |  10 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   9 +-
 src/client/views/nodes/trails/PresBox.tsx          |   4 +-
 src/client/views/pdf/PDFViewer.tsx                 |   9 -
 src/fields/Doc.ts                                  |   2 +-
 src/fields/ScriptField.ts                          |   2 +-
 34 files changed, 171 insertions(+), 421 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index ae1478943..0c7deaf5d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -8,8 +8,12 @@ import { Message } from './server/Message';
 import Color = require('color');
 
 export namespace Utils {
+    export let CLICK_TIME = 300;
     export let DRAG_THRESHOLD = 4;
     export let SNAP_THRESHOLD = 10;
+    export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) {
+        return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD;
+    }
 
     export function readUploadedFileAsText(inputFile: File) {
         const temporaryFileReader = new FileReader();
@@ -509,11 +513,22 @@ export function returnTrue() {
     return true;
 }
 
+export function returnAlways(): 'always' {
+    return 'always';
+}
+export function returnNever(): 'never' {
+    return 'never';
+}
+
+export function returnDefault(): 'default' {
+    return 'default';
+}
+
 export function returnFalse() {
     return false;
 }
 
-export function returnAll() {
+export function returnAll(): 'all' {
     return 'all';
 }
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2bc1b5b1d..c484db0db 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -230,6 +230,7 @@ export class DocumentOptions {
     contextMenuIcons?: List<string>;
     defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
     waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
+    enableDragWhenActive?: boolean;
     dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel)
     description?: string; // added for links
     layout?: string | Doc; // default layout string for a document
@@ -587,7 +588,7 @@ export namespace Docs {
                 DocumentType.FONTICON,
                 {
                     layout: { view: FontIconBox, dataField: 'icon' },
-                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', hideLinkButton: true, _width: 40, _height: 40 },
+                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', enableDragWhenActive: true, hideLinkButton: true, _width: 40, _height: 40 },
                 },
             ],
             [
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 814b7b072..b21f53221 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -62,7 +62,7 @@ export class CurrentUserUtils {
         const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [
             {
                 btnOpts: { title: "slide", icon: "address-card" }, 
-                templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true }, 
+                templateOpts: { _width: 400, _height: 300, title: "slideView",  _xMargin: 3, _yMargin: 3, system: true }, 
                 template: (opts:DocumentOptions) => Docs.Create.MultirowDocument(
                     [
                         Docs.Create.MulticolumnDocument([], { title: "data", _height: 200,  system: true }),
@@ -95,7 +95,7 @@ export class CurrentUserUtils {
 
         const reqdOpts:DocumentOptions = {
             title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
-            _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true,
+            _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true,
             _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
         };
         const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
@@ -184,10 +184,10 @@ export class CurrentUserUtils {
                 case DocumentType.IMG : creator = imageBox; break;
                 case DocumentType.FONTICON: creator = fontBox; break;
             }
-            const allopts = {system: true, ...opts};
+            const allopts = {system: true, onClickScriptDisable: "never",  ...opts};
             return DocUtils.AssignScripts(  (curIcon?.iconTemplate === opts.iconTemplate ? 
                       DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
-                {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)"});
+                {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
         };
         const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
             textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
@@ -314,7 +314,7 @@ export class CurrentUserUtils {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
             const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
             const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, 
-                _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, 
+                _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, 
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true,
                 _removeDropProperties: new List<string>(["_stayInCollection"]),
                 };
@@ -324,7 +324,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true,
             _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
-            childDocumentsActive: true, childDropAction: 'alias' 
+             childDropAction: 'alias' 
         };
         const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
         return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0b92fd864..9fc1487a0 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -82,12 +82,6 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
             return this.props.fieldKey;
         }
 
-        isContentActive = (outsideReaction?: boolean) =>
-            this.props.isContentActive?.() === false
-                ? false
-                : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction)
-                ? true
-                : undefined;
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -135,13 +129,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         isAnyChildContentActive = () => this._isAnyChildContentActive;
 
-        isContentActive = (outsideReaction?: boolean) =>
-            this.props.isContentActive?.() === false
-                ? false
-                : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.isAnyChildContentActive()
-                ? true
-                : undefined;
-
         lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
 
         protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2d2d3c2f6..985e6f88f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -916,9 +916,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                         pointerEvents: 'none',
                                     }}>
                                     {this._isRotating ? null : (
-                                        <div className="documentDecorations-rotation" style={{ pointerEvents: 'all' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
-                                            <IconButton icon={<FaUndo />} isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} />
-                                        </div>
+                                        <Tooltip enterDelay={750} title={<div className="dash-tooltip">tap to set rotate center, drag to rotate</div>}>
+                                            <div className="documentDecorations-rotation" style={{ pointerEvents: 'all' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
+                                                <IconButton icon={<FaUndo />} isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} />
+                                            </div>
+                                        </Tooltip>
                                     )}
                                 </div>
                                 {!this._showRotCenter ? null : (
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a085b69a5..e3642fdaf 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -473,7 +473,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                             dontRegisterView={true}
                             noSidebar={true}
                             dontScale={true}
-                            isContentActive={this.isContentActive}
+                            isContentActive={this.props.isContentActive}
                         />
                     </div>
                 )}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c84d204d5..f5adc17d0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -40,7 +40,7 @@ import { DashboardView } from './DashboardView';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
 import { GestureOverlay } from './GestureOverlay';
-import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
+import { TOPBAR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
 import { Colors } from './global/globalEnums';
 import { KeyManager } from './GlobalKeyHandler';
 import { InkTranscription } from './InkTranscription';
@@ -86,7 +86,7 @@ export class MainView extends React.Component {
         return this._hideUI ? 0 : 27;
     } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
     @computed private get topOfDashUI() {
-        return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
+        return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(TOPBAR_HEIGHT.replace('px', ''));
     }
     @computed private get topOfHeaderBarDoc() {
         return this.topOfDashUI;
@@ -872,7 +872,7 @@ export class MainView extends React.Component {
 
     @computed get docButtons() {
         return !Doc.MyDockedBtns ? null : (
-            <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !Doc.MyDockedBtns.linearViewIsExpanded ? '42px' : undefined }}>
+            <div className="mainView-docButtons" ref={this._docBtnRef}>
                 <CollectionLinearView
                     Document={Doc.MyDockedBtns}
                     DataDoc={undefined}
@@ -910,7 +910,7 @@ export class MainView extends React.Component {
         );
     }
     @computed get snapLines() {
-        return !SnappingManager.GetShowSnapLines() ? null : (
+        return !SelectionManager.Views().some(dv => dv.rootDoc.showSnapLines) ? null : (
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
                     {SnappingManager.horizSnapLines().map(l => (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1b5eb3342..faaa4e1f9 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -89,6 +89,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const isAnchor = property.includes(':anchor');
     const isAnnotated = property.includes(':annotated');
     const isOpen = property.includes(':open');
+    const boxBackground = property.includes(':box');
     const fieldKey = props?.fieldKey ? props.fieldKey + '-' : isCaption ? 'caption-' : '';
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
@@ -200,7 +201,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                     docColor = docColor || (darkScheme() ? 'transparent' : 'transparent');
                     break;
                 case DocumentType.FONTICON:
-                    docColor = docColor || Colors.DARK_GRAY;
+                    docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY;
                     break;
                 case DocumentType.RTF:
                     docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY);
@@ -289,6 +290,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         }
         case StyleProp.PointerEvents:
             const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name);
+            if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
             if (MainView.Instance._exploreMode || doc?.unrendered) return isInk ? 'visiblePainted' : 'all';
             if (doc?.pointerEvents) return StrCast(doc.pointerEvents);
             if (props?.contentPointerEvents) return StrCast(props.contentPointerEvents);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 33d468950..64ec419ec 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -325,6 +325,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 fitWidth={this.props.childFitWidth}
                 isContentActive={doc.isLinkButton ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
+                onBrowseClick={this.props.onBrowseClick}
                 isDocumentActive={this.isContentActive}
                 LayoutTemplate={this.props.childLayoutTemplate}
                 LayoutTemplateString={this.props.childLayoutString}
@@ -659,6 +660,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         addDocument={this.props.addDocument}
                         moveDocument={this.props.moveDocument}
                         addDocTab={this.props.addDocTab}
+                        onBrowseClick={this.props.onBrowseClick}
                         pinToPres={emptyFunction}
                         rootSelected={this.props.isSelected}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 5e6849e7c..93b816581 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -10,7 +10,6 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
-import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
@@ -28,6 +27,7 @@ import { LightboxView } from '../LightboxView';
 import { MainView } from '../MainView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
+import { KeyValueBox } from '../nodes/KeyValueBox';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { CollectionDockingView } from './CollectionDockingView';
@@ -35,7 +35,6 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import React = require('react');
-import { KeyValueBox } from '../nodes/KeyValueBox';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -429,6 +428,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     ContainingCollectionDoc={undefined}
                     onBrowseClick={MainView.Instance.exploreMode}
                     isContentActive={returnTrue}
+                    isDocumentActive={returnFalse}
                     PanelWidth={this.PanelWidth}
                     PanelHeight={this.PanelHeight}
                     styleProvider={DefaultStyleProvider}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 257428d56..99b7549c0 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -573,6 +573,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         </div>
                     )}
                     <ul
+                        style={{ cursor: 'inherit' }}
                         key={expandKey + 'more'}
                         title="click to change sort order"
                         className={''} //this.doc.treeViewHideTitle ? 'no-indent' : ''}
@@ -629,7 +630,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             );
         } else if (this.treeViewExpandedView === 'fields') {
             return (
-                <ul key={this.doc[Id] + this.doc.title}>
+                <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}>
                     <div>{this.expandedField}</div>
                 </ul>
             );
@@ -903,7 +904,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                 hideDecorationTitle={this.props.treeView.outlineMode}
                 hideResizeHandles={this.props.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
-                onClickScriptDisable="never"
+                enableDragWhenActive={true}
+                onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
                 docViewPath={returnEmptyDoclist}
                 treeViewDoc={this.props.treeView.props.Document}
                 addDocument={undefined}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8104ab1a7..aed3683d4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -821,9 +821,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false;
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             Doc.ActiveTool = InkTool.None;
-            if (this.props.isContentActive(true)) e.stopPropagation();
         } else {
-            this.props.DocumentView?.().docView?.cancelMoveEvents();
             if (this.tryDragCluster(e, this._hitCluster)) {
                 return true;
             }
@@ -955,96 +953,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return tVals;
     };
 
-    handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        if (!e.cancelBubble) {
-            const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-            if (myTouches[0]) {
-                if (Doc.ActiveTool === InkTool.None) {
-                    if (this.tryDragCluster(e, this._hitCluster)) {
-                        e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-                        e.preventDefault();
-                        document.removeEventListener('pointermove', this.onPointerMove);
-                        return;
-                    }
-                    // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
-                    this.pan(myTouches[0]);
-                }
-            }
-            // e.stopPropagation();
-            e.preventDefault();
-        }
-    };
-
-    handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        // pinch zooming
-        if (!e.cancelBubble) {
-            const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-            const pt1 = myTouches[0];
-            const pt2 = myTouches[1];
-
-            if (this.prevPoints.size === 2) {
-                const oldPoint1 = this.prevPoints.get(pt1.identifier);
-                const oldPoint2 = this.prevPoints.get(pt2.identifier);
-                if (oldPoint1 && oldPoint2) {
-                    const dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2);
-
-                    // if zooming, zoom
-                    if (dir !== 0) {
-                        const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2));
-                        const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2));
-                        const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
-                        const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-
-                        // calculate the raw delta value
-                        const rawDelta = dir * (d1 + d2);
-
-                        // this floors and ceils the delta value to prevent jitteriness
-                        const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
-                        this.zoom(centerX, centerY, delta * window.devicePixelRatio);
-                        this.prevPoints.set(pt1.identifier, pt1);
-                        this.prevPoints.set(pt2.identifier, pt2);
-                    }
-                    // this is not zooming. derive some form of panning from it.
-                    else {
-                        // use the centerx and centery as the "new mouse position"
-                        const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
-                        const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                        // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
-
-                        this._lastX = centerX;
-                        this._lastY = centerY;
-                    }
-                }
-            }
-            // e.stopPropagation();
-            e.preventDefault();
-        }
-    };
-
-    @action
-    handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
-        if (this.props.isContentActive(true)) {
-            // const pt1: React.Touch | null = e.targetTouches.item(0);
-            // const pt2: React.Touch | null = e.targetTouches.item(1);
-            // // if (!pt1 || !pt2) return;
-            const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-            const pt1 = myTouches[0];
-            const pt2 = myTouches[1];
-            if (pt1 && pt2) {
-                const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
-                const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                this._lastX = centerX;
-                this._lastY = centerY;
-
-                this.removeMoveListeners();
-                this.addMoveListeners();
-                this.removeEndListeners();
-                this.addEndListeners();
-                e.stopPropagation();
-            }
-        }
-    };
-
     cleanUpInteractions = () => {
         this.removeMoveListeners();
         this.removeEndListeners();
@@ -1637,7 +1545,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             } else {
                 const canvas = oldDiv;
                 const img = document.createElement('img'); // create a Image Element
-                img.src = canvas.toDataURL(); //image source
+                try {
+                    img.src = canvas.toDataURL(); //image source
+                } catch (e) {
+                    console.log(e);
+                }
                 img.style.width = canvas.style.width;
                 img.style.height = canvas.style.height;
                 const newCan = newDiv as HTMLCanvasElement;
@@ -2265,6 +2177,8 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
         ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
         Doc.linkFollowHighlight(dv?.props.Document, false);
+    } else {
+        DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true });
     }
 }
 ScriptingGlobals.add(CollectionBrowseClick);
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 521bcda1e..3e3709827 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,9 +1,13 @@
 @import '../../global/globalCssVariables';
 @import '../../_nodeModuleOverrides';
 
+.collectionLinearView {
+    width: 100%;
+}
 .collectionLinearView-label {
     color: black;
     background-color: $light-gray;
+    width: 100%;
 }
 .collectionLinearView-outer {
     overflow: visible;
@@ -15,8 +19,6 @@
     }
 
     &.true {
-        padding-left: 5px;
-        padding-right: 5px;
         border-left: $standard-border;
         background-color: $medium-blue-alt;
     }
@@ -29,7 +31,6 @@
         display: flex;
         height: 100%;
         align-items: center;
-        gap: 5px;
 
         .collectionView {
             overflow: visible !important;
@@ -101,13 +102,12 @@
             background-color: $medium-blue;
             padding: 5;
             border-radius: 2px;
-            height: 25;
+            height: 100%;
             min-width: 25;
             margin: 0;
             color: $white;
             display: flex;
             font-weight: 100;
-            width: fit-content;
             transition: transform 0.2s;
             align-items: center;
             justify-content: center;
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index d54e8ce98..a062c65fc 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { StylesProvider, Tooltip } from '@material-ui/core';
+import { Tooltip } from '@material-ui/core';
 import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -11,8 +11,6 @@ import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
-import { Colors, Shadows } from '../../global/globalEnums';
-import { media_state } from '../../nodes/AudioBox';
 import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { DocumentView } from '../../nodes/DocumentView';
 import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
@@ -46,7 +44,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10),
+            () => 5 + this.dimension() + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[WidthSym]() || this.dimension()) + tot + 4, 0) : 0),
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
@@ -180,10 +178,10 @@ export class CollectionLinearView extends CollectionSubView() {
                 ref={r => (dref = r || undefined)}
                 style={{
                     pointerEvents: 'all',
-                    width: nested ? undefined : NumCast(doc._width),
-                    height: nested ? undefined : NumCast(doc._height),
-                    marginLeft: !nested ? 2.5 : 0,
-                    marginRight: !nested ? 2.5 : 0,
+                    width: NumCast(doc._width),
+                    height: NumCast(doc._height),
+                    marginLeft: 2,
+                    marginRight: 2,
                     // width: NumCast(pair.layout._width),
                     // height: NumCast(pair.layout._height),
                 }}>
@@ -199,7 +197,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     rootSelected={this.props.isSelected}
                     removeDocument={this.props.removeDocument}
                     ScreenToLocalTransform={docXf}
-                    PanelWidth={nested ? doc[WidthSym] : this.dimension}
+                    PanelWidth={doc[WidthSym]}
                     PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension}
                     renderDepth={this.props.renderDepth + 1}
                     dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)}
@@ -236,7 +234,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
         return (
             <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: this.layoutDoc.linearViewIsExpanded ? undefined : 'transparent' }}>
-                <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu}>
+                <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension() }}>
                     {!this.props.Document.linearViewExpandable ? null : (
                         <Tooltip title={<div className="dash-tooltip">{isExpanded ? 'Close' : 'Open'}</div>} placement="top">
                             {menuOpener}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 34e591195..1ef2fb4ef 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -8,7 +8,8 @@
 
     .schema-table {
         background-color: $white;
-        cursor: default;
+        cursor: grab;
+        overflow: scroll;
 
         .schema-column-menu,
         .schema-filter-menu {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 60202a19e..d47c9762c 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
-import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
+import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
@@ -46,7 +46,7 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     public static _rowHeight: number = 40;
     public static _minColWidth: number = 25;
-    public static _rowMenuWidth: number = 100;
+    public static _rowMenuWidth: number = 60;
     public static _previewDividerWidth: number = 4;
 
     @computed get _selectedDocs() {
@@ -364,7 +364,11 @@ export class CollectionSchemaView extends CollectionSubView() {
             this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]);
             this.setSort(undefined);
             SelectionManager.DeselectAll();
-            setTimeout(() => draggedDocs.forEach(doc => DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true))), 100);
+            draggedDocs.forEach(doc => {
+                const draggedView = DocumentManager.Instance.getFirstDocumentView(doc);
+                if (draggedView) DocumentManager.Instance.RemoveView(draggedView);
+                DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true));
+            });
             e.stopPropagation();
             return true;
         }
@@ -812,26 +816,17 @@ export class CollectionSchemaView extends CollectionSubView() {
     previewWidthFunc = () => this.previewWidth;
     render() {
         return (
-            <div
-                className="collectionSchemaView"
-                ref={(ele: HTMLDivElement | null) => {
-                    this.createDashEventsTarget(ele);
-                }}
-                onPointerDown={e => {
-                    // this is analogous to the panning code for a freeform view.
-                    // however, schema views don't pan so it does nothing.  but it does eat the pointerDown event
-                    // if the content is active to prevent the schema from being dragged
-                    this.isContentActive() && setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false);
-                }}
-                onDrop={this.onExternalDrop.bind(this)}>
-                <div className="schema-table">
+            <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
+                <div
+                    className="schema-table"
+                    onWheel={e => this.props.isContentActive() && e.stopPropagation()}
+                    ref={r => {
+                        // prevent wheel events from passively propagating up through containers
+                        r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false });
+                    }}>
                     <div className="schema-header-row" style={{ height: CollectionSchemaView._rowHeight }}>
                         <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
-                            <div
-                                className="schema-header-button"
-                                onPointerDown={e => {
-                                    this._columnMenuIndex && this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true);
-                                }}>
+                            <div className="schema-header-button" onPointerDown={e => (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}>
                                 <FontAwesomeIcon icon="plus" />
                             </div>
                         </div>
@@ -867,8 +862,9 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 DataDoc={undefined}
                                 fitContentsToBox={returnTrue}
                                 dontCenter={'y'}
+                                onClickScriptDisable="always"
                                 focus={DocUtils.DefaultFocus}
-                                renderDepth={this.props.renderDepth}
+                                renderDepth={this.props.renderDepth + 1}
                                 rootSelected={this.rootSelected}
                                 PanelWidth={this.previewWidthFunc}
                                 PanelHeight={this.props.PanelHeight}
@@ -915,16 +911,22 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                     return (
                         <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
                             <DocumentView
+                                key={doc[Id]}
                                 {...this.props.schema.props}
                                 LayoutTemplate={this.props.schema.props.childLayoutTemplate}
                                 LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
                                 Document={doc}
                                 DataDoc={dataDoc}
+                                renderDepth={this.props.schema.props.renderDepth + 1}
                                 ContainingCollectionView={this.props.schema.props.CollectionView}
                                 ContainingCollectionDoc={this.props.schema.Document}
                                 PanelWidth={this.tableWidthFunc}
                                 PanelHeight={this.rowHeightFunc}
                                 styleProvider={DefaultStyleProvider}
+                                waitForDoubleClickToClick={returnNever}
+                                defaultDoubleClick={returnDefault}
+                                enableDragWhenActive={true}
+                                onClickScriptDisable="always"
                                 focus={this.props.schema.focusDocument}
                                 docFilters={this.props.schema.childDocFilters}
                                 docRangeFilters={this.props.schema.childDocRangeFilters}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 10f307068..2defaae00 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -88,11 +88,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div
                 className="schema-row"
-                style={
-                    this.props.isSelected()
-                        ? { height: CollectionSchemaView._rowHeight, backgroundColor: Colors.LIGHT_BLUE, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined /*, opacity: this.props.dragging ? 0.5 : 1 */ }
-                        : { height: CollectionSchemaView._rowHeight, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }
-                }
+                style={{ height: CollectionSchemaView._rowHeight, backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }}
                 onPointerEnter={this.onPointerEnter}
                 onPointerLeave={this.onPointerLeave}
                 ref={(row: HTMLDivElement | null) => {
@@ -103,7 +99,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     className="row-menu"
                     style={{
                         width: CollectionSchemaView._rowMenuWidth,
-                        pointerEvents: !this.isContentActive() ? 'none' : undefined,
+                        pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
                     }}>
                     <div
                         className="schema-row-button"
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 430a36dce..3496bb835 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -59,7 +59,6 @@ $standard-border-radius: 3px;
 // shadow
 $standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
 
-$dashboardselector-height: 32px;
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
@@ -79,7 +78,7 @@ $TREE_BULLET_WIDTH: 20px;
     MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
     SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
     ANTIMODEMENU_HEIGHT: $antimodemenu-height;
-    DASHBOARD_SELECTOR_HEIGHT: $dashboardselector-height;
+    TOPBAR_HEIGHT: $topbar-height;
     DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
     LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
     TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 3375579d6..537ea1e5d 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -6,7 +6,7 @@ interface IGlobalScss {
     MAX_ROW_HEIGHT: string;
     SEARCH_THUMBNAIL_SIZE: string;
     ANTIMODEMENU_HEIGHT: string;
-    DASHBOARD_SELECTOR_HEIGHT: string;
+    TOPBAR_HEIGHT: string;
     DFLT_IMAGE_NATIVE_DIM: string;
     LEFT_MENU_WIDTH: string;
     TREE_BULLET_WIDTH: string;
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index c229a966a..70ba7e182 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -51,7 +51,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const scaling = Math.min(this.layoutDoc.fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]());
         return (
             <div
-                className={`colorBox-container${this.isContentActive() ? '-interactive' : ''}`}
+                className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
                 onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
                 onClick={e => e.stopPropagation()}
                 style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 541dddd83..9a3e77e6e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,7 +5,6 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from 'mobx-react';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
-import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -15,7 +14,7 @@ import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnNone, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -50,11 +49,9 @@ import { FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { RadialMenu } from './RadialMenu';
-import { ScriptingBox } from './ScriptingBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
-import { KeyValueBox } from './KeyValueBox';
 const { Howl } = require('howler');
 
 interface Window {
@@ -135,6 +132,7 @@ export interface DocComponentView {
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     incrementalRendering?: () => void;
     fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
+    overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
@@ -195,6 +193,9 @@ export interface DocumentViewSharedProps {
     forceAutoHeight?: boolean;
     disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
+    enableDragWhenActive?: boolean;
+    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
+    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     createNewFilterDoc?: () => void;
@@ -255,8 +256,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _downX: number = 0;
     private _downY: number = 0;
     private _downTime: number = 0;
-    private _firstX: number = -1;
-    private _firstY: number = -1;
     private _lastTap: number = 0;
     private _doubleTap = false;
     private _mainCont = React.createRef<HTMLDivElement>();
@@ -333,11 +332,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this.props.NativeHeight();
     }
     @computed get disableClickScriptFunc() {
+        const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
+        // prettier-ignore
         return (
             DocumentView.LongPress ||
-            this.props.onClickScriptDisable === 'always' ||
-            this._componentView?.onClickScriptDisable?.() ||
-            (this.props.onClickScriptDisable !== 'never' && (this.rootSelected() || this.props.isSelected())) ||
+            onScriptDisable === 'always' ||
+            (onScriptDisable !== 'never' && (this.rootSelected() || this.props.isSelected())) ||
             this._componentView?.isAnyChildContentActive?.()
         );
     }
@@ -360,7 +360,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     componentDidMount() {
         this.setupHandlers();
     }
-    //componentDidUpdate() { this.setupHandlers(); }
+
     setupHandlers() {
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
@@ -378,151 +378,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
-    handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
-        this.removeMoveListeners();
-        this.removeEndListeners();
-        document.removeEventListener('pointermove', this.onPointerMove);
-        document.removeEventListener('pointerup', this.onPointerUp);
-        if (RadialMenu.Instance._display === false) {
-            this.addHoldMoveListeners();
-            this.addHoldEndListeners();
-            this.onRadialMenu(e, me);
-            const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
-            this._firstX = pt.pageX;
-            this._firstY = pt.pageY;
-        }
-    };
-
-    handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
-
-        if (this._firstX === -1 || this._firstY === -1) {
-            return;
-        }
-        if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
-            this.handle1PointerHoldEnd(e, me);
-        }
-    };
-
-    handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        this.removeHoldMoveListeners();
-        this.removeHoldEndListeners();
-        RadialMenu.Instance.closeMenu();
-        this._firstX = -1;
-        this._firstY = -1;
-        SelectionManager.DeselectAll();
-        me.touchEvent.stopPropagation();
-        me.touchEvent.preventDefault();
-        e.stopPropagation();
-        if (RadialMenu.Instance.used) {
-            this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY);
-        }
-    };
-
-    handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
-        if (!this.props.isSelected()) {
-            e.stopPropagation();
-            e.preventDefault();
-
-            this.removeMoveListeners();
-            this.addMoveListeners();
-            this.removeEndListeners();
-            this.addEndListeners();
-        }
-    };
-
-    handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
-        SelectionManager.DeselectAll();
-        if (this.Document.onPointerDown) return;
-        const touch = me.touchEvent.changedTouches.item(0);
-        if (touch) {
-            this._downX = touch.clientX;
-            this._downY = touch.clientY;
-            if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
-                e.stopPropagation();
-            }
-            this.removeMoveListeners();
-            this.addMoveListeners();
-            this.removeEndListeners();
-            this.addEndListeners();
-            e.stopPropagation();
-        }
-    };
-
-    handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        if (e.cancelBubble && this.props.isDocumentActive?.()) {
-            this.removeMoveListeners();
-        } else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
-            const touch = me.touchEvent.changedTouches.item(0);
-            if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
-                if (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) {
-                    this.cleanUpInteractions();
-                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined);
-                }
-            }
-            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-            e.preventDefault();
-        }
-    };
-
-    @action
-    handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
-        const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-        const pt1 = myTouches[0];
-        const pt2 = myTouches[1];
-        const oldPoint1 = this.prevPoints.get(pt1.identifier);
-        const oldPoint2 = this.prevPoints.get(pt2.identifier);
-        const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
-        if (pinching !== 0 && oldPoint1 && oldPoint2) {
-            const dW = Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX);
-            const dH = Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY);
-            const dX = -1 * Math.sign(dW);
-            const dY = -1 * Math.sign(dH);
-
-            if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
-                const doc = Document(this.props.Document);
-                const layoutDoc = Document(Doc.Layout(this.props.Document));
-                let nwidth = Doc.NativeWidth(layoutDoc);
-                let nheight = Doc.NativeHeight(layoutDoc);
-                const width = layoutDoc._width || 0;
-                const height = layoutDoc._height || (nheight / nwidth) * width;
-                const scale = this.props.ScreenToLocalTransform().Scale * this.NativeDimScaling;
-                const actualdW = Math.max(width + dW * scale, 20);
-                const actualdH = Math.max(height + dH * scale, 20);
-                doc.x = (doc.x || 0) + dX * (actualdW - width);
-                doc.y = (doc.y || 0) + dY * (actualdH - height);
-                const fixedAspect = e.ctrlKey || (nwidth && nheight);
-                if (fixedAspect && (!nwidth || !nheight)) {
-                    Doc.SetNativeWidth(layoutDoc, (nwidth = layoutDoc._width || 0));
-                    Doc.SetNativeHeight(layoutDoc, (nheight = layoutDoc._height || 0));
-                }
-                if (nwidth > 0 && nheight > 0) {
-                    if (Math.abs(dW) > Math.abs(dH)) {
-                        if (!fixedAspect) {
-                            Doc.SetNativeWidth(layoutDoc, (actualdW / (layoutDoc._width || 1)) * Doc.NativeWidth(layoutDoc));
-                        }
-                        layoutDoc._width = actualdW;
-                        if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = (nheight / nwidth) * layoutDoc._width;
-                        else layoutDoc._height = actualdH;
-                    } else {
-                        if (!fixedAspect) {
-                            Doc.SetNativeHeight(layoutDoc, (actualdH / (layoutDoc._height || 1)) * Doc.NativeHeight(doc));
-                        }
-                        layoutDoc._height = actualdH;
-                        if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = (nwidth / nheight) * layoutDoc._height;
-                        else layoutDoc._width = actualdW;
-                    }
-                } else {
-                    dW && (layoutDoc._width = actualdW);
-                    dH && (layoutDoc._height = actualdH);
-                    dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
-                }
-            }
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    };
-
     @action
     onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
@@ -591,11 +446,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
-        if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+        if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
             (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
             if (this._doubleTap) {
+                const defaultDblclick = this.props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
                 if (this.onDoubleClickHandler?.script) {
                     const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
                     // prettier-ignore
@@ -609,7 +465,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             value: undefined,
                         }, console.log  );
                     UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
-                } else if (!Doc.IsSystem(this.rootDoc) && (this.Document.defaultDoubleClick === undefined || this.Document.defaultDoubleClick === 'default')) {
+                } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
                     UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
@@ -649,15 +505,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         LinkFollower.FollowLink(undefined, this.Document, e.altKey);
                     };
                 } else {
+                    // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     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 & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                         stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                     }
                     preventDefault = false;
                 }
 
                 this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey));
-                if ((clickFunc && this.Document.waitForDoubleClickToClick !== 'never') || this.Document.waitForDoubleClickToClick === 'always') {
+                const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
+                if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
                     this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
                 } else {
@@ -673,55 +530,46 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000);
-        if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView();
-        if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return;
-        // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
-        if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool))) {
-            if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-                e.stopPropagation();
-                if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
-                // TODO: check here for panning/inking
-            }
-            return;
-        }
+        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView();
+
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._downTime = Date.now();
         if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
-            // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
+            // click events stop here if the document is active and no modes are overriding it
+            // if this is part of a template, let the event go up to the template root unless right/ctrl clicking
             if (
-                (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
+                // prettier-ignore
+                this.props.isDocumentActive?.() &&
                 !this.props.onBrowseClick?.() &&
                 !this.Document.ignoreClick &&
                 !e.ctrlKey &&
-                (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
+                e.button === 0 &&
+                this.pointerEvents !== 'none' &&
                 !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)
             ) {
                 e.stopPropagation();
                 // don't preventDefault anymore.  Goldenlayout, PDF text selection and RTF text selection all need it to go though
                 //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
+
+                // listen to move events if document content isn't active or document is draggable
+                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.enableDragWhenActive || this.rootDoc.enableDragWhenActive)) {
+                    document.addEventListener('pointermove', this.onPointerMove);
+                }
             }
-            if (this.props.isDocumentActive?.()) {
-                document.removeEventListener('pointermove', this.onPointerMove);
-                document.addEventListener('pointermove', this.onPointerMove);
-            }
-            document.removeEventListener('pointerup', this.onPointerUp);
             document.addEventListener('pointerup', this.onPointerUp);
         }
     };
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (this.layoutDoc._lockedPosition || DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) return;
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
 
-        if (((!this.topMost && this.props.isDocumentActive?.()) || this.layoutDoc.onDragStart) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
+        if (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 this.cleanupPointerEvents();
                 this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
             }
-            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
-            e.preventDefault();
         }
     };
 
@@ -738,13 +586,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.cleanupPointerEvents();
         this._longPressSelector && clearTimeout(this._longPressSelector);
 
-        const now = Date.now();
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
-        } else if (now - this._downTime < 300) {
-            this._doubleTap = now - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2;
-            // bcz: this is a placeholder.  documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
-            if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
+        } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+            this._doubleTap = Date.now() - this._lastTap < Utils.CLICK_TIME;
+            if (!this.props.isSelected(true)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
         if (DocumentView.LongPress) e.preventDefault();
     };
@@ -1386,11 +1232,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>
         );
     }
-    @observable _: string = '';
+
     renderDoc = (style: object) => {
         TraceMobx();
         const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
-        const isButton = this.props.Document.type === DocumentType.FONTICON;
+        const background = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
         if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
         return (
             this.docContents ?? (
@@ -1399,7 +1245,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     id={this.props.Document[Id]}
                     style={{
                         ...style,
-                        background: isButton || thumb ? undefined : this.backgroundColor,
+                        background: thumb ? undefined : background,
                         opacity: this.opacity,
                         cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
                         color: StrCast(this.layoutDoc.color, 'inherit'),
@@ -1809,7 +1655,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         TraceMobx();
         const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
         const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
-        const isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
 
         return this.hidden ? null : (
             <div
@@ -1831,12 +1676,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         ref={this.ContentRef}
                         style={{
                             transition: this.props.dataTransition,
-                            transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                            width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
-                            height:
-                                isButton || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.props.forceAutoHeight
-                                    ? undefined
-                                    : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
+                            transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
+                            width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
+                            height: this.props.forceAutoHeight
+                                ? undefined
+                                : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
                         }}>
                         <DocumentViewInternal
                             {...this.props}
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 75b4907cd..b43e359ff 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -101,7 +101,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
             <div
                 ref={this.createDropTarget}
                 style={{
-                    pointerEvents: !this.isContentActive() ? 'all' : undefined,
+                    pointerEvents: !this.props.isContentActive() ? 'all' : undefined,
                     width: this.props.PanelWidth(),
                     height: this.props.PanelHeight(),
                 }}>
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9c31ed3e4..57018fb93 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -4,8 +4,9 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, DocCast, NumCast } from '../../../fields/Types';
+import { DocCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
+import { returnAll, returnAlways, returnTrue } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
 import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
@@ -20,7 +21,6 @@ import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
 import React = require('react');
 import e = require('express');
-import { returnTrue } from '../../../Utils';
 
 export type KVPScript = {
     script: CompiledScript;
@@ -42,8 +42,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     componentDidMount() {
         this.props.setContentView?.(this);
     }
-    onClickScriptDisable: () => 'always' = () => 'always';
+    reverseNativeScaling = returnTrue;
+    able = returnAlways;
     fitWidth = returnTrue;
+    overridePointerEvents = returnAll;
+    onClickScriptDisable = returnAlways;
 
     @observable private rows: KeyValuePair[] = [];
 
@@ -69,9 +72,9 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     };
     public static CompileKVPScript(value: string): KVPScript | undefined {
         const eq = value.startsWith('=');
-        value = eq ? value.substr(1) : value;
-        const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith(';=') ? 'script' : false;
-        value = dubEq ? value.substr(2) : value;
+        value = eq ? value.substring(1) : value;
+        const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false;
+        value = dubEq ? value.substring(2) : value;
         const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false };
         if (dubEq) options.typecheck = false;
         const script = CompileScript(value, options);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index c4adc7f1a..94434dce7 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,19 +1,18 @@
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, Field, Opt } from '../../../fields/Doc';
-import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Doc, Field } from '../../../fields/Doc';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
+import { DefaultStyleProvider } from '../StyleProvider';
+import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
 import React = require('react');
-import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
 
 // Represents one row in a key value plane
 
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 470f7e803..46ccdecae 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,6 +1,6 @@
 import React = require('react');
 import { observer } from 'mobx-react';
-import { emptyFunction, returnFalse, returnTrue } from '../../../Utils';
+import { emptyFunction, returnAlways, returnFalse, returnTrue } from '../../../Utils';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { ComparisonBox } from './ComparisonBox';
@@ -12,23 +12,22 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
-    isContentActiveFunc = () => this.isContentActive();
 
-    onClickScriptDisable: () => 'always' = () => 'always';
+    onClickScriptDisable = returnAlways;
     componentDidMount() {
         this.props.setContentView?.(this);
     }
     render() {
         if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true));
         return (
-            <div className={`linkBox-container${this.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
+            <div className={`linkBox-container${this.props.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
                     {...this.props}
                     fieldKey="anchor"
                     setHeight={emptyFunction}
                     dontRegisterView={true}
                     renderDepth={this.props.renderDepth + 1}
-                    isContentActive={this.isContentActiveFunc}
+                    isContentActive={this.props.isContentActive}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     moveDocument={returnFalse}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 5683b0fe7..f09962b22 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -8,7 +8,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnEmptyString, returnTrue } from '../../../Utils';
+import { returnAlways, returnEmptyString, returnTrue } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
@@ -114,7 +114,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         }
     }
 
-    onClickScriptDisable: () => 'always' = () => 'always';
+    onClickScriptDisable = returnAlways;
 
     @action
     componentDidMount() {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 73283263f..d57518a8d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -876,7 +876,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
     @computed get content() {
-        const interactive = this.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
+        const interactive = this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
         return (
             <div className={'webBox-cont' + (interactive ? '-interactive' : '')} onKeyDown={e => e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}>
                 {this.urlContent}
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
index 7fe1436c7..f3b43501b 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -158,7 +158,7 @@
         width: 100%;
         border-radius: 100%;
         flex-direction: column;
-        margin-top: -4px;
+        // margin-top: -4px;
 
         svg {
             width: 60% !important;
@@ -427,11 +427,11 @@
     }
 
     .dropbox-background {
-        width: 100vw;
-        height: 100vh;
-        top: 0;
+        width: 200vw;
+        height: 200vh;
+        top: -100vh;
         z-index: 20;
-        left: 0;
+        left: -100vw;
         background: transparent;
         position: fixed;
     }
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 468bcc4d8..28e6eaf1c 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -618,11 +618,10 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void; setMode?: () => void }> = new Map([
+    const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([
         ['font', {
             checkResult: () => RichTextMenu.Instance?.fontFamily,
             setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
-            setMode: () => Doc.UserDoc().textAlign = value,
         }],
         ['highlight', {
             checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
@@ -645,8 +644,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     if (checkResult) {
         return map.get(attr)?.checkResult();
     }
-    if (editorView?.state) map.get(attr)?.setDoc();
-    else map.get(attr)?.setMode?.();
+    map.get(attr)?.setDoc?.();
 });
 
 type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3ce2366f8..ee4249b02 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1470,20 +1470,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 //      but that's changed, so this shouldn't be needed.
                 //e.stopPropagation();  // if the text box is selected, then it consumes all down events
                 document.addEventListener('pointerup', this.onSelectEnd);
-                document.addEventListener('pointermove', this.onSelectMove);
             }
         }
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
         }
     };
-    onSelectMove = (e: PointerEvent) => {
-        this.props.DocumentView?.().docView?.cancelMoveEvents();
-        e.stopPropagation();
-    };
     onSelectEnd = (e: PointerEvent) => {
         document.removeEventListener('pointerup', this.onSelectEnd);
-        document.removeEventListener('pointermove', this.onSelectMove);
     };
     onPointerUp = (e: React.PointerEvent): void => {
         const editor = this._editorView!;
@@ -1534,6 +1528,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @observable public static Focused: FormattedTextBox | undefined;
 
     onClick = (e: React.MouseEvent): void => {
+        if (!this.props.isContentActive()) return;
         if ((e.nativeEvent as any).handledByInnerReactInstance) {
             e.stopPropagation();
             return;
@@ -1876,7 +1871,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     r?.addEventListener(
                         'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
                         (e: WheelEvent) => {
-                            if (this.isContentActive()) {
+                            if (this.props.isContentActive()) {
                                 if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault();
                                 e.stopPropagation();
                             }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e79e7472a..3376c29a9 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1027,8 +1027,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
     getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this.props.PanelHeight() - 40;
-    isContentActive = (outsideReaction?: boolean) =>
-        Doc.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false;
+    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(outsideReaction);
+    //.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false;
 
     /**
      * For sorting the array so that the order is maintained when it is dropped.
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index eb3087399..c5060a2c2 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -381,7 +381,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             const target = e.target as any;
             if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
                 this._textSelecting = false;
-                document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
             } else {
                 // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
                 setTimeout(
@@ -391,7 +390,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
                 this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
                 document.addEventListener('pointerup', this.onSelectEnd);
-                document.addEventListener('pointermove', this.onSelectMove);
             }
         }
     };
@@ -401,12 +399,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this.isAnnotating = false;
         this._marqueeing = undefined;
         this._textSelecting = true;
-        document.removeEventListener('pointermove', this.onSelectMove);
-    };
-
-    onSelectMove = (e: PointerEvent) => {
-        this.props.DocumentView?.().docView?.cancelMoveEvents();
-        e.stopPropagation();
     };
 
     @action
@@ -414,7 +406,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this.isAnnotating = false;
         clearStyleSheetRules(PDFViewer._annotationStyle);
         this.props.select(false);
-        document.removeEventListener('pointermove', this.onSelectMove);
         document.removeEventListener('pointerup', this.onSelectEnd);
 
         const sel = window.getSelection();
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4d82551db..553c4525c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -31,7 +31,7 @@ export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
         const onDelegate = Object.keys(doc).includes(key);
         const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-        return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
+        return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
     }
     export function toScriptString(field: Field): string {
         switch (typeof field) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index feb419597..2b8750714 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -114,7 +114,7 @@ export class ScriptField extends ObjectField {
     }
 
     [ToScriptString]() {
-        return 'script field';
+        return this.script.originalScript;
     }
     [ToString]() {
         return this.script.originalScript;
-- 
cgit v1.2.3-70-g09d2


From e4b76f6c1dee63c49408c48d7b2bc98e62dc813a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Apr 2023 12:19:01 -0400
Subject: adjusted general context menu items to always appear last.  cleaned
 up makeLink api,

---
 src/client/documents/Documents.ts                  | 32 +++++-----
 src/client/util/DragManager.ts                     |  1 -
 src/client/util/LinkFollower.ts                    |  2 -
 src/client/views/ContextMenu.tsx                   | 12 ++--
 src/client/views/DocumentButtonBar.tsx             |  4 +-
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/SidebarAnnos.tsx                  |  2 +-
 .../views/collections/CollectionNoteTakingView.tsx |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 28 ++++-----
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 26 ++------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 70 +++++++++++++++-------
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/PDFBox.tsx                  | 31 +++++-----
 src/client/views/nodes/VideoBox.tsx                |  6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 src/client/views/search/SearchBox.tsx              |  2 +-
 21 files changed, 117 insertions(+), 118 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9cb480c4a..5623ca218 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -470,7 +470,7 @@ export namespace Docs {
                 DocumentType.AUDIO,
                 {
                     layout: { view: AudioBox, dataField: defaultDataKey },
-                    options: { _height: 100, forceReflow: true, nativeDimModifiable: true },
+                    options: { _height: 100, fitWidth: true, forceReflow: true, nativeDimModifiable: true },
                 },
             ],
             [
@@ -934,13 +934,13 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
         }
 
-        export function LinkDocument(source: { doc: Doc; ctx?: Doc }, target: { doc: Doc; ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
+        export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
             const linkDoc = InstanceFromProto(
                 Prototypes.get(DocumentType.LINK),
                 undefined,
                 {
-                    anchor1: source.doc,
-                    anchor2: target.doc,
+                    anchor1: source,
+                    anchor2: target,
                     ...options,
                 },
                 id
@@ -1294,16 +1294,14 @@ export namespace DocUtils {
         broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1));
         return DocUtils.ActiveRecordings.map(audio => {
             const sourceDoc = getSourceDoc();
-            const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor(true) || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
-            link && (link.followLinkLocation = OpenWhere.addRight);
-            return link;
+            return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { linkDisplay: false, linkRelationship: 'recording annotation:linked recording', description: 'recording timeline' });
         });
     }
 
-    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = '', description: string = '', id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
-        if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
-        const sv = DocumentManager.Instance.getDocumentView(source.doc);
-        if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return;
+    export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
+        if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
+        const sv = DocumentManager.Instance.getDocumentView(source);
+        if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target) return;
         if (target.doc === Doc.UserDoc()) return undefined;
 
         const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
@@ -1343,16 +1341,16 @@ export namespace DocUtils {
                 target,
                 {
                     title: ComputedField.MakeFunction('generateLinkTitle(self)') as any,
-                    'anchor1-useLinkSmallAnchor': source.doc.useLinkSmallAnchor ? true : undefined,
-                    'anchor2-useLinkSmallAnchor': target.doc.useLinkSmallAnchor ? true : undefined,
+                    'anchor1-useLinkSmallAnchor': source.useLinkSmallAnchor ? true : undefined,
+                    'anchor2-useLinkSmallAnchor': target.useLinkSmallAnchor ? true : undefined,
                     'acl-Public': SharingPermissions.Augment,
                     '_acl-Public': SharingPermissions.Augment,
-                    linkDisplay: true,
+                    linkDisplay: linkSettings.linkDisplay,
                     _linkAutoMove: true,
-                    linkRelationship,
+                    linkRelationship: linkSettings.linkRelationship,
                     _showCaption: 'description',
                     _showTitle: 'linkRelationship',
-                    description,
+                    description: linkSettings.description,
                 },
                 id
             ),
@@ -1700,7 +1698,7 @@ export namespace DocUtils {
                 _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
             });
             Doc.AddDocToList(context, annotationField, pushpin);
-            const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, 'pushpin', '');
+            const pushpinLink = DocUtils.MakeLink(pushpin, doc, { linkRelationship: 'pushpin' }, '');
             doc._timecodeToShow = undefined;
             return pushpin;
         }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index cc116fb46..7d2aa813f 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -168,7 +168,6 @@ export namespace DragManager {
             this.dropDocCreator = dropDocCreator;
             this.offset = [0, 0];
         }
-        linkSourceDoc?: Doc;
         dropDocCreator: (annotationOn: Doc | undefined) => Doc;
         dropDocument?: Doc;
         offset: number[];
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 785018990..df61ecece 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -9,8 +9,6 @@ import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
-
-type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
 /*
  * link doc:
  * - anchor1: doc
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 6a530e3ae..e4c3e864b 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -102,13 +102,11 @@ export class ContextMenu extends React.Component {
         }
     }
     @action
-    moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
-        if (after && this.findByDescription(after.description)) {
-            const curInd = this._items.findIndex(i => i.description === item.description);
-            this._items.splice(curInd, 1);
-            const afterInd = this._items.findIndex(i => i.description === after.description);
-            this._items.splice(afterInd + 1, 0, item);
-        }
+    moveAfter(item: ContextMenuProps, after?: ContextMenuProps) {
+        const curInd = this._items.findIndex(i => i.description === item.description);
+        this._items.splice(curInd, 1);
+        const afterInd = after && this.findByDescription(after.description) ? this._items.findIndex(i => i.description === after.description) : this._items.length;
+        this._items.splice(afterInd, 0, item);
     }
     @action
     setDefaultItem(prefix: string, item: (name: string) => void) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 9389fed01..3e1a0f265 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -416,7 +416,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const targetDoc = this.view0?.props.Document;
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
-                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={e => this.openContextMenu(e)}>
+                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={this.openContextMenu}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
                 </div>
             </Tooltip>
@@ -569,7 +569,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc;
             const trail = DocCast(anchor.presTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
             if (trail !== anchor.presTrail) {
-                DocUtils.MakeLink({ doc: anchor }, { doc: trail }, 'link trail');
+                DocUtils.MakeLink(anchor, trail, { linkRelationship: 'link trail' });
                 anchor.presTrail = trail;
             }
             Doc.ActivePresentation = trail;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index befb0200d..b0888df68 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -270,6 +270,7 @@ export class MainView extends React.Component {
                 fa.faLaptopCode,
                 fa.faMale,
                 fa.faCopy,
+                fa.faHome,
                 fa.faHandPointLeft,
                 fa.faHandPointRight,
                 fa.faCompass,
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 48c0150e6..2d2b0f83e 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -74,7 +74,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         });
         FormattedTextBox.SelectOnLoad = target[Id];
         FormattedTextBox.DontSelectInitialText = true;
-        const link = DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on');
+        const link = DocUtils.MakeLink(anchor, target, { linkRelationship: 'inline comment:comment on' });
         link && (link.linkDisplay = false);
 
         const taggedContent = this.docFilters()
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 121260680..cb5be990d 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -441,7 +441,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
             this.props.addDocument?.(source);
-            de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
+            de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: 'doc annotation' }); // TODODO this is where in text links get passed
             e.stopPropagation();
         } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
         return false;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 64ec419ec..1e02fc9d4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,14 +3,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { CursorProperty } from 'csstype';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -460,7 +460,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
             this.props.addDocument?.(source);
-            de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
+            de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: 'doc annotation' }); // TODODO this is where in text links get passed
             e.stopPropagation();
         } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
         return false;
@@ -623,11 +623,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         if (!e.isPropagationStopped()) {
-            const subItems: ContextMenuProps[] = [];
-            subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
-            subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
-            subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
-            ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
+            const cm = ContextMenu.Instance;
+            const options = cm.findByDescription('Options...');
+            const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+            optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
+            optionItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+            optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' });
+            !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
         }
     };
 
@@ -735,14 +737,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                                 <EditableView {...editableViewProps} />
                             </div>
                         )}
-                        {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
-                            <Switch
-                                onChange={this.onToggle}
-                                onClick={this.onToggle}
-                                defaultChecked={true}
-                                checkedChildren="edit"
-                                unCheckedChildren="view"
-                            />} */}
                     </div>
                 </div>
             </>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5100d8d67..132ed6fb6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -335,7 +335,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                                 const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
                                 if (focusNode) {
                                     const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
-                                    anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+                                    anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
                                 }
                             }
                         }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 99b7549c0..75e76019e 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -359,7 +359,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         if (de.complete.linkDragData) {
             const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
             const destDoc = this.doc;
-            DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, 'tree link', '');
+            DocUtils.MakeLink(sourceDoc, destDoc, { linkRelationship: 'tree link' });
             e.stopPropagation();
         }
         const docDragData = de.complete.docDragData;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d39668a5d..fb0bd2a19 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -397,7 +397,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
                 const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
                 this.props.addDocument?.(source);
-                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, 'annotated by:annotation of', ''); // TODODO this is where in text links get passed
+                de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { linkRelationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
             }
             e.stopPropagation(); //  do nothing if link is dropped into any freeform view parent of dragged document
             return true;
@@ -1693,6 +1693,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             },
             icon: 'compress-arrows-alt',
         });
+        if (this.props.setContentView === emptyFunction) {
+            !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+            return;
+        }
         !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
         appearanceItems.push({
             description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
@@ -1700,7 +1704,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
         });
         appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
-        //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
+        !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
         this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
@@ -1730,27 +1734,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
         const mores = ContextMenu.Instance.findByDescription('More...');
         const moreItems = mores && 'subitems' in mores ? mores.subitems : [];
-        moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
         !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
     };
 
-    importDocument = (x: number, y: number) => {
-        const input = document.createElement('input');
-        input.type = 'file';
-        input.accept = '.zip';
-        input.onchange = _e => {
-            input.files &&
-                Doc.importDocument(input.files[0]).then(doc => {
-                    if (doc instanceof Doc) {
-                        const [xx, yy] = this.getTransform().transformPoint(x, y);
-                        (doc.x = xx), (doc.y = yy);
-                        this.props.addDocument?.(doc);
-                    }
-                });
-        };
-        input.click();
-    };
-
     @undoBatch
     @action
     transcribeStrokes = (math: boolean) => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 043fe0bcf..0b4f76fa1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -527,7 +527,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         });
         const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, followLinkToggle: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' });
         const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
-        DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', '');
+        DocUtils.MakeLink(summary, portal, { linkRelationship: 'summary of:summarized by' });
 
         portal.hidden = true;
         this.props.addDocument?.(portal);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d6679b46d..df3299eef 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -142,7 +142,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             } else if (startLink !== endLink) {
                 endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
                 startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
-                const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined, undefined, undefined, true);
+                const linkDoc = DocUtils.MakeLink(startLink, endLink, { linkRelationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined }, undefined, true);
 
                 LinkManager.currentLink = linkDoc;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7bf37a45..159aa026a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -617,22 +617,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @undoBatch
     @action
-    drop = async (e: Event, de: DragManager.DropEvent) => {
+    drop = (e: Event, de: DragManager.DropEvent) => {
         if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
         if (this.props.Document === Doc.ActiveDashboard) {
             alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.');
             return;
         }
         const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
-        if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
-        if (linkdrag?.linkSourceDoc) {
-            e.stopPropagation();
-            if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
-                de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
-            }
-            if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
-                const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.props.Document;
-                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]);
+        if (linkdrag) {
+            linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
+            if (linkdrag.linkSourceDoc) {
+                e.stopPropagation();
+                if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
+                    de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
+                }
+                if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
+                    const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
+                    de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, undefined, [de.x, de.y - 50]);
+                }
             }
         }
     };
@@ -643,9 +645,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document && d.linkRelationship === 'portal to:portal from');
         if (!portalLink) {
             DocUtils.MakeLink(
-                { doc: this.props.Document },
-                { doc: Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }) },
-                'portal to:portal from'
+                this.props.Document,
+                Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }),
+                { linkRelationship: 'portal to:portal from' }
             );
         }
         this.Document.followLinkLocation = OpenWhere.lightbox;
@@ -653,6 +655,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.Document._isLinkButton = true;
     };
 
+    importDocument = () => {
+        const input = document.createElement('input');
+        input.type = 'file';
+        input.accept = '.zip';
+        input.onchange = _e => {
+            if (input.files) {
+                const batch = UndoManager.StartBatch('importing');
+                Doc.importDocument(input.files[0]).then(doc => {
+                    if (doc instanceof Doc) {
+                        this.props.addDocTab(doc, OpenWhere.addRight);
+                        batch.end();
+                    }
+                });
+            }
+        };
+        input.click();
+    };
+
     @action
     onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
         if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) {
@@ -704,9 +724,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const appearance = cm.findByDescription('UI Controls...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
-            LinkManager.Links(this.Document).length &&
-                appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' });
-            !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
+            !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
 
             if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
                 const existingOnClick = cm.findByDescription('OnClick...');
@@ -774,16 +792,21 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     }
                     moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' });
                 }
-                moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
-
-                (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
             }
 
-            if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && Doc.ActiveDashboard !== this.props.Document) {
+            !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
+        }
+        const constantItems: ContextMenuProps[] = [];
+
+        if (!Doc.IsSystem(this.rootDoc)) {
+            constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
+            constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() });
+            (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
+            if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
-                moreItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
+                constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
-            !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
+            cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
         }
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
@@ -834,7 +857,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 icon: 'book',
             });
         }
-        cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
+        if (!help) cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
+        else cm.moveAfter(help);
 
         e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b5193cd20..f38ebba27 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -222,7 +222,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.panYMin = anchy / viewScale;
         croppingProto.panYMax = anchh / viewScale;
         if (addCrop) {
-            DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+            DocUtils.MakeLink(region, cropping, { linkRelationship: 'cropped image' });
             cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]();
             cropping.y = NumCast(this.rootDoc.y);
             this.props.addDocTab(cropping, OpenWhere.inParent);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a254edd87..e38d7e2a8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -130,7 +130,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         croppingProto['data-nativeWidth'] = anchw;
         croppingProto['data-nativeHeight'] = anchh;
         if (addCrop) {
-            DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+            DocUtils.MakeLink(region, cropping, { linkRelationship: 'cropped image' });
         }
         this.props.bringToFront(cropping);
 
@@ -344,7 +344,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             },
             (e, movement, isClick) => !isClick && batch.end(),
             () => {
-                this.toggleSidebar();
+                onButton && this.toggleSidebar();
                 batch.end();
             }
         );
@@ -440,17 +440,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
         return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
+    @undoBatch
+    toggleSidebarType = () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
     specificContextMenu = (e: React.MouseEvent): void => {
-        const funcs: ContextMenuProps[] = [];
-        funcs.push({
-            description: 'Toggle Sidebar Type',
-            event: () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform),
-            icon: 'expand-arrows-alt',
-        });
-        funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
-        funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
-        //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
-        ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
+        const cm = ContextMenu.Instance;
+        const options = cm.findByDescription('Options...');
+        const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+        optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
+        !Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
+        //optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
+        !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
+        const help = cm.findByDescription('Help...');
+        const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
+        helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
+        !help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' });
     };
 
     @computed get renderTitleBox() {
@@ -523,8 +526,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
-                    //PanelWidth={this.sidebarWidth}
-                    //ScreenToLocalTransform={this.sidebarScreenToLocal}
                 />
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
@@ -568,7 +569,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
         return !this._pdf ? null : (
             <div
-                className={'pdfBox'}
+                className="pdfBox"
                 onContextMenu={this.specificContextMenu}
                 style={{
                     display: this.props.thumbShown?.() ? 'none' : undefined,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0570c7fcb..e14ad4b05 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -327,7 +327,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 _isLinkButton: true,
             });
             this.props.addDocument?.(b);
-            DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, 'video snapshot');
+            DocUtils.MakeLink(b, this.rootDoc, { linkRelationship: 'video snapshot' });
             Networking.PostToServer('/youtubeScreenshot', {
                 id: this.youtubeVideoId,
                 timecode: this.layoutDoc._currentTimecode,
@@ -376,7 +376,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
         Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
         this.props.addDocument?.(imageSnapshot);
-        const link = DocUtils.MakeLink({ doc: imageSnapshot }, { doc: this.getAnchor(true) }, 'video snapshot');
+        const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { linkRelationship: 'video snapshot' });
         link && (Doc.GetProto(link.anchor2 as Doc).timecodeToHide = NumCast((link.anchor2 as Doc).timecodeToShow) + 3);
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
     };
@@ -1031,7 +1031,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.panYMin = anchy / viewScale;
         croppingProto.panYMax = anchh / viewScale;
         if (addCrop) {
-            DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+            DocUtils.MakeLink(region, cropping, { linkRelationship: 'cropped image' });
         }
         this.props.bringToFront(cropping);
         return cropping;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d8afc3227..361e000f9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -454,7 +454,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                             alink =
                                 alink ??
                                 (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
-                                    DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
+                                    DocUtils.MakeLink(this.props.Document, target, { linkRelationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
                             const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
@@ -1273,7 +1273,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         ]),
                     ]);
 
-                    const link = DocUtils.MakeLink({ doc: pdfAnchor }, { doc: this.rootDoc }, 'PDF pasted');
+                    const link = DocUtils.MakeLink(pdfAnchor, this.rootDoc, { linkRelationship: 'PDF pasted' });
                     if (link) {
                         view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
                     }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 9e9b61db3..e691869cc 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -259,7 +259,7 @@ export class RichTextRules {
                                 this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
                             }
                             const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500 }, docId);
-                            DocUtils.MakeLink({ doc: this.TextBox.getAnchor(true) }, { doc: target }, 'portal to:portal from', undefined);
+                            DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { linkRelationship: 'portal to:portal from' });
 
                             const fstate = this.TextBox.EditorView?.state;
                             if (fstate && selection) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index fe9b13fbe..8f93f1150 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -117,7 +117,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     makeLink = action((linkTo: Doc) => {
         const linkFrom = this.props.linkCreateAnchor?.();
         if (linkFrom) {
-            const link = DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
+            const link = DocUtils.MakeLink(linkFrom, linkTo, {});
             link && this.props.linkCreated?.(link);
         }
     });
-- 
cgit v1.2.3-70-g09d2


From 8a1267faf796b2e2a30a6ba9f86879854e9ee983 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 11 Apr 2023 13:51:44 -0400
Subject: removed arrangeItems context button for collections to make it a
 funciton on drop instead of a reaction.  Converted isLinkButton to be an
 onClick script.  got rid of unused PARAMS field on templates.   fixed
 PresElementBox rendering of embedded docs.

---
 src/client/documents/Documents.ts                  |  7 +--
 src/client/util/CurrentUserUtils.ts                |  4 +-
 src/client/util/DropConverter.ts                   | 10 +---
 src/client/util/LinkFollower.ts                    |  6 ++
 src/client/views/DocumentButtonBar.tsx             |  9 +--
 src/client/views/MarqueeAnnotator.tsx              |  8 ++-
 src/client/views/PropertiesButtons.tsx             |  7 ++-
 src/client/views/StyleProvider.tsx                 |  5 +-
 src/client/views/collections/CollectionMenu.tsx    | 19 +-----
 .../views/collections/CollectionNoteTakingView.tsx |  4 +-
 .../collections/CollectionStackedTimeline.tsx      |  6 +-
 .../views/collections/CollectionStackingView.tsx   |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 56 ++++++++++--------
 src/client/views/nodes/DocumentContentsView.tsx    |  3 +-
 src/client/views/nodes/DocumentView.tsx            | 67 +++++-----------------
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  7 ++-
 .../views/nodes/formattedText/DashFieldView.tsx    |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  2 +-
 src/fields/Doc.ts                                  | 10 ++--
 src/fields/documentSchemas.ts                      |  1 -
 24 files changed, 104 insertions(+), 147 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 979b294e0..7600fcac9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -263,7 +263,6 @@ export class DocumentOptions {
     caption?: RichTextField;
     opacity?: number;
     defaultBackgroundColor?: string;
-    _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked
     _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint
     hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed
     isFolder?: boolean;
@@ -508,13 +507,13 @@ export namespace Docs {
                     layout: { view: LinkBox, dataField: defaultDataKey },
                     options: {
                         childDontRegisterViews: true,
-                        _isLinkButton: true,
+                        onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
                         hideLinkAnchors: true,
                         _height: 150,
                         description: '',
                         showCaption: 'description',
                         backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
-                        _removeDropProperties: new List(['isLinkButton']),
+                        _removeDropProperties: new List(['onClick']),
                     },
                 },
             ],
@@ -1695,7 +1694,7 @@ export namespace DocUtils {
                 _width: 15,
                 _height: 15,
                 _xPadding: 0,
-                _isLinkButton: true,
+                onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
                 _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
             });
             Doc.AddDocToList(context, annotationField, pushpin);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 623aaf357..3d0db04b4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -271,7 +271,7 @@ export class CurrentUserUtils {
             {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true,  }},
             {key: "Screengrab", creator: Docs.Create.ScreenshotDocument,                    opts: { _width: 400, _height: 200 }},
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List<string>(["system"]) }},
-            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
+            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, onClick: ScriptField.MakeScript("followLink(this,altKey)", {altKey:"boolean"}) }},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}},
@@ -622,7 +622,7 @@ export class CurrentUserUtils {
             { title: "Snap\xA0Lines",icon: "th",          toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "View\xA0All",  icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Clusters",     icon: "braille",     toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Arrange",      icon: "window",      toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Arrange",      icon: "window",      toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
             { title: "Reset",        icon: "check",       toolTip: "Reset View",          btnType: ButtonType.ClickButton,  expertMode: false, backgroundColor:"transparent",    scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform
         ]
     }
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 7c209d1e0..2e1d6ba23 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -33,15 +33,7 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
     let any = false;
     docs.forEach(d => {
         if (!StrCast(d.title).startsWith('-')) {
-            const params = StrCast(d.title)
-                .match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1]
-                .replace('()', '');
-            if (params) {
-                any = makeTemplate(d, false) || any;
-                d.PARAMS = params;
-            } else {
-                any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any;
-            }
+            any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any;
         } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) {
             any = makeTemplate(d, false) || any;
         }
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index df61ecece..f5aae6632 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -7,6 +7,7 @@ import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/no
 import { PresBox } from '../views/nodes/trails';
 import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
+import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
 /*
@@ -118,3 +119,8 @@ export class LinkFollower {
         });
     }
 }
+
+ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
+    SelectionManager.DeselectAll();
+    LinkFollower.FollowLink(undefined, doc, altKey);
+});
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index e83ea00cf..7e1b32398 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, DocCast, NumCast } from '../../fields/Types';
+import { Cast, DocCast, NumCast, ScriptCast } from '../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
@@ -238,11 +238,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 </Tooltip>
             );
         };
+        const followLink = ScriptCast(targetDoc?.onClick)?.script.originalScript.includes('followLink(');
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">Set onClick to follow primary link</div>}>
                 <div
                     className="documentButtonBar-icon documentButtonBar-follow"
-                    style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
+                    style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
                     onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
                     <div className="documentButtonBar-followTypes">
                         {followBtn(
@@ -384,7 +385,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             .views()
                             .filter(v => v)
                             .map(dv => dv!.rootDoc);
-                        TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: {dataview: e.altKey}, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                        TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                         e.stopPropagation();
                     }}>
                     <div className="documentButtonBar-pinTypes">
@@ -593,7 +594,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         <LinkPopup
                             key="popup"
                             showPopup={this._showLinkPopup}
-                            linkCreated={link => (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)}
+                            linkCreated={link => (link.linkDisplay = !ScriptCast(this.props.views().lastElement()?.rootDoc.onClick)?.script.originalScript.includes('followLink('))}
                             linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
                             linkFrom={() => this.props.views().lastElement()?.rootDoc}
                         />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 30867a774..6b4f8ad85 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -14,6 +14,7 @@ import { DocumentView } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import React = require('react');
+import { ScriptField } from '../../fields/ScriptField';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export interface MarqueeAnnotatorProps {
@@ -146,7 +147,12 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             const scale = this.props.scaling?.() || 1;
             const anno = savedAnnos[0];
             const containerOffset = this.props.containerOffset?.() || [0, 0];
-            const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title });
+            const marqueeAnno = Docs.Create.FreeformDocument([], {
+                onClick: isLinkButton ? ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }) : undefined,
+                backgroundColor: color,
+                annotationOn: this.props.rootDoc,
+                title: 'Annotation on ' + this.props.rootDoc.title,
+            });
             marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1);
             marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1) + NumCast(this.props.scrollTop);
             marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1);
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 11e9dd9c9..0723966e1 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -289,6 +289,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             .filter(dv => dv.docView)
             .map(dv => dv.docView!)
             .forEach(docView => {
+                const linkButton = ScriptCast(docView.props.Document.onClick)?.script.originalScript.includes('followLink(');
                 docView.noOnClick();
                 switch (onClick) {
                     case 'enterPortal':
@@ -299,11 +300,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                         break;
                     case 'linkInPlace':
                         docView.toggleFollowLink(false, false);
-                        docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.lightbox : undefined;
+                        docView.props.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
                         break;
                     case 'linkOnRight':
                         docView.toggleFollowLink(false, false);
-                        docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.addRight : undefined;
+                        docView.props.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
                         break;
                 }
             });
@@ -326,7 +327,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         ];
         const list = buttonList.map(value => {
             const click = () => this.handleOptionChange(value[0]);
-            const linkButton = BoolCast(this.selectedDoc._isLinkButton);
+            const linkButton = BoolCast(ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('followLink('));
             const followLoc = this.selectedDoc._followLinkLocation;
             const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index f7b9420a7..8d67f0528 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -4,7 +4,7 @@ import { Shadows } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { extname } from 'path';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
-import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { DashColor, lightOrDark, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
@@ -279,7 +279,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.BoxShadow: {
             if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
             if (doc.boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
-            if (doc?.isLinkButton && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+            if (ScriptCast(doc?.onClick)?.script.originalScript.includes('followLink(') && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any))
+                return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index c83f4e689..225743748 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -246,23 +246,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         immediate: undoBatch((source: Doc[]) => {}),
         initialize: emptyFunction,
     };
-    _openLinkInCommand = {
-        params: ['target', 'container'],
-        title: 'link follow target',
-        script: `{ if (self.container?.length) {
-            getProto(self.target).linkContainer = self.container[0];
-            getProto(self.target).isLinkButton = true;
-            getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
-            }}`,
-        immediate: undoBatch((container: Doc[]) => {
-            if (container.length) {
-                Doc.GetProto(this.target).linkContainer = container[0];
-                Doc.GetProto(this.target).isLinkButton = true;
-                Doc.GetProto(this.target).onClick = ScriptField.MakeScript('getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])');
-            }
-        }),
-        initialize: emptyFunction,
-    };
     _viewCommand = {
         params: ['target'],
         title: 'bookmark view',
@@ -328,7 +311,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand];
     }
     @computed get _doc_commands() {
-        return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand];
+        return Doc.noviceMode ? undefined : [this._onClickCommand];
     }
     @computed get _tree_commands() {
         return undefined;
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index cb5be990d..aec0734b4 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -215,7 +215,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined);
     // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView)
     getDisplayDoc(doc: Doc, width: () => number) {
-        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
         let dref: Opt<DocumentView>;
         const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
@@ -293,7 +293,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     getDocHeight(d?: Doc) {
         if (!d || d.hidden) return 0;
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
-        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
         const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
         const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 642d29d2a..5891cfe78 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -7,7 +7,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, NumCast, ScriptCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
@@ -406,7 +406,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 backgroundColor: 'rgba(128, 128, 128, 0.5)',
                 useLinkSmallAnchor: true,
                 hideLinkButton: true,
-                _isLinkButton: true,
+                onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
                 annotationOn: rootDoc,
                 _timelineLabel: true,
                 borderRounding: anchorEndTime === undefined ? '100%' : undefined,
@@ -450,7 +450,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     @action
     clickAnchor = (anchorDoc: Doc, clientX: number) => {
-        if (anchorDoc.isLinkButton) {
+        if (ScriptCast(anchorDoc.onClick)?.script.originalScript.includes('followLink(')) {
             LinkFollower.FollowLink(undefined, anchorDoc, false);
         }
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 1e02fc9d4..a85ee0e02 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -305,7 +305,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
     getDisplayDoc(doc: Doc, width: () => number, count: number) {
-        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
+        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
 
         let dref: Opt<DocumentView>;
@@ -323,7 +323,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 styleProvider={this.styleProvider}
                 docViewPath={this.props.docViewPath}
                 fitWidth={this.props.childFitWidth}
-                isContentActive={doc.isLinkButton ? this.isChildButtonContentActive : this.isChildContentActive}
+                isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
                 onBrowseClick={this.props.onBrowseClick}
                 isDocumentActive={this.isContentActive}
@@ -381,7 +381,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     getDocHeight(d?: Doc) {
         if (!d || d.hidden) return 0;
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
-        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
         const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
         const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1ecc69174..7ae7be3c8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -349,6 +349,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
         const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
         const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
@@ -370,6 +371,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             (d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
         }
 
+        if (this.layoutDoc._autoArrange || de.metaKey) {
+            const sorted = this.childLayoutPairs.slice().sort((a, b) => NumCast(a.layout.y) - NumCast(b.layout.y));
+            sorted.splice(
+                sorted.findIndex(pair => pair.layout === refDoc),
+                1
+            );
+            if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
+                const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height);
+                const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
+                const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
+
+                let lastx = NumCast(refDoc.x);
+                let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
+                setTimeout(
+                    action(() =>
+                        sorted.slice(1).forEach((pair, i) => {
+                            lastx = pair.layout.x = lastx + deltax;
+                            lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
+                        })
+                    )
+                );
+            }
+        }
+
         (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
         return true;
     }
@@ -1409,24 +1434,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
-        if (this.layoutDoc._autoArrange) {
-            const sorted = this.childLayoutPairs.slice().sort((a, b) => (NumCast(a.layout.y) < NumCast(b.layout.y) ? -1 : 1));
-            if (sorted.length > 1) {
-                const deltay = sorted.length > 1 ? NumCast(sorted[1].layout.y) - (NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height)) : 0;
-                const deltax = sorted.length > 1 ? NumCast(sorted[1].layout.x) - NumCast(sorted[0].layout.x) : 0;
-
-                let lastx = NumCast(sorted[0].layout.x);
-                let lasty = NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height);
-                setTimeout(
-                    action(() =>
-                        sorted.slice(1).forEach((pair, i) => {
-                            lastx = pair.layout.x = lastx + deltax;
-                            lasty = (pair.layout.y = lasty + deltay) + NumCast(pair.layout._height);
-                        })
-                    )
-                );
-            }
-        }
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
         return [] as ViewDefResult[];
     }
@@ -1712,11 +1719,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             },
             icon: 'compress-arrows-alt',
         });
-        appearanceItems.push({
-            description: 'Toggle auto arrange',
-            event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
-            icon: 'compress-arrows-alt',
-        });
+        !Doc.noviceMode &&
+            appearanceItems.push({
+                description: 'Toggle auto arrange',
+                event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
+                icon: 'compress-arrows-alt',
+            });
         if (this.props.setContentView === emptyFunction) {
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
@@ -1729,7 +1737,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         });
         appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
-        appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+        this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
         this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 9e56de8c2..2d12005fb 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -139,7 +139,6 @@ export class DocumentContentsView extends React.Component<
         return proto instanceof Promise ? undefined : proto;
     }
     get layoutDoc() {
-        const params = StrCast(this.props.Document.PARAMS);
         // bcz: replaced this with below : is it correct?  change was made to accommodate passing fieldKey's from a layout script
         // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
         const template: Doc =
@@ -147,7 +146,7 @@ export class DocumentContentsView extends React.Component<
             (this.props.LayoutTemplateString && this.props.Document) ||
             (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) ||
             Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
-        return Doc.expandTemplateLayout(template, this.props.Document, params ? '(' + params + ')' : this.props.layoutKey);
+        return Doc.expandTemplateLayout(template, this.props.Document, this.props.layoutKey);
     }
 
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e24bf35f5..a39c3cf8f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -469,11 +469,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             ? this.props.select(false)
                             : '';
                     clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
-                } else if (!this.disableClickScriptFunc && this.allLinks.length && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
-                    clickFunc = () => {
-                        SelectionManager.DeselectAll();
-                        LinkFollower.FollowLink(undefined, this.Document, e.altKey);
-                    };
                 } else {
                     // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
@@ -563,48 +558,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch
     @action
     toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
-        this.Document.ignoreClick = false;
-        if (setTargetToggle) {
-            this.Document.followLinkToggle = !this.Document.followLinkToggle;
-            this.Document._isLinkButton = this.Document.followLinkToggle || this.Document._isLinkButton;
-        } else {
-            this.Document._isLinkButton = !this.Document._isLinkButton;
-        }
-        if (this.Document._isLinkButton && !this.onClickHandler) {
-            zoom !== undefined && (this.Document.followLinkZoom = zoom);
-        } else if (this.Document._isLinkButton && this.onClickHandler) {
-            this.Document._isLinkButton = false;
-            this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
-        }
-    };
-    @undoBatch
-    @action
-    toggleTargetOnClick = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document._isLinkButton = true;
-        this.Document.followLinkToggle = true;
+        const hadOnClick = this.rootDoc.onClick;
+        this.noOnClick();
+        this.Document.onClick = hadOnClick ? undefined : ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
+        this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
     };
     @undoBatch
     @action
-    followLinkOnClick = (location: Opt<string>, zoom: boolean): void => {
+    followLinkOnClick = (): void => {
         this.Document.ignoreClick = false;
-        this.Document._isLinkButton = true;
+        this.Document.onClick = ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
         this.Document.followLinkToggle = false;
-        this.Document.followLinkZoom = zoom;
-        this.Document.followLinkLocation = location;
-    };
-    @undoBatch
-    @action
-    selectOnClick = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document._isLinkButton = false;
-        this.Document.followLinkToggle = false;
-        this.Document.onClick = this.layoutDoc.onClick = undefined;
+        this.Document.followLinkZoom = false;
+        this.Document.followLinkLocation = undefined;
     };
     @undoBatch
     noOnClick = (): void => {
         this.Document.ignoreClick = false;
-        this.Document._isLinkButton = false;
+        this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
     };
 
     @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
@@ -652,8 +623,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             );
         }
         this.Document.followLinkLocation = OpenWhere.lightbox;
-        this.Document.followLinkZoom = true;
-        this.Document._isLinkButton = true;
+        this.Document.onClick = ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
     };
 
     importDocument = () => {
@@ -744,29 +714,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     !zorders && cm.addItem({ description: 'ZOrder...', noexpand: true, subitems: zorderItems, icon: 'compass' });
                 }
 
-                !Doc.noviceMode && onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
+                onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
-                this.props.CollectionFreeFormDocumentView &&
-                    onClicks.push({
-                        description: (this.Document.followLinkZoom ? "Don't" : '') + ' zoom following link',
-                        event: () => (this.Document.followLinkZoom = !this.Document.followLinkZoom),
-                        icon: this.Document.ignoreClick ? 'unlock' : 'lock',
-                    });
 
                 if (!this.Document.annotationOn) {
                     const options = cm.findByDescription('Options...');
                     const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
                     !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
 
-                    onClicks.push({ description: this.Document.ignoreClick ? 'Select' : 'Do Nothing', event: () => (this.Document.ignoreClick = !this.Document.ignoreClick), icon: this.Document.ignoreClick ? 'unlock' : 'lock' });
-                    onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
-                    onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(false, true), icon: 'map-pin' });
+                    onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
                     onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
                     !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
                 } else if (LinkManager.Links(this.Document).length) {
-                    onClicks.push({ description: 'Select on Click', event: () => this.selectOnClick(), icon: 'link' });
-                    onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(undefined, false), icon: 'link' });
-                    onClicks.push({ description: 'Toggle Link Target on Click', event: () => this.toggleTargetOnClick(), icon: 'map-pin' });
+                    onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' });
+                    onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
                     !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, subitems: onClicks, icon: 'mouse-pointer' });
                 }
             }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f38ebba27..22ecaa299 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -202,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping.y = NumCast(this.rootDoc.y);
         cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
         cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
-        cropping.isLinkButton = undefined;
+        cropping.onClick = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
         croppingProto.isPrototype = true;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 3feb95ce9..31f1775e5 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -47,7 +47,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (separation > 100) {
                 const dragData = new DragManager.DocumentDragData([this.rootDoc]);
                 dragData.dropAction = 'alias';
-                dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'isLinkButton'];
+                dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'onClick'];
                 DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
                 return true;
             } else {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e38d7e2a8..502811e35 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -119,7 +119,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         cropping.y = NumCast(this.rootDoc.y);
         cropping._width = anchw;
         cropping._height = anchh;
-        cropping.isLinkButton = undefined;
+        cropping.onClick = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
         croppingProto.isPrototype = true;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e14ad4b05..83ad5628c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -33,6 +33,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
 import './VideoBox.scss';
+import { ScriptField } from '../../../fields/ScriptField';
 const path = require('path');
 
 /**
@@ -324,7 +325,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 _width: 150,
                 _height: 50,
                 title: (this.layoutDoc._currentTimecode || 0).toString(),
-                _isLinkButton: true,
+                onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
             });
             this.props.addDocument?.(b);
             DocUtils.MakeLink(b, this.rootDoc, { linkRelationship: 'video snapshot' });
@@ -368,7 +369,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             _nativeHeight: Doc.NativeHeight(this.layoutDoc),
             x: NumCast(this.layoutDoc.x) + width,
             y: NumCast(this.layoutDoc.y),
-            _isLinkButton: true,
+            onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }),
             _width: 150,
             _height: (height / width) * 150,
             title: '--snapshot' + NumCast(this.layoutDoc._currentTimecode) + ' image-',
@@ -1010,7 +1011,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
         cropping.timecodeToHide = undefined;
         cropping.timecodeToShow = undefined;
-        cropping.isLinkButton = undefined;
+        cropping.onClick = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
         croppingProto.isPrototype = true;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 21ccf3bc7..f23426bb3 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -116,7 +116,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
 
     public static multiValueDelimeter = ';';
     public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
-        const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === 'PARAMS' ? textBoxDoc[fieldKey] : '');
+        const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? '';
         const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
         return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' };
     }
@@ -223,7 +223,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                         Doc.SetInPlace(this._dashDoc!, this._fieldKey, Number(newText), true);
                     } else {
                         const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
-                        if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
+                        if (!this._textBoxDoc[this._fieldKey]) {
                             const strVal = splits.length > 1 ? new List<string>(splits) : newText;
                             if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
                             Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 361e000f9..809315963 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -17,7 +17,7 @@ import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { ComputedField } from '../../../../fields/ScriptField';
+import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
@@ -1933,7 +1933,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                                 paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
                                 paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
                                 paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
-                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
+                                pointerEvents: !active && !SnappingManager.GetIsDragging() ? (ScriptCast(this.layoutDoc.onClick)?.script.originalScript.includes('followLink(') ? 'none' : undefined) : undefined,
                             }}
                         />
                     </div>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 9a74b5dba..92696240b 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
             <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
                 <DocumentView
-                    Document={this.rootDoc}
+                    Document={PresBox.targetRenderedDoc(this.rootDoc)}
                     DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     PanelWidth={this.embedWidth}
                     PanelHeight={this.embedHeight}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1db56cbdd..3a5ba4f75 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -927,7 +927,7 @@ export namespace Doc {
     //   layout_mytemplate(somparam=somearg).
     // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
     export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
-        const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || StrCast(templateLayoutDoc.PARAMS);
+        const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || '';
         if ((!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc)) || !targetDoc) return templateLayoutDoc;
 
         const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
@@ -936,7 +936,7 @@ export namespace Doc {
         // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
         // saves it on the data doc indexed by the template layout's id.
         //
-        const params = args.split('=').length > 1 ? args.split('=')[0] : 'PARAMS';
+        const params = args.split('=').length > 1 ? args.split('=')[0] : '';
         const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
         const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']';
         let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
@@ -945,7 +945,7 @@ export namespace Doc {
             expandedTemplateLayout = undefined;
             _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
         } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
-            if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
+            if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
                 templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
@@ -981,8 +981,8 @@ export namespace Doc {
             console.log('No, no, no!');
             return { layout: childDoc, data: childDoc };
         }
-        const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc;
-        return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc };
+        const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc;
+        return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '()'), data: resolvedDataDoc };
     }
 
     export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 5b489a96c..b7fd06973 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -89,7 +89,6 @@ export const documentSchema = createSchema({
     hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
     linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints.
     isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations
-    isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked
     layers: listSpec('string'), // which layers the document is part of
     _lockedPosition: 'boolean', // whether the document can be moved (dragged)
     _lockedTransform: 'boolean', // whether a freeformview can pan/zoom
-- 
cgit v1.2.3-70-g09d2


From 5cd64622f14ede408d3baca4a10d155b60392e46 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 13 Apr 2023 22:22:07 -0400
Subject: lots of changes to get rid of ContainingCollectionDoc and
 ContainingCollectionView props.

---
 src/client/documents/Documents.ts                  |  8 +--
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/util/DocumentManager.ts                 | 83 ++++++++++------------
 src/client/util/DragManager.ts                     |  4 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |  6 +-
 src/client/util/ReplayMovements.ts                 | 64 ++++++-----------
 src/client/util/SelectionManager.ts                |  6 +-
 src/client/util/SharingManager.tsx                 |  5 +-
 src/client/util/TrackMovements.ts                  | 36 +++++-----
 src/client/views/DashboardView.tsx                 | 17 ++---
 src/client/views/DocComponent.tsx                  |  1 -
 src/client/views/DocumentDecorations.tsx           |  8 +--
 src/client/views/GestureOverlay.tsx                |  4 +-
 src/client/views/InkStrokeProperties.ts            |  3 +-
 src/client/views/LightboxView.tsx                  |  4 +-
 src/client/views/MainView.tsx                      | 20 ++----
 src/client/views/OverlayView.tsx                   | 10 +--
 src/client/views/Palette.tsx                       | 31 ++++----
 src/client/views/PropertiesDocContextSelector.tsx  | 22 ++++--
 src/client/views/PropertiesView.tsx                |  2 -
 src/client/views/StyleProvider.tsx                 | 11 ++-
 src/client/views/TemplateMenu.tsx                  |  2 -
 .../views/collections/CollectionCarouselView.tsx   |  1 -
 src/client/views/collections/CollectionMenu.tsx    |  6 +-
 .../views/collections/CollectionNoteTakingView.tsx |  4 --
 .../views/collections/CollectionPileView.tsx       |  2 +-
 .../collections/CollectionStackedTimeline.tsx      |  2 -
 .../views/collections/CollectionStackingView.tsx   |  4 --
 .../views/collections/CollectionTreeView.tsx       | 10 +--
 src/client/views/collections/CollectionView.tsx    |  5 +-
 src/client/views/collections/TabDocView.tsx        |  6 +-
 src/client/views/collections/TreeView.tsx          | 54 ++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 31 ++++----
 .../collectionLinear/CollectionLinearView.tsx      |  5 +-
 .../CollectionMulticolumnView.tsx                  |  2 -
 .../CollectionMultirowView.tsx                     |  2 -
 .../collectionSchema/CollectionSchemaView.tsx      |  6 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |  2 +-
 .../collectionSchema/SchemaTableCell.tsx           |  2 -
 src/client/views/linking/LinkMenuItem.tsx          | 21 +-----
 src/client/views/linking/LinkPopup.tsx             |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentLinksButton.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 12 ++--
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/KeyValuePair.tsx            |  2 -
 src/client/views/nodes/LinkDocPreview.tsx          |  4 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  5 +-
 src/client/views/nodes/button/FontIconBox.tsx      |  4 +-
 .../views/nodes/formattedText/DashDocView.tsx      |  7 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |  5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 20 ++++--
 src/client/views/nodes/trails/PresBox.tsx          |  1 -
 src/client/views/nodes/trails/PresElementBox.tsx   | 27 ++++---
 src/client/views/topbar/TopBar.tsx                 | 51 ++-----------
 src/mobile/AudioUpload.tsx                         | 76 ++++++++++----------
 src/mobile/MobileInterface.tsx                     |  4 +-
 58 files changed, 291 insertions(+), 456 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8d97ce5af..09b45a481 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1285,10 +1285,6 @@ export namespace DocUtils {
         return rangeFilteredDocs;
     }
 
-    export function DefaultFocus(doc: Doc, options: DocFocusOptions) {
-        return undefined;
-    }
-
     export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
 
     export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
@@ -1299,10 +1295,9 @@ export namespace DocUtils {
         });
     }
 
-    export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
+    export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, showPopup?: number[]) {
         if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
         const sv = DocumentManager.Instance.getDocumentView(source);
-        if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target) return;
         if (target.doc === Doc.UserDoc()) return undefined;
 
         const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
@@ -1371,7 +1366,6 @@ export namespace DocUtils {
                         value: 'any',
                         _readOnly_: 'boolean',
                         scriptContext: 'any',
-                        thisContainer: Doc.name,
                         documentView: Doc.name,
                         heading: Doc.name,
                         checked: 'boolean',
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 76ea3e3ea..abf7313a4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ccf370662..ad89e8653 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,15 +1,11 @@
-import { loadAsync } from 'jszip';
-import { action, observable, ObservableSet, runInAction } from 'mobx';
+import { action, observable, ObservableSet } from 'mobx';
 import { AnimationSym, Doc, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../fields/Types';
 import { AudioField } from '../../fields/URLField';
-import { emptyFunction } from '../../Utils';
 import { CollectionViewType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
-import { CollectionView } from '../views/collections/CollectionView';
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
 import { MainView } from '../views/MainView';
@@ -39,7 +35,7 @@ export class DocumentManager {
     private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
     public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => {
         if (doc) {
-            const dv = this.getDocumentViewById(doc[Id]);
+            const dv = this.getDocumentView(doc);
             this._viewRenderedCbs.push({ doc, func });
             if (dv) {
                 this.callAddViewFuncs(dv);
@@ -129,42 +125,24 @@ export class DocumentManager {
         return this.getDocumentViewsById(doc[Id]);
     }
 
-    public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined {
-        if (!id) return undefined;
-        let toReturn: DocumentView | undefined;
-        const passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
-
-        for (const pass of passes) {
-            Array.from(DocumentManager.Instance.DocumentViews).map(view => {
-                if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
-                    toReturn = view;
-                    return;
-                }
-            });
-            if (!toReturn) {
-                Array.from(DocumentManager.Instance.DocumentViews).map(view => {
-                    const doc = view.rootDoc.proto;
-                    if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
-                        toReturn = view;
-                    }
-                });
-            } else {
-                break;
-            }
-        }
-
-        return toReturn;
-    }
-
-    public getDocumentView(toFind: Doc, preferredCollection?: CollectionView): DocumentView | undefined {
-        const found =
+    public getDocumentView(toFind: Doc | undefined, preferredCollection?: DocumentView): DocumentView | undefined {
+        const doc =
+            // bcz: this was temporary code used to match documents by data url instead of by id. intended only for repairing the DB
             // Array.from(DocumentManager.Instance.DocumentViews).find(
             //     dv =>
             //         ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFind.data as any)?.url?.href) ||
             //         ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFind.annotationOn)?.data as any)?.url?.href)
             // )?.rootDoc ??
             toFind;
-        return this.getDocumentViewById(found[Id], preferredCollection);
+        const docViewArray = Array.from(DocumentManager.Instance.DocumentViews);
+        const passes = !doc ? [] : preferredCollection ? [preferredCollection, undefined] : [undefined];
+        return passes.reduce(
+            (pass, toReturn) =>
+                toReturn ??
+                docViewArray.filter(view => view.rootDoc === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection) ??
+                docViewArray.filter(view => Doc.GetProto(view.rootDoc) === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection),
+            undefined as Opt<DocumentView>
+        );
     }
 
     public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
@@ -247,7 +225,8 @@ export class DocumentManager {
     public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
         const docViewPath = targetDocView.docViewPath.slice();
         let rootContextView = docViewPath.shift();
-        return rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }));
+        await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
+        if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
     };
 
     // shows a document by first:
@@ -318,15 +297,25 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
-    const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
-    const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView);
-    if (dv) {
-        DocumentManager.Instance.showDocumentView(dv, { willZoomCentered: true });
-    } else {
-        const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null);
-        const showDoc = context || doc;
-        DocumentManager.Instance.showDocument(Doc.BestAlias(showDoc), { openLocation: OpenWhere.addRight }, () => DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }));
-    }
+export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) {
+    const func = () => {
+        const cv = DocumentManager.Instance.getDocumentView(containingDoc);
+        const dv = DocumentManager.Instance.getDocumentView(doc, cv);
+        if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
+            DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
+        } else {
+            const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc));
+            DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => {
+                const cv = DocumentManager.Instance.getDocumentView(containingDoc);
+                const dv = DocumentManager.Instance.getDocumentView(doc, cv);
+                dv && Doc.linkFollowHighlight(dv.rootDoc);
+            });
+        }
+    };
+    if (doc.hidden) {
+        doc.hidden = false;
+        options.toggleTarget = false;
+        setTimeout(func);
+    } else func();
 }
 ScriptingGlobals.add(DocFocusOrOpen);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 7d2aa813f..7e6de5e67 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -3,10 +3,8 @@ import { DateField } from '../../fields/DateField';
 import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
-import { listSpec } from '../../fields/Schema';
-import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
 import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
 import { emptyFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import * as globalCssVariables from '../views/global/globalCssVariables.scss';
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 559958c2b..76b1323c4 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -14,12 +14,14 @@ import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes';
 import { Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
 import { DocumentManager } from '../DocumentManager';
 import './DirectoryImportBox.scss';
 import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
 import React = require('react');
+import _ = require('lodash');
 
 const unsupported = ['text/html', 'text/plain'];
 
@@ -155,8 +157,8 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset,
         };
-        const parent = this.props.ContainingCollectionView;
-        if (parent) {
+        const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it.
+        if (parent?.rootDoc.type === DocumentType.COL) {
             let importContainer: Doc;
             if (docs.length < 50) {
                 importContainer = Docs.Create.MasonryDocument(docs, options);
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index d5bffc5e2..40261985a 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -7,6 +7,7 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
 import { DocServer } from '../DocServer';
 import { Movement, Presentation } from './TrackMovements';
 import { OpenWhereMod } from '../views/nodes/DocumentView';
+import { returnTransparent } from '../../Utils';
 
 export class ReplayMovements {
     private timers: NodeJS.Timeout[] | null;
@@ -60,17 +61,11 @@ export class ReplayMovements {
             return;
         }
 
-        let docIdtoDoc: Map<string, Doc> = new Map();
-        try {
-            docIdtoDoc = await this.loadPresentation(presentation);
-        } catch {
-            console.error('[recordingApi.ts] setVideoBox(): error loading presentation - no replay movements');
-            throw 'error loading docs from server';
-        }
+        const docIdtoDoc = this.loadPresentation(presentation);
 
         this.videoBoxDisposeFunc = reaction(
             () => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }),
-            ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, docIdtoDoc, timeViewed) : this.pauseMovements())
+            ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, timeViewed) : this.pauseMovements())
         );
         this.videoBox = videoBox;
     };
@@ -93,45 +88,33 @@ export class ReplayMovements {
         this.pauseMovements();
     };
 
-    loadPresentation = async (presentation: Presentation) => {
+    loadPresentation = (presentation: Presentation) => {
         const { movements } = presentation;
         if (movements === null) {
             throw '[recordingApi.ts] followMovements() failed: no presentation data';
         }
 
         // generate a set of all unique docIds
-        const docIds = new Set<string>();
-        for (const { docId } of movements) {
-            if (!docIds.has(docId)) docIds.add(docId);
-        }
-
-        const docIdtoDoc = new Map<string, Doc>();
-
-        let refFields = await DocServer.GetRefFields([...docIds.keys()]);
-        for (const docId in refFields) {
-            if (!refFields[docId]) {
-                throw `one field was undefined`;
-            }
-            docIdtoDoc.set(docId, refFields[docId] as Doc);
+        const docs = new Set<Doc>();
+        for (const { doc } of movements) {
+            if (!docs.has(doc)) docs.add(doc);
         }
-        // console.info('loadPresentation refFields', refFields, docIdtoDoc);
 
-        return docIdtoDoc;
+        return docs;
     };
 
     // returns undefined if the docView isn't open on the screen
-    getCollectionFFView = (docId: string) => {
-        const isInView = DocumentManager.Instance.getDocumentViewById(docId);
+    getCollectionFFView = (doc: Doc) => {
+        const isInView = DocumentManager.Instance.getDocumentView(doc);
         if (isInView) {
             return isInView.ComponentView as CollectionFreeFormView;
         }
     };
 
     // will open the doc in a tab then return the CollectionFFView that holds it
-    openTab = (docId: string, docIdtoDoc: Map<string, Doc>) => {
-        const doc = docIdtoDoc.get(docId);
-        if (doc == undefined) {
-            console.error(`docIdtoDoc did not contain docId ${docId}`);
+    openTab = (doc: Doc) => {
+        if (doc === undefined) {
+            console.error(`doc undefined`);
             return undefined;
         }
         // console.log('openTab', docId, doc);
@@ -149,13 +132,12 @@ export class ReplayMovements {
         document.Document._panY = panY;
     };
 
-    getFirstMovements = (movements: Movement[]): Map<string, Movement> => {
+    getFirstMovements = (movements: Movement[]): Map<Doc, Movement> => {
         if (movements === null) return new Map();
         // generate a set of all unique docIds
-        const docIdtoFirstMove = new Map();
+        const docIdtoFirstMove = new Map<Doc, Movement>();
         for (const move of movements) {
-            const { docId } = move;
-            if (!docIdtoFirstMove.has(docId)) docIdtoFirstMove.set(docId, move);
+            if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move);
         }
         return docIdtoFirstMove;
     };
@@ -165,7 +147,7 @@ export class ReplayMovements {
         Doc.UserDoc().presentationMode = 'none';
     };
 
-    public playMovements = (presentation: Presentation, docIdtoDoc: Map<string, Doc>, timeViewed: number = 0) => {
+    public playMovements = (presentation: Presentation, timeViewed: number = 0) => {
         // console.info('playMovements', presentation, timeViewed, docIdtoDoc);
 
         if (presentation.movements === null || presentation.movements.length === 0) {
@@ -183,13 +165,13 @@ export class ReplayMovements {
         const handleFirstMovements = () => {
             // if the first movement is a closed tab, open it
             const firstMovement = filteredMovements[0];
-            const isClosed = this.getCollectionFFView(firstMovement.docId) === undefined;
-            if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc);
+            const isClosed = this.getCollectionFFView(firstMovement.doc) === undefined;
+            if (isClosed) this.openTab(firstMovement.doc);
 
             // for the open tabs, set it to the first move
             const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
-            for (const [docId, firstMove] of docIdtoFirstMove) {
-                const colFFView = this.getCollectionFFView(docId);
+            for (const [doc, firstMove] of docIdtoFirstMove) {
+                const colFFView = this.getCollectionFFView(doc);
                 if (colFFView) this.zoomAndPan(firstMove, colFFView);
             }
         };
@@ -200,12 +182,12 @@ export class ReplayMovements {
             const timeDiff = movement.time - timeViewed * 1000;
 
             return setTimeout(() => {
-                const collectionFFView = this.getCollectionFFView(movement.docId);
+                const collectionFFView = this.getCollectionFFView(movement.doc);
                 if (collectionFFView) {
                     this.zoomAndPan(movement, collectionFFView);
                 } else {
                     // tab wasn't open - open it and play the movement
-                    const openedColFFView = this.openTab(movement.docId, docIdtoDoc);
+                    const openedColFFView = this.openTab(movement.doc);
                     console.log('openedColFFView', openedColFFView);
                     openedColFFView && this.zoomAndPan(movement, openedColFFView);
                 }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 313c255a0..bfad93334 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,4 +1,3 @@
-import { ModalManager } from '@material-ui/core';
 import { action, observable, ObservableMap } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { Doc, Opt } from '../../fields/Doc';
@@ -64,8 +63,9 @@ export namespace SelectionManager {
     export function DeselectView(docView?: DocumentView): void {
         manager.DeselectView(docView);
     }
-    export function SelectView(docView: DocumentView, ctrlPressed: boolean): void {
-        manager.SelectView(docView, ctrlPressed);
+    export function SelectView(docView: DocumentView | undefined, ctrlPressed: boolean): void {
+        if (!docView) DeselectAll();
+        else manager.SelectView(docView, ctrlPressed);
     }
     export function SelectSchemaViewDoc(document: Opt<Doc>, deselectAllFirst?: boolean): void {
         if (deselectAllFirst) manager.DeselectAll();
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 3c05af4bb..a73eda04c 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -370,11 +370,10 @@ export class SharingManager extends React.Component<{}> {
         const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc];
         return (
             <span
-                className={'focus-span'}
+                className="focus-span"
                 title={title}
                 onClick={() => {
-                    let context: Opt<CollectionView>;
-                    if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
+                    if (this.targetDoc && this.targetDocView && docs.length === 1) {
                         DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
                     }
                 }}
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index 4a2ccd706..2f16307b3 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -9,7 +9,7 @@ export type Movement = {
     panX: number;
     panY: number;
     scale: number;
-    docId: string;
+    doc: Doc;
 };
 
 export type Presentation = {
@@ -28,7 +28,7 @@ export class TrackMovements {
     private tracking: boolean;
     private absoluteStart: number;
     // instance variable for holding the FFViews and their disposers
-    private recordingFFViews: Map<string, IReactionDisposer> | null;
+    private recordingFFViews: Map<Doc, IReactionDisposer> | null;
     private tabChangeDisposeFunc: IReactionDisposer | null;
 
     // create static instance and getter for global use
@@ -55,33 +55,33 @@ export class TrackMovements {
         return this.currentPresentation.movements === null;
     }
 
-    private addRecordingFFView(doc: Doc, key: string = doc[Id]): void {
+    private addRecordingFFView(doc: Doc): void {
         // console.info('adding dispose func : docId', key, 'doc', doc);
 
         if (this.recordingFFViews === null) {
             console.warn('addFFView on null RecordingApi');
             return;
         }
-        if (this.recordingFFViews.has(key)) {
-            console.warn('addFFView : key already in map');
+        if (this.recordingFFViews.has(doc)) {
+            console.warn('addFFView : doc already in map');
             return;
         }
 
         const disposeFunc = reaction(
             () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }),
-            res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale)
+            res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, doc, res.scale)
         );
-        this.recordingFFViews?.set(key, disposeFunc);
+        this.recordingFFViews?.set(doc, disposeFunc);
     }
 
-    private removeRecordingFFView = (key: string) => {
+    private removeRecordingFFView = (doc: Doc) => {
         // console.info('removing dispose func : docId', key);
         if (this.recordingFFViews === null) {
             console.warn('removeFFView on null RecordingApi');
             return;
         }
-        this.recordingFFViews.get(key)?.();
-        this.recordingFFViews.delete(key);
+        this.recordingFFViews.get(doc)?.();
+        this.recordingFFViews.delete(doc);
     };
 
     // in the case where only one tab was changed (updates not across dashboards), set only one to true
@@ -90,15 +90,15 @@ export class TrackMovements {
 
         // so that the size comparisons are correct, we must filter to only the FFViews
         const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform';
-        const tabbedFFViews = new Set<string>();
+        const tabbedFFViews = new Set<Doc>();
         for (const DashDoc of tabbedDocs) {
-            if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]);
+            if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
         }
 
         // new tab was added - need to add it
         if (tabbedFFViews.size > this.recordingFFViews.size) {
             for (const DashDoc of tabbedDocs) {
-                if (!this.recordingFFViews.has(DashDoc[Id])) {
+                if (!this.recordingFFViews.has(DashDoc)) {
                     if (isFFView(DashDoc)) {
                         this.addRecordingFFView(DashDoc);
 
@@ -110,9 +110,9 @@ export class TrackMovements {
         }
         // tab was removed - need to remove it from recordingFFViews
         else if (tabbedFFViews.size < this.recordingFFViews.size) {
-            for (const [key] of this.recordingFFViews) {
-                if (!tabbedFFViews.has(key)) {
-                    this.removeRecordingFFView(key);
+            for (const [doc] of this.recordingFFViews) {
+                if (!tabbedFFViews.has(doc)) {
+                    this.removeRecordingFFView(doc);
                     if (onlyOne) return;
                 }
             }
@@ -214,7 +214,7 @@ export class TrackMovements {
         }
     };
 
-    private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => {
+    private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => {
         // ensure we are recording to track
         if (!this.tracking) {
             console.error('[recordingApi.ts] trackMovements(): tracking is false');
@@ -231,7 +231,7 @@ export class TrackMovements {
         // get the time
         const time = new Date().getTime() - this.absoluteStart;
         // make new movement object
-        const movement: Movement = { time, panX, panY, scale, docId };
+        const movement: Movement = { time, panX, panY, scale, doc };
 
         // add that movement to the current presentation data's movement array
         this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 2b586b0e2..dee161931 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -109,17 +109,8 @@ export class DashboardView extends React.Component {
                     />
                 </div>
                 <div className="button-bar">
-                    <Button text="Cancel" borderRadius={10} hoverStyle={'gray'} fontSize={FontSize.SECONDARY} onClick={this.abortCreateNewDashboard} />
-                    <Button
-                        text="Create"
-                        borderRadius={10}
-                        backgroundColor={Colors.LIGHT_BLUE}
-                        hoverStyle={'darken'}
-                        fontSize={FontSize.SECONDARY}
-                        onClick={() => {
-                            this.createNewDashboard(this.newDashboardName!, this.newDashboardColor);
-                        }}
-                    />
+                    <Button text="Cancel" onClick={this.abortCreateNewDashboard} />
+                    <Button text="Create" onClick={() => this.createNewDashboard(this.newDashboardName!, this.newDashboardColor)} />
                 </div>
             </div>
         );
@@ -163,7 +154,7 @@ export class DashboardView extends React.Component {
                 <div className="dashboard-view">
                     <div className="left-menu">
                         <div className="new-dashboard-button">
-                            <Button icon={<FaPlus />} hoverStyle="darken" backgroundColor={Colors.LIGHT_BLUE} size={Size.MEDIUM} fontSize={FontSize.HEADER} text="New" onClick={() => this.setNewDashboardName('')} borderRadius={50} />
+                            <Button icon={<FaPlus />} size={Size.MEDIUM} text="New" onClick={() => this.setNewDashboardName('')} />
                         </div>
                         <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.MyDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)}>
                             My Dashboards
@@ -196,7 +187,7 @@ export class DashboardView extends React.Component {
                                                 e.stopPropagation();
                                                 this.onContextMenu(dashboard, e);
                                             }}>
-                                            <IconButton isCircle={true} size={Size.SMALL} hoverStyle="gray" icon={<FontAwesomeIcon color="black" size="lg" icon="bars" />} />
+                                            <Button size={Size.SMALL} icon={<FontAwesomeIcon color="black" size="lg" icon="bars" />} />
                                         </div>
                                     </div>
                                 </div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9fc1487a0..a59189fd2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -52,7 +52,6 @@ export function DocComponent<P extends DocComponentProps>() {
 interface ViewBoxBaseProps {
     Document: Doc;
     DataDoc?: Doc;
-    ContainingCollectionDoc: Opt<Doc>;
     DocumentView?: () => DocumentView;
     fieldKey: string;
     isSelected: (outsideReaction?: boolean) => boolean;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9ffbe083f..9a4aaf00e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -33,6 +33,7 @@ import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
 import { RichTextField } from '../../fields/RichTextField';
 import { LinkFollower } from '../util/LinkFollower';
+import _ = require('lodash');
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -303,8 +304,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         SelectionManager.DeselectAll();
     };
 
-    onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
-
+    onSelectorClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false);
     /**
      * Handles setting up events when user clicks on the border radius editor
      * @param e PointerEvent
@@ -744,7 +744,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             seldocview.props.hideDeleteButton ||
             seldocview.rootDoc.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
-                const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
+                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DataSym]) : AclEdit;
                 return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
             });
 
@@ -865,7 +865,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
                                     <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
 
-                                    {seldocview.props.renderDepth <= 1 || !seldocview.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
+                                    {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
                                 </>
                             )}
                             {useRounding && (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0feccb742..13faae783 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1010,14 +1010,12 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 renderDepth={0}
                 styleProvider={returnEmptyString}
                 docViewPath={returnEmptyDoclist}
-                focus={DocUtils.DefaultFocus}
+                focus={emptyFunction}
                 whenChildContentsActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
                 docRangeFilters={returnEmptyFilter}
                 docFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined}
             />
         );
     };
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index e6df0801c..156825f41 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -373,10 +373,11 @@ export class InkStrokeProperties {
 
     snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => {
         const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+        const containingDocView = inkView.props.CollectionFreeFormDocumentView?.().props.DocumentView?.();
         containingCollection?.childDocs
             .filter(doc => doc.type === DocumentType.INK)
             .forEach(doc => {
-                const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView);
+                const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView);
                 const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
                 if (snapped && snapped.distance < snapData.distance) {
                     const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 976c8763e..69eec8456 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -266,8 +266,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             docFilters={this.docFilters}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
                             addDocument={undefined}
                             removeDocument={undefined}
                             whenChildContentsActiveChanged={emptyFunction}
@@ -275,7 +273,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             pinToPres={TabDocView.PinDoc}
                             bringToFront={emptyFunction}
                             onBrowseClick={MainView.Instance.exploreMode}
-                            focus={DocUtils.DefaultFocus}
+                            focus={emptyFunction}
                         />
                     </GestureOverlay>
                 </div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 60459cf30..0384d925e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -595,14 +595,12 @@ export class MainView extends React.Component {
                     PanelWidth={this.headerBarDocWidth}
                     PanelHeight={this.headerBarDocHeight}
                     renderDepth={0}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
@@ -626,14 +624,12 @@ export class MainView extends React.Component {
                     ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
                     PanelWidth={this.mainDocViewWidth}
                     PanelHeight={this.mainDocViewHeight}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     suppressSetHeight={true}
                     renderDepth={this._hideUI ? 0 : -1}
                 />
@@ -727,14 +723,12 @@ export class MainView extends React.Component {
                         renderDepth={0}
                         isContentActive={returnTrue}
                         scriptContext={CollectionDockingView.Instance?.props.Document}
-                        focus={DocUtils.DefaultFocus}
+                        focus={emptyFunction}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
                         docFilters={returnEmptyFilter}
                         docRangeFilters={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
                 {this.docButtons}
@@ -758,7 +752,7 @@ export class MainView extends React.Component {
                     PanelHeight={this.leftMenuHeight}
                     renderDepth={0}
                     docViewPath={returnEmptyDoclist}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     styleProvider={DefaultStyleProvider}
                     isContentActive={returnTrue}
                     whenChildContentsActiveChanged={emptyFunction}
@@ -766,8 +760,6 @@ export class MainView extends React.Component {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     scriptContext={this}
                 />
             </div>
@@ -900,13 +892,11 @@ export class MainView extends React.Component {
                     PanelWidth={this.leftMenuFlyoutWidth}
                     PanelHeight={this.leftMenuFlyoutHeight}
                     renderDepth={0}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
                 {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
             </div>
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index ec22128d4..bdc48d03a 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, trace } from 'mobx';
+import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
@@ -10,15 +10,13 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, retu
 import { DocUtils } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from '../util/DragManager';
-import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
 import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView';
 import { LightboxView } from './LightboxView';
 import { MainView } from './MainView';
 import { DocumentView } from './nodes/DocumentView';
 import './OverlayView.scss';
-import { ScriptingRepl } from './ScriptingRepl';
-import { DefaultStyleProvider, testDocProps } from './StyleProvider';
+import { DefaultStyleProvider } from './StyleProvider';
 
 export type OverlayDisposer = () => void;
 
@@ -223,7 +221,7 @@ export class OverlayView extends React.Component {
                             isDocumentActive={returnTrue}
                             isContentActive={returnTrue}
                             whenChildContentsActiveChanged={emptyFunction}
-                            focus={DocUtils.DefaultFocus}
+                            focus={emptyFunction}
                             styleProvider={DefaultStyleProvider}
                             docViewPath={returnEmptyDoclist}
                             addDocTab={d.type === DocumentType.PRES ? MainView.addDocTabFunc : returnFalse}
@@ -231,8 +229,6 @@ export class OverlayView extends React.Component {
                             docFilters={returnEmptyFilter}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
                         />
                     </div>
                 );
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 954529bc9..3ad28c418 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -1,12 +1,12 @@
-import { IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc } from "../../fields/Doc";
-import { NumCast } from "../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { DocumentView } from "./nodes/DocumentView";
-import "./Palette.scss";
+import { IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../fields/Doc';
+import { NumCast } from '../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from '../../Utils';
+import { Transform } from '../util/Transform';
+import { DocumentView } from './nodes/DocumentView';
+import './Palette.scss';
 
 export interface PaletteProps {
     x: number;
@@ -23,20 +23,20 @@ export default class Palette extends React.Component<PaletteProps> {
     componentDidMount = () => {
         this._selectedDisposer = reaction(
             () => NumCast(this.props.thumbDoc.selectedIndex),
-            (i) => this._selectedIndex = i,
+            i => (this._selectedIndex = i),
             { fireImmediately: true }
         );
-    }
+    };
 
     componentWillUnmount = () => {
         this._selectedDisposer?.();
-    }
+    };
 
     render() {
         return (
             <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
                 <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
-                    <div className="palette-thumbContent" style={{ transform: `translate(-${(this._selectedIndex * 50) + 10}px, 0px)` }}>
+                    <div className="palette-thumbContent" style={{ transform: `translate(-${this._selectedIndex * 50 + 10}px, 0px)` }}>
                         <DocumentView
                             Document={this.props.thumbDoc}
                             DataDoc={undefined}
@@ -59,12 +59,11 @@ export default class Palette extends React.Component<PaletteProps> {
                             docFilters={returnEmptyFilter}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined} />
+                        />
                         <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
                     </div>
                 </div>
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 2c7da5931..93eec61f3 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -22,9 +22,9 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
     @computed get _docs() {
         if (!this.props.DocView) return [];
         const target = this.props.DocView.props.Document;
-        const targetContext = this.props.DocView.props.ContainingCollectionDoc;
+        const targetContext = this.props.DocView.props.docViewPath().lastElement()?.rootDoc;
         const aliases = DocListCast(target.aliases);
-        const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc && Cast(alias.context, Doc, null) !== targetContext).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set<Doc>());
+        const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.aliases));
         const containers = containerSets.reduce((p, set) => {
             set.map(s => p.add(s));
@@ -42,6 +42,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
         return doclayouts
             .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
             .filter(doc => !Doc.IsSystem(doc))
+            .filter(doc => doc !== targetContext)
             .map(doc => ({ col: doc, target }));
     }
 
@@ -53,8 +54,21 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
             col._panY = NumCast(target.y) + NumCast(target._height) / 2;
         }
         col.hidden = false;
-        this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
-        setTimeout(() => DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100);
+        //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
+        setTimeout(
+            () =>
+                this.props.DocView &&
+                DocFocusOrOpen(
+                    Doc.GetProto(this.props.DocView.props.Document),
+                    {
+                        //
+                        willZoomCentered: true,
+                        openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere,
+                    },
+                    col
+                ),
+            100
+        );
     };
 
     render() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index fbc7d7696..6582c3f2a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -341,8 +341,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         docFilters={returnEmptyFilter}
                         docRangeFilters={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
-                        ContainingCollectionDoc={undefined}
-                        ContainingCollectionView={undefined}
                         addDocument={returnFalse}
                         moveDocument={undefined}
                         removeDocument={returnFalse}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index b950b4860..739d6d819 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -18,7 +18,7 @@ import { TreeSort } from './collections/TreeView';
 import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { MainView } from './MainView';
-import { DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewProps, OpenWhere } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { SliderBox } from './nodes/SliderBox';
@@ -295,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.ContainingCollectionDoc?._useClusters
+                        : props?.docViewPath().lastElement()?.rootDoc._useClusters
                         ? `${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
                         ? `gray ${StrCast(doc.boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
@@ -316,7 +316,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
             const lock = () => {
-                if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) {
+                if (props?.docViewPath().lastElement()?.rootDoc?._viewType === CollectionViewType.Freeform) {
                     return doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon={'lock'} style={{ color: 'red' }} size="lg" />
@@ -383,10 +383,7 @@ export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps
         return doc._viewType === CollectionViewType.Docking ? null : (
             <>
                 {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => {
-                    doc.hidden = doc.hidden ? undefined : true;
-                    if (!doc.hidden) {
-                        DocFocusOrOpen(doc, props?.ContainingCollectionDoc);
-                    }
+                    DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc);
                 })}
             </>
         );
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 45db240a9..c5a501aa6 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -114,8 +114,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
                     CollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    ContainingCollectionView={undefined}
                     styleProvider={DefaultStyleProvider}
                     setHeight={returnFalse}
                     docViewPath={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index c0220f804..0e4556eb4 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -62,7 +62,6 @@ export class CollectionCarouselView extends CollectionSubView() {
                         onClick={this.onContentClick}
                         hideCaptions={showCaptions ? true : false}
                         renderDepth={this.props.renderDepth + 1}
-                        ContainingCollectionView={this.props.CollectionView}
                         LayoutTemplate={this.props.childLayoutTemplate}
                         LayoutTemplateString={this.props.childLayoutString}
                         Document={curDoc.layout}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 225743748..2007f73aa 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -134,8 +134,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
@@ -642,8 +640,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                         key="float"
                         style={{
                             backgroundColor: this.props.docView.layoutDoc.z ? '121212' : undefined,
-                            pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'none' : undefined,
-                            color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
+                            pointerEvents: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Freeform ? 'none' : undefined,
+                            color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
                         }}
                         onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}>
                         <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index aec0734b4..80e81bc1c 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -255,8 +255,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 hideTitle={this.props.childHideTitle?.()}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                ContainingCollectionView={this.props.CollectionView}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
@@ -632,8 +630,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                         docFilters={this.props.docFilters}
                         docRangeFilters={this.props.docRangeFilters}
                         searchFilterDocs={this.props.searchFilterDocs}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
             );
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index ba90ed8cd..fd9b0c0ce 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -30,7 +30,7 @@ export class CollectionPileView extends CollectionSubView() {
         // pileups are designed to go away when they are empty.
         this._disposers.selected = reaction(
             () => this.childDocs.length,
-            num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)
+            num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document)
         );
     }
     componentWillUnmount() {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index bbd81d06d..22a575989 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -815,8 +815,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={focusFunc}
                     isContentActive={returnFalse}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     searchFilterDocs={returnEmptyDoclist}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a85ee0e02..67f5dc9f4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -346,8 +346,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 hideTitle={this.props.childHideTitle?.()}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                ContainingCollectionView={this.props.CollectionView}
                 xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
                 yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
                 addDocument={this.props.addDocument}
@@ -678,8 +676,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         docFilters={this.props.docFilters}
                         docRangeFilters={this.props.docRangeFilters}
                         searchFilterDocs={this.props.searchFilterDocs}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
             );
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4a11e8f0b..92932fb61 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this.props.removeDocument?.(doc);
             if (ind > 0) {
                 FormattedTextBox.SelectOnLoad = prev[Id];
-                DocumentManager.Instance.getDocumentView(prev, this.props.CollectionView)?.select(false);
+                DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
             }
             return true;
         }
@@ -242,8 +242,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionDoc={this.doc}
-                ContainingCollectionView={this.props.CollectionView}
                 addDocument={returnFalse}
                 moveDocument={returnFalse}
                 removeDocument={returnFalse}
@@ -272,7 +270,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this,
             this.doc,
             this.props.DataDoc,
-            this.props.ContainingCollectionDoc,
+            undefined,
             undefined,
             addDoc,
             this.remove,
@@ -331,10 +329,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     isDocumentActive={returnTrue}
                     addDocument={this.props.addDocument}
                     moveDocument={this.props.moveDocument}
+                    removeDocument={this.props.removeDocument}
                     addDocTab={this.props.addDocTab}
                     pinToPres={emptyFunction}
                     rootSelected={this.props.isSelected}
-                    removeDocument={this.props.removeDocument}
                     ScreenToLocalTransform={Transform.Identity}
                     PanelWidth={this.return35}
                     PanelHeight={this.return35}
@@ -347,8 +345,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     docFilters={this.props.docFilters}
                     docRangeFilters={this.props.docRangeFilters}
                     searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index bc25ad43a..53fbcc3cc 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -279,7 +279,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             CollectionView: this,
         };
         return (
-            <div className={'collectionView'} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
+            <div
+                className="collectionView"
+                onContextMenu={this.onContextMenu}
+                style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.rootDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
                 {this.showIsTagged()}
                 {this.renderSubView(this.collectionViewType, props)}
             </div>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 458712999..ef8f395c5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -425,8 +425,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     hideTitle={this.props.keyValue}
                     Document={this._document}
                     DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     onBrowseClick={MainView.Instance.exploreMode}
                     waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick}
                     isContentActive={returnTrue}
@@ -580,8 +578,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                 <CollectionFreeFormView
                     Document={this.props.document}
                     CollectionView={undefined}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     docViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
@@ -603,7 +599,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     ScreenToLocalTransform={Transform.Identity}
                     renderDepth={0}
                     whenChildContentsActiveChanged={emptyFunction}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     styleProvider={TabMinimapView.miniStyleProvider}
                     addDocTab={this.props.addDocTab}
                     pinToPres={TabDocView.PinDoc}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 75e76019e..8b1dfc767 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -42,7 +42,7 @@ export interface TreeViewProps {
     prevSibling?: Doc;
     document: Doc;
     dataDoc?: Doc;
-    containerCollection: Doc;
+    treeViewParent: Doc;
     renderDepth: number;
     dropAction: dropActionType;
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
@@ -143,7 +143,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView;
     }
     @computed get MAX_EMBED_HEIGHT() {
-        return NumCast(this.props.containerCollection.maxEmbedHeight, 200);
+        return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
     }
     @computed get dataDoc() {
         return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym];
@@ -194,7 +194,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const ind = this.dataDoc[key].indexOf(doc);
 
         const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
-        res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false);
+        res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
         return res;
     };
 
@@ -385,7 +385,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         };
         const addDoc = inside ? localAdd : parentAddDoc;
         const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
-        const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd;
+        const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.freezeChildren).includes('add')) || forceAdd;
         if (canAdd) {
             this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
             const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
@@ -406,7 +406,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     getTransform = () => this.refTransform(this._tref.current);
     embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
     embeddedPanelHeight = () => {
-        const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+        const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
         return Math.min(
             layoutDoc[HeightSym](),
             this.MAX_EMBED_HEIGHT,
@@ -416,7 +416,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 return layoutDoc._fitWidth
                     ? !Doc.NativeHeight(layoutDoc)
                         ? NumCast(layoutDoc._height)
-                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
+                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
                     : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym]();
             })()
         );
@@ -452,7 +452,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     this,
                     doc,
                     undefined,
-                    this.props.containerCollection,
+                    this.props.treeViewParent,
                     this.props.prevSibling,
                     addDoc,
                     remDoc,
@@ -596,7 +596,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                                   this,
                                   this.layoutDoc,
                                   this.dataDoc,
-                                  this.props.containerCollection,
+                                  this.props.treeViewParent,
                                   this.props.prevSibling,
                                   addDoc,
                                   remDoc,
@@ -656,7 +656,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             this.onCheckedClick?.script.run(
                 {
                     this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
-                    heading: this.props.containerCollection.title,
+                    heading: this.props.treeViewParent.title,
                     checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check',
                     containingTreeView: this.props.treeView.props.Document,
                 },
@@ -724,9 +724,11 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     @observable headerEleWidth = 0;
-    @computed get headerElements() {
+    @computed get titleButtons() {
+        const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
         return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : (
             <>
+                {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
                 {this.doc.hideContextMenu ? null : (
                     <FontAwesomeIcon
                         title="context menu"
@@ -779,9 +781,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
 
-    onChildClick = () => {
-        return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
-    };
+    onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
 
     onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
 
@@ -906,7 +906,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 styleProvider={this.titleStyleProvider}
                 enableDragWhenActive={true}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
-                docViewPath={returnEmptyDoclist}
+                docViewPath={this.props.treeView.props.docViewPath}
                 treeViewDoc={this.props.treeView.props.Document}
                 addDocument={undefined}
                 addDocTab={this.props.addDocTab}
@@ -937,12 +937,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={this.props.treeView.props.CollectionView}
-                ContainingCollectionDoc={this.props.treeView.props.Document}
             />
         );
-
-        const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : ''));
         return (
             <>
                 <div
@@ -958,8 +954,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     {view}
                 </div>
                 <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}>
-                    {buttons} {/* hide and lock buttons */}
-                    {this.headerElements}
+                    {this.titleButtons}
                 </div>
             </>
         );
@@ -1017,8 +1012,6 @@ export class TreeView extends React.Component<TreeViewProps> {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionDoc={this.props.containerCollection}
-                    ContainingCollectionView={undefined}
                     addDocument={this.props.addDocument}
                     moveDocument={this.move}
                     removeDocument={this.props.removeDoc}
@@ -1131,7 +1124,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         childDocs: Doc[],
         treeView: CollectionTreeView,
         parentTreeView: CollectionTreeView | TreeView | undefined,
-        containerCollection: Doc,
+        treeViewParent: Doc,
         dataDoc: Doc | undefined,
         parentCollectionDoc: Doc | undefined,
         containerPrevSibling: Doc | undefined,
@@ -1162,19 +1155,19 @@ export class TreeView extends React.Component<TreeViewProps> {
         hierarchyIndex?: number[],
         renderCount?: number
     ) {
-        const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField);
+        const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField);
         if (viewSpecScript) {
             childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
         }
 
-        const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None));
+        const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeViewSortCriterion, TreeSort.None));
         const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
         const treeViewRefs = new Map<Doc, TreeView | undefined>();
         return docs
             .filter(child => child instanceof Doc)
             .map((child, i) => {
                 if (renderCount && i > renderCount) return null;
-                const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
+                const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
                 if (!pair.layout || pair.data instanceof Promise) {
                     return null;
                 }
@@ -1192,10 +1185,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }
                 };
                 const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
-                const outdent =
-                    parentCollectionDoc?._viewType !== CollectionViewType.Tree
-                        ? undefined
-                        : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
+                const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
                 const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
@@ -1208,7 +1198,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         ref={r => treeViewRefs.set(child, r ? r : undefined)}
                         document={pair.layout}
                         dataDoc={pair.data}
-                        containerCollection={containerCollection}
+                        treeViewParent={treeViewParent}
                         prevSibling={docs[i]}
                         // TODO: [AL] add these
                         hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
@@ -1220,7 +1210,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         onCheckedClick={onCheckedClick}
                         onChildClick={onChildClick}
                         renderDepth={renderDepth}
-                        removeDoc={StrCast(containerCollection.freezeChildren).includes('remove') ? undefined : remove}
+                        removeDoc={StrCast(treeViewParent.freezeChildren).includes('remove') ? undefined : remove}
                         addDocument={addDocument}
                         styleProvider={styleProvider}
                         panelWidth={rowWidth}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7ae7be3c8..a3f5e73fb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -262,7 +262,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true));
     };
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
@@ -319,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
         if (didMove) {
             const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
-            options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale);
+            (options.zoomScale || options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
             this.setPan(panX, panY, focusTime, 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
             return focusTime;
         }
@@ -378,19 +378,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 1
             );
             if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
-                const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height);
+                const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2;
                 const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
                 const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
 
                 let lastx = NumCast(refDoc.x);
                 let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
-                setTimeout(
-                    action(() =>
-                        sorted.slice(1).forEach((pair, i) => {
-                            lastx = pair.layout.x = lastx + deltax;
-                            lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
-                        })
-                    )
+                runInAction(() =>
+                    sorted.slice(1).forEach((pair, i) => {
+                        lastx = pair.layout.x = lastx + deltax;
+                        lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
+                    })
                 );
             }
         }
@@ -464,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0);
             if (ptsParent) {
                 const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
-                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
+                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined);
                 de.moveDocument = this.props.moveDocument;
@@ -872,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
 
         return this.childDocs
-            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
+            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
             .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
             .filter(
@@ -966,7 +964,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs
             .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
             .forEach(doc => {
-                const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke;
+                const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
                 const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
                 const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
                 const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -1121,7 +1119,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
-        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
+        const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
+        if (collectionDoc?._viewType !== CollectionViewType.Freeform || collectionDoc._panX !== undefined) {
             this.setPan(
                 NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
                 NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
@@ -1256,8 +1255,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 replica={entry.replica}
                 suppressSetHeight={this.layoutEngine ? true : false}
                 renderCutoffProvider={this.renderCutoffProvider}
-                ContainingCollectionView={this.props.CollectionView}
-                ContainingCollectionDoc={this.props.Document}
                 CollectionFreeFormView={this}
                 LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
                 LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
@@ -1779,7 +1776,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const lines = text.split('\n');
                 const height = 30 + 15 * lines.length;
 
-                this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
+                this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
             }
         }
     };
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index c7d9b6619..97eed7752 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -209,8 +209,6 @@ export class CollectionLinearView extends CollectionSubView() {
                     docFilters={this.props.docFilters}
                     docRangeFilters={this.props.docRangeFilters}
                     searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     hideResizeHandles={true}
                 />
             </div>
@@ -251,8 +249,7 @@ export class CollectionLinearView extends CollectionSubView() {
                                 self: this.rootDoc,
                                 _readOnly_: false,
                                 scriptContext: this.props.scriptContext,
-                                thisContainer: this.props.ContainingCollectionDoc,
-                                documentView: this.props.docViewPath().lastElement(),
+                                documentView: this.props.DocumentView?.(),
                             });
                             this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked;
                         })}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b73b1d779..78d3d1b6e 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -262,8 +262,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 docFilters={this.childDocFilters}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                ContainingCollectionView={this.props.CollectionView}
                 dontRegisterView={this.props.dontRegisterView}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 0cca83803..4d61dc272 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -261,8 +261,6 @@ export class CollectionMultirowView extends CollectionSubView() {
                 docFilters={this.childDocFilters}
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                ContainingCollectionView={this.props.CollectionView}
                 dontRegisterView={this.props.dontRegisterView}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index fd9bcf681..6d5a73e55 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -793,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 fitContentsToBox={returnTrue}
                                 dontCenter={'y'}
                                 onClickScriptDisable="always"
-                                focus={DocUtils.DefaultFocus}
+                                focus={emptyFunction}
                                 renderDepth={this.props.renderDepth + 1}
                                 rootSelected={this.rootSelected}
                                 PanelWidth={this.previewWidthFunc}
@@ -806,8 +806,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 searchFilterDocs={this.searchFilterDocs}
                                 styleProvider={DefaultStyleProvider}
                                 docViewPath={returnEmptyDoclist}
-                                ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-                                ContainingCollectionView={this.props.CollectionView}
                                 moveDocument={this.props.moveDocument}
                                 addDocument={this.addRow}
                                 removeDocument={this.props.removeDocument}
@@ -849,8 +847,6 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 Document={doc}
                                 DataDoc={dataDoc}
                                 renderDepth={this.props.schema.props.renderDepth + 1}
-                                ContainingCollectionView={this.props.schema.props.CollectionView}
-                                ContainingCollectionDoc={this.props.schema.Document}
                                 PanelWidth={this.tableWidthFunc}
                                 PanelHeight={this.rowHeightFunc}
                                 styleProvider={DefaultStyleProvider}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 2defaae00..856537927 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -29,7 +29,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get schemaDoc() {
-        return this.props.ContainingCollectionDoc!;
+        return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
     }
 
     @computed get rowIndex() {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 13e45963e..5f8ffe8b0 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -28,8 +28,6 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
-            ContainingCollectionView: undefined,
-            ContainingCollectionDoc: undefined,
             fieldKey: this.props.fieldKey,
             rootSelected: returnFalse,
             isSelected: returnFalse,
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 29e7cd3ad..d703c9595 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -44,25 +44,10 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
         }
 
         const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
-        dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-            docView.props.removeDocument?.(doc);
-            addDocument(doc);
-            return true;
-        };
-        const containingView = docView.props.ContainingCollectionView;
-        const finishDrag = (e: DragManager.DragCompleteEvent) =>
-            e.docDragData &&
-            (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => {
-                const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
-                if (dvs.length) {
-                    dvs.forEach(dv => droppedDocs.push(dv.props.Document));
-                } else {
-                    droppedDocs.push(Doc.MakeAlias(d));
-                }
-                return droppedDocs;
-            }, [] as Doc[]));
+        dragData.canEmbed = true;
+        dragData.moveDocument = (docView.props.docViewPath().lastElement()?.ComponentView as any)?.props.CollectionView?.moveDocument; // this is equal to docView.props.moveDocument, but moveDocument is not a defined prop of a DocumentView .. but maybe it should be?
 
-        DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
+        DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined);
     }
 }
 
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 7bdace2b6..5f2d4a7b6 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -85,15 +85,13 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         PanelWidth={this.getPWidth}
                         PanelHeight={this.getPHeight}
                         renderDepth={0}
-                        focus={DocUtils.DefaultFocus}
+                        focus={emptyFunction}
                         docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
                         docFilters={returnEmptyFilter}
                         docRangeFilters={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
             </div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 9bdb2cee7..24b9f3b25 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -160,8 +160,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     @action public float = () => {
-        const { Document: topDoc, ContainingCollectionView: container } = this.props;
-        const screenXf = container?.screenToLocalTransform();
+        const topDoc = this.rootDoc;
+        const containerDocView = this.props.docViewPath().lastElement();
+        const screenXf = containerDocView?.screenToLocalTransform();
         if (screenXf) {
             SelectionManager.DeselectAll();
             if (topDoc.z) {
@@ -178,7 +179,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 topDoc.x = fpt[0];
                 topDoc.y = fpt[1];
             }
-            setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0);
+            setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0);
         }
     };
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index dbcfe43cf..76a5ce7b3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -154,7 +154,6 @@ export class DocumentContentsView extends React.Component<
             // these are the properties in  DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
             'hideResizeHandles',
             'hideTitle',
-            'treeViewDoc',
             'contentPointerEvents',
             'radialMenu',
             'LayoutTemplateString',
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index df3299eef..47705d53d 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -142,7 +142,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             } else if (startLink !== endLink) {
                 endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
                 startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
-                const linkDoc = DocUtils.MakeLink(startLink, endLink, { linkRelationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined }, undefined, true);
+                const linkDoc = DocUtils.MakeLink(startLink, endLink, { linkRelationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
 
                 LinkManager.currentLink = linkDoc;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f161a7b9b..be07f1fe5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -148,8 +148,6 @@ export interface DocumentViewSharedProps {
     DataDoc?: Doc;
     contentBounds?: () => undefined | { x: number; y: number; r: number; b: number };
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitContentsToBox property on a Document
-    ContainingCollectionView: Opt<CollectionView>;
-    ContainingCollectionDoc: Opt<Doc>;
     suppressSetHeight?: boolean;
     thumbShown?: () => boolean;
     setContentView?: (view: DocComponentView) => any;
@@ -179,6 +177,7 @@ export interface DocumentViewSharedProps {
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
     canEmbedOnDrag?: boolean;
+    treeViewDoc?: Doc;
     xPadding?: number;
     yPadding?: number;
     dropAction?: dropActionType;
@@ -210,7 +209,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     hideOpenButton?: boolean;
     hideDeleteButton?: boolean;
     hideLinkAnchors?: boolean;
-    treeViewDoc?: Doc;
     isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
     isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
     contentPointerEvents?: string; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
@@ -429,7 +427,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             this: this.layoutDoc,
                             self: this.rootDoc,
                             scriptContext: this.props.scriptContext,
-                            thisContainer: this.props.ContainingCollectionDoc,
                             documentView: this.props.DocumentView(),
                             clientX, clientY,  altKey,  shiftKey,  ctrlKey,
                             value: undefined,
@@ -456,7 +453,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                                 self: this.rootDoc,
                                 _readOnly_: false,
                                 scriptContext: this.props.scriptContext,
-                                thisContainer: this.props.ContainingCollectionDoc,
                                 documentView: this.props.DocumentView(),
                                 clientX,
                                 clientY,
@@ -605,7 +601,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 }
                 if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
                     const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
-                    de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, undefined, [de.x, de.y - 50]);
+                    de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                 }
             }
         }
@@ -697,7 +693,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
 
-            if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
+            if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Tree) {
                 const existingOnClick = cm.findByDescription('OnClick...');
                 const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
 
@@ -1446,7 +1442,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
-    select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
+    select = (extendSelection: boolean) => SelectionManager.SelectView(this, extendSelection);
     NativeWidth = () => this.effectiveNativeWidth;
     NativeHeight = () => this.effectiveNativeHeight;
     PanelWidth = () => this.panelWidth;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8d3534a5c..86779e0dd 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -32,6 +32,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
     // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields
     backgroundColor?: string;
+    treeViewDoc?: Doc;
     color?: string;
     fontSize?: number;
     height?: number;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 94434dce7..7ea6d42ff 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,8 +61,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
-            ContainingCollectionView: undefined,
-            ContainingCollectionDoc: undefined,
             fieldKey: this.props.keyName,
             rootSelected: returnFalse,
             isSelected: returnFalse,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index fcc5b6975..c58b5dd8c 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -271,14 +271,12 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             docFilters={returnEmptyFilter}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
-                            ContainingCollectionDoc={undefined}
-                            ContainingCollectionView={undefined}
                             renderDepth={0}
                             suppressSetHeight={true}
                             PanelWidth={this.width}
                             PanelHeight={this.height}
                             pointerEvents={returnNone}
-                            focus={DocUtils.DefaultFocus}
+                            focus={emptyFunction}
                             whenChildContentsActiveChanged={returnFalse}
                             ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size.
                             bringToFront={returnFalse}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index db11a7776..e015024fd 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -305,8 +305,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             addDocument={returnFalse}
                             CollectionView={undefined}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                            renderDepth={this.props.renderDepth + 1}
-                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+                            renderDepth={this.props.renderDepth + 1}>
                             <>
                                 {this.threed}
                                 {this.content}
@@ -330,7 +329,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                                 moveDocument={returnFalse}
                                 addDocument={returnFalse}
                                 renderDepth={this.props.renderDepth + 1}
-                                ContainingCollectionDoc={this.props.ContainingCollectionDoc}></FormattedTextBox>
+                            />
                         )}
                     </div>
                 </div>
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 8eacfbc51..1a75a7e76 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -568,12 +568,12 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
     if (selectedViews.length) {
         if (checkResult) {
             const selView = selectedViews.lastElement();
-            const layoutFrameNumber = Cast(selView.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber).backgroundColor ?? 'transparent';
         }
         selectedViews.forEach(dv => {
-            const layoutFrameNumber = Cast(dv.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color });
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index c00ab6a7e..b31fc01ff 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -9,10 +9,9 @@ import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { ColorScheme } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
-import { DocumentView } from '../DocumentView';
+import { DocFocusOptions, DocumentView } from '../DocumentView';
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require('react');
-import { SelectionManager } from '../../../util/SelectionManager';
 
 export class DashDocView {
     dom: HTMLSpanElement; // container for label and value
@@ -151,7 +150,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
         const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
         return new Transform(-translateX, -translateY, 1).scale(1 / scale);
     };
-    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document, {}); // ideally, this would scroll to show the focus target
+    outerFocus = (target: Doc, options: DocFocusOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
 
     onKeyDown = (e: any) => {
         e.stopPropagation();
@@ -212,8 +211,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     docFilters={this.props.tbox?.props.docFilters}
                     docRangeFilters={this.props.tbox?.props.docRangeFilters}
                     searchFilterDocs={this.props.tbox?.props.searchFilterDocs}
-                    ContainingCollectionView={this._textBox.props.ContainingCollectionView}
-                    ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
                 />
             </div>
         );
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index f23426bb3..c43206629 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -235,10 +235,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     };
 
     createPivotForField = (e: React.MouseEvent) => {
-        let container = this.props.tbox.props.ContainingCollectionView;
-        while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
-            container = container.props.ContainingCollectionView;
-        }
+        let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement();
         if (container) {
             const alias = Doc.MakeAlias(container.props.Document);
             alias._viewType = CollectionViewType.Time;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 677c4662b..bc2a5d797 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -541,7 +541,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 // embed document when dragg marked as embed
             } else if (de.embedKey) {
                 const target = dragData.droppedDocuments[0];
-                target._fitContentsToBox = true;
                 const node = schema.nodes.dashDoc.create({
                     width: target[WidthSym](),
                     height: target[HeightSym](),
@@ -552,6 +551,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 if (!['alias', 'copy'].includes((dragData.dropAction ?? '') as any)) {
                     dragData.removeDocument?.(dragData.draggedDocuments[0]);
                 }
+                target._fitContentsToBox = true;
+                target.context = this.rootDoc;
                 const view = this._editorView!;
                 view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
                 e.stopPropagation();
@@ -880,7 +881,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this._editorView && this._recordingStart) {
             if (this._break) {
                 const textanchorFunc = () => {
-                    const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
+                    const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor', unrendered: true });
                     return this.addDocument(tanch) ? tanch : undefined;
                 };
                 const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
@@ -956,7 +957,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             let hadStart = start !== 0;
             frag.forEach((node, index) => {
                 const examinedNode = findAnchorNode(node, editor);
-                if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
+                if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
                     nodes.push(examinedNode.node);
                     !hadStart && (start = index + examinedNode.start);
                     hadStart = true;
@@ -971,9 +972,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 }
                 return undefined;
             }
+            if (node.type === this._editorView?.state.schema.nodes.dashDoc) {
+                if (node.attrs.docId === textAnchorId) {
+                    return { node, start: 0 };
+                }
+                return undefined;
+            }
             if (!node.isText) {
                 const content = findAnchorFrag(node.content, editor);
-                return { node: node.copy(content.frag), start: content.start };
+                if (content.frag.childCount) return { node: content.frag.childCount ? content.frag.child(0) : node, start: content.start };
             }
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
@@ -987,7 +994,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const ret = findAnchorFrag(editor.state.doc.content, editor);
 
             const content = (ret.frag as any)?.content;
-            if ((ret.frag.size > 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
+            if ((ret.frag.size || (content?.length && content[0].type === this._editorView.state.schema.nodes.dashDoc) || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
                 !options.instant && (this._focusSpeed = focusSpeed);
                 let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
                 if (ret.frag.firstChild) {
@@ -998,6 +1005,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
                 setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
                 setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
+                return focusSpeed;
+            } else {
+                return this.props.focus(this.rootDoc, options);
             }
         }
     };
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3589a9065..807a19771 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -2457,7 +2457,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         {mode !== CollectionViewType.Invalid ? (
                             <CollectionView
                                 {...this.props}
-                                ContainingCollectionDoc={this.props.Document}
                                 PanelWidth={this.props.PanelWidth}
                                 PanelHeight={this.panelHeight}
                                 childIgnoreNativeSize={true}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 92696240b..502eef373 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -39,7 +39,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Idea: this boolean will determine whether to automatically show the video when this preselement is selected.
     // @observable static showVideo: boolean = false;
     @computed get indexInPres() {
-        return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
+        return DocListCast(this.presBox?.[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
     } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
     @computed get expandViewHeight() {
         return 100;
@@ -51,11 +51,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.presBoxView?.selectedArray;
     }
     @computed get presBoxView() {
-        const vpath = this.props.docViewPath();
-        return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined;
+        return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox;
     }
     @computed get presBox() {
-        return this.props.ContainingCollectionDoc!;
+        return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
     }
     @computed get targetDoc() {
         return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc;
@@ -110,14 +109,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     docFilters={this.props.docFilters}
                     docRangeFilters={this.props.docRangeFilters}
                     searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                     rootSelected={returnTrue}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     fitContentsToBox={returnTrue}
                     moveDocument={this.props.moveDocument!}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     whenChildContentsActiveChanged={returnFalse}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
@@ -195,7 +192,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
         if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
         dragData.dropAction = 'move';
-        dragData.treeViewDoc = this.presBox._viewType === CollectionViewType.Tree ? this.props.ContainingCollectionDoc : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
+        dragData.treeViewDoc = this.presBox?._viewType === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
         dragData.moveDocument = this.props.moveDocument;
         const dragItem: HTMLElement[] = [];
         if (dragArray.length === 1) {
@@ -269,7 +266,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     removeItem = action((e: React.MouseEvent) => {
         e.stopPropagation();
-        if (this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
+        if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
             this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
         }
         this.props.removeDocument?.(this.rootDoc);
@@ -406,15 +403,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed
     get toolbarWidth(): number {
         const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
-        let width: number = NumCast(this.presBox._width);
+        let width: number = NumCast(this.presBox?._width);
         if (presBoxDocView) width = presBoxDocView.props.PanelWidth();
         if (width === 0) width = 300;
         return width;
     }
 
     @computed get presButtons() {
-        const presBox: Doc = this.presBox; //presBox
-        const presBoxColor: string = StrCast(presBox._backgroundColor);
+        const presBox = this.presBox; //presBox
+        const presBoxColor: string = StrCast(presBox?._backgroundColor);
         const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
         const targetDoc: Doc = this.targetDoc;
         const activeItem: Doc = this.rootDoc;
@@ -494,10 +491,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed get mainItem() {
         const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false;
-        const isCurrent: boolean = this.presBox._itemIndex === this.indexInPres;
+        const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
         const miniView: boolean = this.toolbarWidth <= 110;
-        const presBox: Doc = this.presBox; //presBox
-        const presBoxColor: string = StrCast(presBox._backgroundColor);
+        const presBox = this.presBox; //presBox
+        const presBoxColor: string = StrCast(presBox?._backgroundColor);
         const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
         const activeItem: Doc = this.rootDoc;
 
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index f2e9be61d..d63c25dbe 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -46,7 +46,7 @@ export class TopBar extends React.Component {
         return (
             <div className="topbar-left">
                 {Doc.ActiveDashboard ? (
-                    <IconButton onClick={this.navigateToHome} icon={<FontAwesomeIcon icon="home" />} isCircle={true} hoverStyle="gray" color={this.textColor} />
+                    <IconButton onClick={this.navigateToHome} icon={<FontAwesomeIcon icon="home" />} color={this.textColor} />
                 ) : (
                     <div className="logo-container">
                         <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo"></img>
@@ -55,18 +55,7 @@ export class TopBar extends React.Component {
                     </div>
                 )}
                 {Doc.ActiveDashboard && (
-                    <Button
-                        text="Explore"
-                        tooltip="Browsing mode for directly navigating to documents"
-                        fontSize={FontSize.SECONDARY}
-                        isActive={MainView.Instance._exploreMode}
-                        size={Size.SMALL}
-                        color={this.textColor}
-                        borderRadius={5}
-                        hoverStyle="gray"
-                        iconPosition="right"
-                        onClick={action(() => (MainView.Instance._exploreMode = !MainView.Instance._exploreMode))}
-                    />
+                    <Button text="Explore" tooltip="Browsing mode for directly navigating to documents" size={Size.SMALL} color={this.textColor} onClick={action(() => (MainView.Instance._exploreMode = !MainView.Instance._exploreMode))} />
                 )}
             </div>
         );
@@ -94,13 +83,8 @@ export class TopBar extends React.Component {
                 <Button
                     text={StrCast(Doc.ActiveDashboard.title)}
                     tooltip="Browsing mode for directly navigating to documents"
-                    fontSize={FontSize.SECONDARY}
                     size={Size.SMALL}
                     color={'white'}
-                    type="outline"
-                    backgroundColor={Colors.MEDIUM_BLUE}
-                    borderRadius={5}
-                    hoverStyle="none"
                     onClick={(e: React.MouseEvent) => {
                         const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
                         ContextMenu.Instance.addItem({ description: 'Open Dashboard View', event: this.navigateToHome, icon: 'edit' });
@@ -121,21 +105,13 @@ export class TopBar extends React.Component {
                     onClick={() => {
                         SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
                     }}
-                    type="outline"
-                    fontSize={FontSize.SECONDARY}
                     size={Size.SMALL}
-                    borderRadius={5}
-                    hoverStyle="gray"
                 />
                 {!Doc.noviceMode && (
                     <IconButton
-                        fontSize={FontSize.SECONDARY}
-                        isCircle={true}
                         tooltip="Work on a copy of the dashboard layout"
                         size={Size.SMALL}
                         color={this.textColor}
-                        borderRadius={Borders.STANDARD}
-                        hoverStyle="gray"
                         onClick={async () => {
                             const batch = UndoManager.StartBatch('snapshot');
                             await DashboardView.snapshotDashboard();
@@ -156,26 +132,9 @@ export class TopBar extends React.Component {
     @computed get topbarRight() {
         return (
             <div className="topbar-right">
-                <IconButton size={Size.SMALL} isCircle={true} color={Colors.LIGHT_GRAY} backgroundColor={Colors.DARK_GRAY} hoverStyle="gray" onClick={() => ReportManager.Instance.open()} icon={<FaBug />} />
-                <IconButton
-                    size={Size.SMALL}
-                    isCircle={true}
-                    color={Colors.LIGHT_GRAY}
-                    backgroundColor={Colors.DARK_GRAY}
-                    hoverStyle="gray"
-                    onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')}
-                    icon={<FontAwesomeIcon icon="question-circle" />}
-                />
-                <IconButton
-                    size={Size.SMALL}
-                    isCircle={true}
-                    color={Colors.LIGHT_GRAY}
-                    backgroundColor={Colors.DARK_GRAY}
-                    hoverStyle="gray"
-                    isActive={SettingsManager.Instance.isOpen}
-                    onClick={() => SettingsManager.Instance.open()}
-                    icon={<FontAwesomeIcon icon="cog" />}
-                />
+                <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => ReportManager.Instance.open()} icon={<FaBug />} />
+                <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+                <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => SettingsManager.Instance.open()} icon={<FontAwesomeIcon icon="cog" />} />
                 {/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.textColor} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */}
             </div>
         );
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 64baa351c..a0b437354 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -13,13 +13,18 @@ import { listSpec } from '../fields/Schema';
 import { Cast, FieldValue } from '../fields/Types';
 import { nullAudio } from '../fields/URLField';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyPath } from '../Utils';
-import "./ImageUpload.scss";
+import './ImageUpload.scss';
 import { MobileInterface } from './MobileInterface';
 import React = require('react');
 
 @observer
 export class AudioUpload extends React.Component {
-    @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
+    @observable public _audioCol: Doc = FieldValue(
+        Cast(
+            Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: 'mobile audio', _width: 500, _height: 100 }), Doc) as Doc], { title: 'mobile audio', _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: '0 0' }),
+            Doc
+        )
+    ) as Doc;
 
     /**
      * Handles the onclick functionality for the 'Restart' button
@@ -28,25 +33,36 @@ export class AudioUpload extends React.Component {
     @action
     clearUpload = () => {
         for (let i = 1; i < 8; i++) {
-            this.setOpacity(i, "0.2");
+            this.setOpacity(i, '0.2');
         }
-        this._audioCol = FieldValue(Cast(
-            Docs.Create.FreeformDocument(
-                [Cast(Docs.Create.AudioDocument(nullAudio, {
-                    title: "mobile audio",
-                    _width: 500,
-                    _height: 100
-                }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
-    }
+        this._audioCol = FieldValue(
+            Cast(
+                Docs.Create.FreeformDocument(
+                    [
+                        Cast(
+                            Docs.Create.AudioDocument(nullAudio, {
+                                title: 'mobile audio',
+                                _width: 500,
+                                _height: 100,
+                            }),
+                            Doc
+                        ) as Doc,
+                    ],
+                    { title: 'mobile audio', _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: '0 0' }
+                ),
+                Doc
+            )
+        ) as Doc;
+    };
 
-    /** 
+    /**
      * Handles the onClick of the 'Close' button
      * Reset upload interface and toggle audio
      */
     closeUpload = () => {
         this.clearUpload();
         MobileInterface.Instance.toggleAudio();
-    }
+    };
 
     /**
      * Handles the on click of the 'Upload' button.
@@ -57,14 +73,14 @@ export class AudioUpload extends React.Component {
         const audioDoc = this._audioCol;
         const data = Cast(audioRightSidebar.data, listSpec(Doc));
         for (let i = 1; i < 8; i++) {
-            setTimeout(() => this.setOpacity(i, "1"), i * 200);
+            setTimeout(() => this.setOpacity(i, '1'), i * 200);
         }
         if (data) {
             data.push(audioDoc);
         }
         // Resets uploader after 3 seconds
         setTimeout(this.clearUpload, 3000);
-    }
+    };
 
     // Returns the upload audio menu
     private get uploadInterface() {
@@ -72,11 +88,13 @@ export class AudioUpload extends React.Component {
             <>
                 <ContextMenu />
                 <DictationOverlay />
-                <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
+                <div style={{ display: 'none' }}>
+                    <RichTextMenu key="rich" />
+                </div>
                 <div className="closeUpload" onClick={() => this.closeUpload()}>
-                    <FontAwesomeIcon icon="window-close" size={"lg"} />
+                    <FontAwesomeIcon icon="window-close" size={'lg'} />
                 </div>
-                <FontAwesomeIcon icon="microphone" size="lg" style={{ fontSize: "130" }} />
+                <FontAwesomeIcon icon="microphone" size="lg" style={{ fontSize: '130' }} />
                 <div className="audioUpload_cont">
                     <DocumentView
                         Document={this._audioCol}
@@ -96,12 +114,10 @@ export class AudioUpload extends React.Component {
                         isDocumentActive={returnTrue}
                         isContentActive={emptyFunction}
                         focus={emptyFunction}
-                        styleProvider={() => "rgba(0,0,0,0)"}
+                        styleProvider={() => 'rgba(0,0,0,0)'}
                         docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
                     />
                 </div>
                 <div className="restart_label" onClick={this.clearUpload}>
@@ -125,28 +141,16 @@ export class AudioUpload extends React.Component {
 
     // Handles the setting of the loading bar
     setOpacity = (index: number, opacity: string) => {
-        const slab = document.getElementById("slab0" + index);
+        const slab = document.getElementById('slab0' + index);
         if (slab) {
             slab.style.opacity = opacity;
         }
-    }
+    };
 
     @observable private dialogueBoxOpacity = 1;
     @observable private overlayOpacity = 0.4;
 
     render() {
-        return (
-            <MainViewModal
-                contents={this.uploadInterface}
-                isDisplayed={true}
-                interactive={true}
-                dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                overlayDisplayedOpacity={this.overlayOpacity}
-                closeOnExternalClick={this.closeUpload}
-            />
-        );
+        return <MainViewModal contents={this.uploadInterface} isDisplayed={true} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
     }
-
 }
-
-
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 2ae597b0b..a543c2f70 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -397,7 +397,7 @@ export class MobileInterface extends React.Component {
                     renderDepth={0}
                     isDocumentActive={returnTrue}
                     isContentActive={emptyFunction}
-                    focus={DocUtils.DefaultFocus}
+                    focus={emptyFunction}
                     styleProvider={this.whitebackground}
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
@@ -405,8 +405,6 @@ export class MobileInterface extends React.Component {
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
                 />
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From 650cc00d18c97f89aef77f50703aa9b525dd0368 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 14 Apr 2023 11:12:49 -0400
Subject: got rid of CollectionView prop.  fixed DocFocusOrOpen to toggle
 targets and zoom correctly.

---
 src/client/util/DocumentManager.ts                  | 12 +++++++-----
 .../util/Import & Export/DirectoryImportBox.tsx     |  2 +-
 src/client/util/LinkFollower.ts                     |  2 +-
 src/client/views/MainView.tsx                       |  1 -
 src/client/views/PropertiesDocBacklinksSelector.tsx |  2 +-
 src/client/views/PropertiesDocContextSelector.tsx   | 21 +--------------------
 src/client/views/SidebarAnnos.tsx                   |  1 -
 src/client/views/StyleProvider.tsx                  |  4 ++--
 src/client/views/TemplateMenu.tsx                   |  1 -
 src/client/views/collections/CollectionMenu.tsx     |  1 -
 .../views/collections/CollectionNoteTakingView.tsx  |  2 +-
 .../views/collections/CollectionStackingView.tsx    |  2 +-
 src/client/views/collections/CollectionSubView.tsx  |  6 +++---
 src/client/views/collections/CollectionTreeView.tsx |  6 +++---
 src/client/views/collections/CollectionView.tsx     |  1 -
 src/client/views/collections/TabDocView.tsx         |  1 -
 src/client/views/collections/TreeView.tsx           |  6 +++---
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 11 +++++++----
 src/client/views/linking/LinkMenuItem.tsx           |  2 +-
 src/client/views/nodes/AudioBox.tsx                 |  1 -
 src/client/views/nodes/DocumentView.tsx             | 14 +++++++-------
 src/client/views/nodes/ImageBox.tsx                 |  1 -
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx  |  1 -
 src/client/views/nodes/PDFBox.tsx                   |  1 -
 src/client/views/nodes/ScreenshotBox.tsx            |  1 -
 src/client/views/nodes/VideoBox.tsx                 |  2 --
 src/client/views/nodes/WebBox.tsx                   |  1 -
 .../views/nodes/formattedText/FormattedTextBox.tsx  |  1 -
 src/client/views/pdf/PDFViewer.tsx                  |  1 -
 29 files changed, 39 insertions(+), 69 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ad89e8653..e01457b4f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -227,6 +227,7 @@ export class DocumentManager {
         let rootContextView = docViewPath.shift();
         await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
         if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
+        else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
     };
 
     // shows a document by first:
@@ -246,10 +247,9 @@ export class DocumentManager {
             const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
             if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!);
             options.didMove = true;
-            docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation as OpenWhere);
+            docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
             this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
         });
-
         docContextPath.shift();
         const childViewIterator = async (docView: DocumentView) => {
             const innerDoc = docContextPath.shift();
@@ -297,15 +297,17 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) {
+export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
     const func = () => {
         const cv = DocumentManager.Instance.getDocumentView(containingDoc);
         const dv = DocumentManager.Instance.getDocumentView(doc, cv);
         if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
             DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
         } else {
-            const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc));
-            DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => {
+            const container = DocCast(containingDoc ?? doc.context);
+            const showDoc = !Doc.IsSystem(container) ? container : doc;
+            options.toggleTarget = undefined;
+            DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
                 const cv = DocumentManager.Instance.getDocumentView(containingDoc);
                 const dv = DocumentManager.Instance.getDocumentView(doc, cv);
                 dv && Doc.linkFollowHighlight(dv.rootDoc);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 76b1323c4..b9bb22564 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -157,7 +157,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset,
         };
-        const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it.
+        const parent = this.props.DocumentView?.().props.docViewPath().lastElement();
         if (parent?.rootDoc.type === DocumentType.COL) {
             let importContainer: Doc;
             if (docs.length < 50) {
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index d4d7c66f5..ba2edb65c 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -75,7 +75,7 @@ export class LinkFollower {
                         zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500),
                         zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null),
                         easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
-                        openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox),
+                        openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox) as OpenWhere,
                         effect: sourceDoc,
                         zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
                     };
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0384d925e..ccc9a7215 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -883,7 +883,6 @@ export class MainView extends React.Component {
                     isSelected={returnFalse}
                     docViewPath={returnEmptyDoclist}
                     moveDocument={this.moveButtonDoc}
-                    CollectionView={undefined}
                     addDocument={this.addButtonDoc}
                     addDocTab={MainView.addDocTabFunc}
                     pinToPres={emptyFunction}
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 00c3400cb..935d551c6 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
 
         if (otherdoc) {
             otherdoc.hidden = false;
-            this.props.addDocTab(Doc.IsPrototype(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
+            this.props.addDocTab(Doc.IsPrototype(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, OpenWhere.toggleRight);
         }
     };
 
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 93eec61f3..c14d7ef73 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -49,26 +49,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
     getOnClick = (col: Doc, target: Doc) => {
         if (!this.props.DocView) return;
         col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
-        if (col._viewType === CollectionViewType.Freeform) {
-            col._panX = NumCast(target.x) + NumCast(target._width) / 2;
-            col._panY = NumCast(target.y) + NumCast(target._height) / 2;
-        }
-        col.hidden = false;
-        //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
-        setTimeout(
-            () =>
-                this.props.DocView &&
-                DocFocusOrOpen(
-                    Doc.GetProto(this.props.DocView.props.Document),
-                    {
-                        //
-                        willZoomCentered: true,
-                        openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere,
-                    },
-                    col
-                ),
-            100
-        );
+        DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col);
     };
 
     render() {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 2d2b0f83e..b0aab968e 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -243,7 +243,6 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         removeDocument={this.removeDocument}
                         moveDocument={this.moveDocument}
                         addDocument={this.addDocument}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         renderDepth={this.props.renderDepth + 1}
                         viewType={CollectionViewType.Stacking}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 739d6d819..5f16e0ebd 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -5,7 +5,7 @@ import { Shadows } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { extname } from 'path';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
-import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
 import { DashColor, lightOrDark, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
@@ -383,7 +383,7 @@ export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps
         return doc._viewType === CollectionViewType.Docking ? null : (
             <>
                 {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => {
-                    DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc);
+                    DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.context ?? doc?.annotationOn));
                 })}
             </>
         );
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index c5a501aa6..1c816aa05 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -113,7 +113,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 {templateMenu}
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
-                    CollectionView={undefined}
                     styleProvider={DefaultStyleProvider}
                     setHeight={returnFalse}
                     docViewPath={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 2007f73aa..5b631676e 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -120,7 +120,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     isSelected={returnFalse}
                     docViewPath={returnEmptyDoclist}
                     moveDocument={returnFalse}
-                    CollectionView={undefined}
                     addDocument={returnFalse}
                     addDocTab={returnFalse}
                     pinToPres={emptyFunction}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 80e81bc1c..99d4d0bee 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -162,7 +162,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     }
 
     @action
-    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
         return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
     };
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 67f5dc9f4..bdad325d5 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -230,7 +230,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     isAnyChildContentActive = () => this.props.isAnyChildContentActive();
 
     @action
-    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
         return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
     };
     createRef = (ele: HTMLDivElement | null) => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 132ed6fb6..5581ac8fe 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -19,7 +19,6 @@ import { DocComponent } from '../DocComponent';
 import React = require('react');
 
 export interface SubCollectionViewProps extends CollectionViewProps {
-    CollectionView: Opt<CollectionView>;
     isAnyChildContentActive: () => boolean;
 }
 
@@ -201,8 +200,9 @@ export function CollectionSubView<X>(moreProps?: X) {
             }
         }
 
-        addDocument = (doc: Doc | Doc[]) => this.props.addDocument?.(doc) || false;
-
+        addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false;
+        removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false;
+        moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument);
         @action
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 92932fb61..f81c17a7b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -374,10 +374,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
     panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
 
-    addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
-    remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
+    addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
+    remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
     moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
-        this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
+        this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
 
     @observable _headerHeight = 0;
     @computed get content() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 53fbcc3cc..d7a889362 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -276,7 +276,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             childLayoutString: this.childLayoutString,
             childHideResizeHandles: this.childHideResizeHandles,
             childHideDecorationTitle: this.childHideDecorationTitle,
-            CollectionView: this,
         };
         return (
             <div
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ef8f395c5..4bbc3bb44 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -577,7 +577,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
             <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
                 <CollectionFreeFormView
                     Document={this.props.document}
-                    CollectionView={undefined}
                     docViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 8b1dfc767..8fb610b87 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -48,7 +48,7 @@ export interface TreeViewProps {
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     panelWidth: () => number;
     panelHeight: () => number;
-    addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
+    addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean;
     removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined;
     moveDocument: DragManager.MoveFunction;
     isContentActive: (outsideReaction?: boolean) => boolean;
@@ -373,7 +373,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     dropping: boolean = false;
     dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
-        const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
+        const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before);
         const localAdd = (doc: Doc | Doc[]) => {
             const innerAdd = (doc: Doc) => {
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
@@ -1186,7 +1186,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 };
                 const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
                 const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
-                const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
+                const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
                     const aspect = Doc.NativeAspect(childLayout);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a3f5e73fb..33fc2ddf3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -311,7 +311,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
         const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
-        const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined);
+        const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined);
 
         // focus on the document in the collection
         const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
@@ -319,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
         if (didMove) {
             const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
-            (options.zoomScale || options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
+            (options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
             this.setPan(panX, panY, focusTime, 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
             return focusTime;
         }
@@ -1181,9 +1181,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
 
-        if (scale) {
+        if (scale !== undefined) {
             const maxZoom = 5; // 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
-            const newScale = Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+            const newScale =
+                scale === 0
+                    ? NumCast(this.layoutDoc[this.scaleFieldKey])
+                    : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
             return {
                 panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
                 panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d703c9595..19d6c2ae2 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -45,7 +45,7 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
 
         const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
         dragData.canEmbed = true;
-        dragData.moveDocument = (docView.props.docViewPath().lastElement()?.ComponentView as any)?.props.CollectionView?.moveDocument; // this is equal to docView.props.moveDocument, but moveDocument is not a defined prop of a DocumentView .. but maybe it should be?
+        dragData.dropAction = 'alias';
 
         DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined);
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index a6acf882c..68fb19208 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -663,7 +663,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 startTag={'_timecodeToShow' /* audioStart */}
                 endTag={'_timecodeToHide' /* audioEnd */}
                 bringToFront={emptyFunction}
-                CollectionView={undefined}
                 playFrom={this.playFrom}
                 setTime={this.setPlayheadTime}
                 playing={this.playing}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index be07f1fe5..7107707d1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -22,7 +22,7 @@ import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
-import { FollowLinkScript, LinkFollower } from '../../util/LinkFollower';
+import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -30,7 +30,6 @@ import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionView } from '../collections/CollectionView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from '../DocComponent';
@@ -69,6 +68,7 @@ export enum OpenWhere {
     close = 'close',
     fullScreen = 'fullScreen',
     toggle = 'toggle',
+    toggleRight = 'toggle:right',
     replace = 'replace',
     replaceRight = 'replace:right',
     replaceLeft = 'replace:left',
@@ -87,7 +87,7 @@ export enum OpenWhereMod {
 
 export interface DocFocusOptions {
     willPan?: boolean; // determines whether to pan to target document
-    willZoomCentered?: boolean; // determines whether to zoom in on target document
+    willZoomCentered?: boolean; // determines whether to zoom in on target document.  if zoomScale is 0, this just centers the document
     zoomScale?: number; // percent of containing frame to zoom into document
     zoomTime?: number;
     didMove?: boolean; // whether a document was changed during the showDocument process
@@ -97,7 +97,7 @@ export interface DocFocusOptions {
     effect?: Doc; // animation effect for focus
     noSelect?: boolean; // whether target should be selected after focusing
     playAudio?: boolean; // whether to play audio annotation on focus
-    openLocation?: string; // where to open a missing document
+    openLocation?: OpenWhere; // where to open a missing document
     zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
     toggleTarget?: boolean; // whether to toggle target on and off
     anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
@@ -170,9 +170,9 @@ export interface DocumentViewSharedProps {
     rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
-    addDocument?: (doc: Doc | Doc[]) => boolean;
-    removeDocument?: (doc: Doc | Doc[]) => boolean;
-    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
     pinToPres: (document: Doc, pinProps: PinProps) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 22ecaa299..7c98aa6e4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -478,7 +478,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     renderDepth={this.props.renderDepth + 1}
                     fieldKey={this.annotationKey}
                     styleProvider={this.props.styleProvider}
-                    CollectionView={undefined}
                     isAnnotationOverlay={true}
                     annotationLayerHostsContent={true}
                     PanelWidth={this.props.PanelWidth}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index d65ef9c4c..7b437c7de 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -57,7 +57,6 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                             Document={this.props.place}
                             DataDoc={undefined}
                             fieldKey="data"
-                            CollectionView={undefined}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             docFilters={returnEmptyFilter}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 502811e35..8f87b9e08 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -547,7 +547,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         removeDocument={this.sidebarRemDocument}
                         moveDocument={this.sidebarMoveDocument}
                         addDocument={this.sidebarAddDocument}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={this.sidebarScreenToLocal}
                         renderDepth={this.props.renderDepth + 1}
                         noSidebar={true}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index e015024fd..aa2b22e28 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -303,7 +303,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
                             addDocument={returnFalse}
-                            CollectionView={undefined}
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                             renderDepth={this.props.renderDepth + 1}>
                             <>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b144c9318..7a7d4fe37 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -963,7 +963,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     startTag={'_timecodeToShow' /* videoStart */}
                     endTag={'_timecodeToHide' /* videoEnd */}
                     bringToFront={emptyFunction}
-                    CollectionView={undefined}
                     playFrom={this.playFrom}
                     setTime={this.setPlayheadTime}
                     playing={this.playing}
@@ -1074,7 +1073,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             NativeHeight={returnZero}
                             renderDepth={this.props.renderDepth + 1}
                             fieldKey={this.annotationKey}
-                            CollectionView={undefined}
                             isAnnotationOverlay={true}
                             annotationLayerHostsContent={true}
                             PanelWidth={this.panelWidth}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 66d0fd385..ef3aa1f47 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -915,7 +915,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 renderDepth={this.props.renderDepth + 1}
                 isAnnotationOverlay={true}
                 fieldKey={this.annotationKey}
-                CollectionView={undefined}
                 setPreviewCursor={this.setPreviewCursor}
                 setBrushViewer={this.setBrushViewer}
                 PanelWidth={this.panelWidth}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bc2a5d797..5719ea83c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1844,7 +1844,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         removeDocument={this.sidebarRemDocument}
                         moveDocument={this.sidebarMoveDocument}
                         addDocument={this.sidebarAddDocument}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={this.sidebarScreenToLocal}
                         renderDepth={this.props.renderDepth + 1}
                         setHeight={this.setSidebarHeight}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6ab541207..d82a7d1ae 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -530,7 +530,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 renderDepth={this.props.renderDepth + 1}
                 isAnnotationOverlay={true}
                 fieldKey={this.props.fieldKey + '-annotations'}
-                CollectionView={undefined}
                 getScrollHeight={this.getScrollHeight}
                 setPreviewCursor={this.setPreviewCursor}
                 setBrushViewer={this.setBrushViewer}
-- 
cgit v1.2.3-70-g09d2


From 33299a19d86948051eef442825c0b3241d1ac619 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 24 Apr 2023 11:55:53 -0400
Subject: fixed isContentActive=false to apply to stacking collections. fixed
 pile view to be faster and to work in fit content panels. fixed issues with
 hidden docs and freeformviews that act as lightboxes - hidden docs can be
 shown as the lightbox doc without modifying the hidden flag to allow
 collection state to be restored.

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/CurrentUserUtils.ts                |  12 +--
 src/client/util/DocumentManager.ts                 |  15 ++-
 src/client/views/DocComponent.tsx                  | 104 ++++++++++----------
 src/client/views/LightboxView.tsx                  |   6 +-
 src/client/views/MainView.tsx                      |  18 ++--
 src/client/views/PropertiesButtons.tsx             |   7 +-
 src/client/views/StyleProvider.tsx                 |   5 +-
 .../views/collections/CollectionPileView.tsx       |  35 +++----
 .../views/collections/CollectionStackingView.tsx   |  15 ++-
 .../views/collections/CollectionTimeView.tsx       |   2 +-
 src/client/views/collections/CollectionView.tsx    |   1 +
 src/client/views/collections/TreeView.tsx          |   2 -
 .../CollectionFreeFormLayoutEngines.tsx            |  23 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 107 ++++++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |   8 +-
 src/client/views/nodes/DocumentView.tsx            |  57 ++++++-----
 src/client/views/nodes/ImageBox.scss               |   4 +-
 src/client/views/nodes/ImageBox.tsx                |   1 +
 src/client/views/nodes/WebBox.scss                 |   1 +
 .../views/nodes/formattedText/DashDocView.tsx      |   2 +
 .../nodes/formattedText/FormattedTextBox.scss      |  30 ++++++
 .../views/nodes/formattedText/FormattedTextBox.tsx |  47 ++++++++-
 src/fields/util.ts                                 |   3 +
 24 files changed, 315 insertions(+), 192 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a0149eadf..dee5feebc 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1066,7 +1066,7 @@ export namespace Docs {
         }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', enableDragWhenActive: true, _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index abf7313a4..cdb12624f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -619,12 +619,12 @@ export class CurrentUserUtils {
     }
     static viewTools(): Button[] {
         return [
-            { title: "Grid",         icon: "border-all",  toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Snap\xA0Lines",icon: "th",          toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "View\xA0All",  icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Clusters",     icon: "braille",     toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Arrange",      icon: "window",      toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Reset",        icon: "check",       toolTip: "Reset View",          btnType: ButtonType.ClickButton,  expertMode: false, backgroundColor:"transparent",    scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform
+            { title: "Grid",          icon: "border-all",   toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Snap\xA0Lines", icon: "th",           toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "View\xA0All",   icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Clusters",      icon: "braille",      toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Arrange",icon: "arrow-down-short-wide",toolTip: "Toggle Auto Arrange",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Reset",         icon: "check",        toolTip: "Reset View",          btnType: ButtonType.ClickButton,  expertMode: false, backgroundColor:"transparent",    scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform
         ]
     }
     static textTools():Button[] {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e01457b4f..4542c1c05 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -8,8 +8,7 @@ import { CollectionViewType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
-import { MainView } from '../views/MainView';
-import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
 import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { PresBox } from '../views/nodes/trails';
@@ -227,7 +226,7 @@ export class DocumentManager {
         let rootContextView = docViewPath.shift();
         await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
         if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
-        else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
+        else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
     };
 
     // shows a document by first:
@@ -247,9 +246,17 @@ export class DocumentManager {
             const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
             if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!);
             options.didMove = true;
-            docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+            docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
             this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
         });
+        if (options.openLocation === OpenWhere.lightbox) {
+            // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
+            const target = DocCast(targetDoc.annotationOn, targetDoc);
+            const contextView = this.getDocumentView(DocCast(target.context));
+            if (contextView?.docView?._componentView?.addDocTab?.(target, OpenWhere.lightbox)) {
+                await new Promise<void>(waitres => setTimeout(() => waitres()));
+            }
+        }
         docContextPath.shift();
         const childViewIterator = async (docView: DocumentView) => {
             const innerDoc = docContextPath.shift();
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index a59189fd2..d60ad68c6 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -141,29 +141,25 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
             const indocs = doc instanceof Doc ? [doc] : doc;
             const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
-            if (docs.length) {
-                docs.map(doc => {
-                    Doc.SetInPlace(doc, 'followLinkToggle', undefined, true);
-                    doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true);
+
+            docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
+            const targetDataDoc = this.dataDoc;
+            const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
+            const toRemove = value.filter(v => docs.includes(v));
+
+            if (toRemove.length !== 0) {
+                const recent = Doc.MyRecentlyClosed;
+                toRemove.forEach(doc => {
+                    leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
+                    Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+                    doc.context = undefined;
+                    if (recent) {
+                        Doc.RemoveDocFromList(recent, 'data', doc);
+                        doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
+                    }
                 });
-                const targetDataDoc = this.dataDoc;
-                const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
-                const toRemove = value.filter(v => docs.includes(v));
-
-                if (toRemove.length !== 0) {
-                    const recent = Doc.MyRecentlyClosed;
-                    toRemove.forEach(doc => {
-                        leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
-                        Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
-                        doc.context = undefined;
-                        if (recent) {
-                            Doc.RemoveDocFromList(recent, 'data', doc);
-                            doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
-                        }
-                    });
-                    this.isAnyChildContentActive() && this.props.select(false);
-                    return true;
-                }
+                this.isAnyChildContentActive() && this.props.select(false);
+                return true;
             }
 
             return false;
@@ -190,46 +186,44 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                 return false;
             }
             const targetDataDoc = this.props.Document[DataSym];
-            const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
-            const added = docs.filter(d => !docList.includes(d));
             const effectiveAcl = GetEffectiveAcl(targetDataDoc);
 
+            if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
+                return false;
+            }
+            const added = docs;
             if (added.length) {
-                if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
-                    return false;
-                } else {
-                    if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
-                        added.forEach(d => {
-                            for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
-                                if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d);
-                            }
-                        });
-                    }
+                if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
+                    added.forEach(d => {
+                        for (const key of Object.keys(this.props.Document[AclSym])) {
+                            if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d);
+                        }
+                    });
+                }
 
-                    if (effectiveAcl === AclAugment) {
-                        added.map(doc => {
-                            if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
+                if (effectiveAcl === AclAugment) {
+                    added.map(doc => {
+                        if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
+                        doc.context = this.props.Document;
+                        if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
+                        Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+                    });
+                } else {
+                    added
+                        .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)))
+                        .map(doc => {
+                            // only make a pushpin if we have acl's to edit the document
+                            //DocUtils.LeavePushpin(doc);
+                            doc._stayInCollection = undefined;
                             doc.context = this.props.Document;
                             if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
-                            Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+
+                            Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
                         });
-                    } else {
-                        added
-                            .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)))
-                            .map(doc => {
-                                // only make a pushpin if we have acl's to edit the document
-                                //DocUtils.LeavePushpin(doc);
-                                doc._stayInCollection = undefined;
-                                doc.context = this.props.Document;
-                                if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
-
-                                Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
-                            });
-                        const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
-                        if (annoDocs instanceof List) annoDocs.push(...added);
-                        else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
-                        targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now()));
-                    }
+                    const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
+                    if (annoDocs instanceof List) annoDocs.push(...added);
+                    else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
+                    targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now()));
                 }
             }
             return true;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 69eec8456..c18a89481 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -63,10 +63,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             Doc.ActiveTool = InkTool.None;
             MainView.Instance._exploreMode = false;
         } else {
-            if (doc) {
-                const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
-                l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen');
-            }
+            const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+            l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen');
             CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
             //TabDocView.PinDoc(doc, { hidePresBox: true });
             this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4cbf8a811..e4554c339 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -49,7 +49,7 @@ import { LinkMenu } from './linking/LinkMenu';
 import './MainView.scss';
 import { AudioBox } from './nodes/AudioBox';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
@@ -222,6 +222,7 @@ export class MainView extends React.Component {
 
     constructor(props: Readonly<{}>) {
         super(props);
+        DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
         MainView.Instance = this;
         DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
 
@@ -245,6 +246,7 @@ export class MainView extends React.Component {
             ...[
                 fa.faExclamationCircle,
                 fa.faEdit,
+                fa.faArrowDownShortWide,
                 fa.faTrash,
                 fa.faTrashAlt,
                 fa.faShare,
@@ -584,7 +586,7 @@ export class MainView extends React.Component {
                     Document={this.headerBarDoc}
                     DataDoc={undefined}
                     addDocument={undefined}
-                    addDocTab={MainView.addDocTabFunc}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
                     docViewPath={returnEmptyDoclist}
                     styleProvider={DefaultStyleProvider}
@@ -619,7 +621,7 @@ export class MainView extends React.Component {
                     Document={this.mainContainer!}
                     DataDoc={undefined}
                     addDocument={undefined}
-                    addDocTab={MainView.addDocTabFunc}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
                     docViewPath={returnEmptyDoclist}
                     styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
@@ -688,7 +690,7 @@ export class MainView extends React.Component {
 
     sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
-    static addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => {
+    static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => {
         const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
         const keyValue = whereFields[1]?.includes('KeyValue');
         const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
@@ -716,7 +718,7 @@ export class MainView extends React.Component {
                         Document={this._sidebarContent.proto || this._sidebarContent}
                         DataDoc={undefined}
                         addDocument={undefined}
-                        addDocTab={MainView.addDocTabFunc}
+                        addDocTab={DocumentViewInternal.addDocTabFunc}
                         pinToPres={emptyFunction}
                         docViewPath={returnEmptyDoclist}
                         styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
@@ -748,7 +750,7 @@ export class MainView extends React.Component {
                     Document={Doc.MyLeftSidebarMenu}
                     DataDoc={undefined}
                     addDocument={undefined}
-                    addDocTab={MainView.addDocTabFunc}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
                     rootSelected={returnTrue}
                     removeDocument={returnFalse}
@@ -810,7 +812,7 @@ export class MainView extends React.Component {
                             </div>
                         )}
                         <div className="properties-container" style={{ width: this.propertiesWidth() }}>
-                            {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={MainView.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
+                            {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                         </div>
                     </div>
                 </div>
@@ -889,7 +891,7 @@ export class MainView extends React.Component {
                     docViewPath={returnEmptyDoclist}
                     moveDocument={this.moveButtonDoc}
                     addDocument={this.addButtonDoc}
-                    addDocTab={MainView.addDocTabFunc}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={this.remButtonDoc}
                     ScreenToLocalTransform={this.buttonBarXf}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 98dcf4f21..cf808f801 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -136,10 +136,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     // containerDoc.noShadow =
                     // containerDoc.disableDocBrushing =
                     // containerDoc._forceActive =
-                    containerDoc._fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox;
-                    containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
+                    //containerDoc._fitContentsToBox =
+                    containerDoc._isLightbox = !containerDoc._isLightbox;
+                    //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
                     const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
-                    dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+                    //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
                     containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.linkDisplay = false)));
                 });
             }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5f16e0ebd..d98e5aa80 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -30,7 +30,6 @@ export enum StyleProp {
     TreeViewSortings = 'treeViewSortings', // options for how to sort tree view items
     DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
     Opacity = 'opacity', // opacity of the document view
-    Hidden = 'hidden', // whether the document view should not be isplayed
     BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
     BorderRounding = 'borderRounding', // border radius of the document view
     Color = 'color', // foreground color of Document view items
@@ -172,8 +171,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor);
             if (!backColor) return undefined;
             return lightOrDark(backColor);
-        case StyleProp.Hidden:
-            return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? false : BoolCast(doc?.hidden);
         case StyleProp.BorderRounding:
             return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.borderRounding, doc?._viewType === CollectionViewType.Pile ? '50%' : ''));
         case StyleProp.TitleHeight:
@@ -257,7 +254,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                     if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
                     docColor =
                         docColor ||
-                        (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc)
+                        (Doc.IsSystem(doc)
                             ? darkScheme()
                                 ? Colors.DARK_GRAY
                                 : Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index fd9b0c0ce..e5d89865b 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, IReactionDisposer, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
@@ -26,12 +26,6 @@ export class CollectionPileView extends CollectionSubView() {
         }
         this._originalChrome = this.layoutDoc._chromeHidden;
         this.layoutDoc._chromeHidden = true;
-
-        // pileups are designed to go away when they are empty.
-        this._disposers.selected = reaction(
-            () => this.childDocs.length,
-            num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document)
-        );
     }
     componentWillUnmount() {
         this.layoutDoc._chromeHidden = this._originalChrome;
@@ -48,13 +42,15 @@ export class CollectionPileView extends CollectionSubView() {
 
     @undoBatch
     removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
-        (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d)));
-        return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+        (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
+        const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+        if (ret && !DocListCast(this.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc);
+        return ret;
     };
 
-    toggleIcon = () => {
+    @computed get toggleIcon() {
         return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
-    };
+    }
 
     // returns the contents of the pileup in a CollectionFreeFormView
     @computed get contents() {
@@ -63,11 +59,11 @@ export class CollectionPileView extends CollectionSubView() {
             <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}>
                 <CollectionFreeFormView
                     {...this.props}
+                    childContentsActive={returnFalse}
                     layoutEngine={this.layoutEngine}
-                    childDocumentsActive={isStarburst ? returnTrue : undefined}
                     addDocument={this.addPileDoc}
                     childCanEmbedOnDrag={true}
-                    childClickScript={this.toggleIcon()}
+                    childClickScript={this.toggleIcon}
                     moveDocument={this.removePileDoc}
                 />
             </div>
@@ -87,15 +83,12 @@ export class CollectionPileView extends CollectionSubView() {
             this.layoutDoc._panY = -10;
             this.props.Document._pileLayoutEngine = computePassLayout.name;
         } else {
-            const defaultSize = 25;
-            !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
+            const defaultSize = 500;
             !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
-            if (this.layoutEngine() === computePassLayout.name) {
-                this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
-                this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
-                this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
-                this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
-            }
+            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
+            this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
+            this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
             this.layoutDoc._panX = this.layoutDoc._panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
             this.props.Document._pileLayoutEngine = computeStarburstLayout.name;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bdad325d5..020fe1cb4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -10,7 +10,7 @@ import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -238,9 +238,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
     };
 
-    @computed get onChildClickHandler() {
-        return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
-    }
+    onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
     @computed get onChildDoubleClickHandler() {
         return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     }
@@ -300,7 +298,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     @observable _renderCount = 5;
     isChildContentActive = () =>
-        this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
+        this.props.isContentActive?.() === false
+            ? false
+            : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive))
+            ? true
+            : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false
+            ? false
+            : undefined;
     isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined);
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
@@ -320,6 +324,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
+                pointerEvents={this.props.DocumentView?.().props.onClick ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
                 docViewPath={this.props.docViewPath}
                 fitWidth={this.props.childFitWidth}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 4d5978548..03c010703 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -277,7 +277,7 @@ export class CollectionTimeView extends CollectionSubView() {
 }
 
 ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
-    const pivotField = StrCast(pivotDoc._pivotField) || 'author';
+    const pivotField = StrCast(pivotDoc._pivotField, 'author');
     let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
     const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
     pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 790aa765d..b76033a0c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -46,6 +46,7 @@ interface CollectionViewProps_ extends FieldViewProps {
     // property overrides for child documents
     childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
     childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+    childContentsActive?: () => boolean | undefined;
     childFitWidth?: (child: Doc) => boolean;
     childShowTitle?: () => string;
     childOpacity?: () => number;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 8fb610b87..4adf86683 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -801,7 +801,6 @@ export class TreeView extends React.Component<TreeViewProps> {
             case StyleProp.Opacity:         return this.props.treeView.outlineMode ? undefined : 1;
             case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
             case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
-            case StyleProp.Hidden:          return false;
             case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
                 const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
@@ -827,7 +826,6 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
     embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
-        if (property.startsWith(StyleProp.Hidden)) return false;
         return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 81b0c4d8a..2549ee0b3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -93,6 +93,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
             width: layout[WidthSym](),
             height: layout[HeightSym](),
             pair: { layout, data },
+            transition: 'all .3s',
             replica: '',
         });
     });
@@ -100,28 +101,27 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
 }
 
 export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
-    const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
     const docMap = new Map<string, PoolData>();
-    const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75
-    const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
-    const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
+    const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
     childPairs.forEach(({ layout, data }, i) => {
-        const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75
+        const aspect = layout[HeightSym]() / layout[WidthSym]();
+        const docSize = Math.min(Math.min(400, layout[WidthSym]()), Math.min(400, layout[WidthSym]()) / aspect);
         const deg = (i / childPairs.length) * Math.PI * 2;
         docMap.set(layout[Id], {
-            x: Math.cos(deg) * burstRadius[0] - docSize / 2,
-            y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2,
-            width: docSize, //layout[WidthSym](),
-            height: (docSize * layout[HeightSym]()) / layout[WidthSym](),
+            x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
+            y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)),
+            width: docSize,
+            height: docSize * aspect,
             zIndex: NumCast(layout.zIndex),
             pair: { layout, data },
             replica: '',
             color: 'white',
             backgroundColor: 'white',
+            transition: 'all 0.3s',
         });
     });
-    const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
-    return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
+    const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined };
+    return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
 }
 
 export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
@@ -424,6 +424,7 @@ function normalizeResults(
                     color: newPosRaw.color,
                     pair: ele[1].pair,
                 };
+                if (newPosRaw.transition) newPos.transition = newPosRaw.transition;
                 poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
             }
         });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ff0d01f29..1fc4d9259 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -308,6 +308,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
+        if (this._lightboxDoc) return;
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
         const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -327,7 +328,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     getView = async (doc: Doc): Promise<Opt<DocumentView>> => {
         return new Promise<Opt<DocumentView>>(res => {
-            doc.hidden && (doc.hidden = false);
+            if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false;
             const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
             findDoc(dv => res(dv));
         });
@@ -778,7 +779,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._batch?.end();
     };
 
+    @action
     onClick = (e: React.MouseEvent) => {
+        if (this._lightboxDoc) this._lightboxDoc = undefined;
         if (this.onBrowseClickHandler()) {
             if (this.props.DocumentView?.()) {
                 this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY });
@@ -1274,7 +1277,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 docRangeFilters={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
-                isContentActive={emptyFunction}
+                isContentActive={this.props.childContentsActive ?? emptyFunction}
                 focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
                 addDocTab={this.addDocTab}
                 addDocument={this.props.addDocument}
@@ -1320,13 +1323,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case undefined:
             case OpenWhere.lightbox:
                 if (this.layoutDoc._isLightbox) {
-                    // _isLightbox docs have a script that will unset this overlay onClick
-                    this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc);
+                    this._lightboxDoc = doc;
+                    return true;
+                } else if (this.childDocList?.includes(doc)) {
+                    if (doc.hidden) doc.hidden = false;
                     return true;
                 }
         }
         return this.props.addDocTab(doc, where);
     });
+    @observable _lightboxDoc: Opt<Doc>;
 
     getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
         const childDoc = params.pair.layout;
@@ -1936,7 +1942,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             1000
         );
     };
-
+    lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
+    lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
+    lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
     render() {
         TraceMobx();
         return (
@@ -1965,36 +1973,65 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     width: `${100 / (this.nativeDimScaling || 1)}%`,
                     height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
                 }}>
-                {this._firstRender ? this.placeholder : this.marqueeView}
-                {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
-
-                {/* // uncomment to show snap lines */}
-                <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
-                    <svg style={{ width: '100%', height: '100%' }}>
-                        {this._hLines?.map(l => (
-                            <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
-                        ))}
-                        {this._vLines?.map(l => (
-                            <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
-                        ))}
-                    </svg>
-                </div>
+                {this._lightboxDoc ? (
+                    <div style={{ padding: 15, width: '100%', height: '100%' }}>
+                        <DocumentView
+                            {...this.props}
+                            Document={this._lightboxDoc}
+                            DataDoc={undefined}
+                            PanelWidth={this.lightboxPanelWidth}
+                            PanelHeight={this.lightboxPanelHeight}
+                            NativeWidth={returnZero}
+                            NativeHeight={returnZero}
+                            onClick={this.onChildClickHandler}
+                            onKey={this.onKeyDown}
+                            onDoubleClick={this.onChildDoubleClickHandler}
+                            onBrowseClick={this.onBrowseClickHandler}
+                            docFilters={this.childDocFilters}
+                            docRangeFilters={this.childDocRangeFilters}
+                            searchFilterDocs={this.searchFilterDocs}
+                            isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+                            isContentActive={this.props.childContentsActive ?? emptyFunction}
+                            addDocTab={this.addDocTab}
+                            ScreenToLocalTransform={this.lightboxScreenToLocal}
+                            fitContentsToBox={undefined}
+                            focus={this.focus}
+                        />
+                    </div>
+                ) : (
+                    <>
+                        {this._firstRender ? this.placeholder : this.marqueeView}
+                        {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+
+                        {/* // uncomment to show snap lines */}
+                        <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
+                            <svg style={{ width: '100%', height: '100%' }}>
+                                {this._hLines?.map(l => (
+                                    <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
+                                ))}
+                                {this._vLines?.map(l => (
+                                    <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+                                ))}
+                            </svg>
+                        </div>
 
-                {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
-                    <div
-                        className="collectionFreeForm-groupDropper"
-                        ref={this.createGroupEventsTarget}
-                        style={{
-                            width: this.ChildDrag ? '10000' : '100%',
-                            height: this.ChildDrag ? '10000' : '100%',
-                            left: this.ChildDrag ? '-5000' : 0,
-                            top: this.ChildDrag ? '-5000' : 0,
-                            position: 'absolute',
-                            background: '#0009930',
-                            pointerEvents: 'all',
-                        }}
-                    />
-                ) : null}
+                        {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
+                            <div
+                                className="collectionFreeForm-groupDropper"
+                                ref={this.createGroupEventsTarget}
+                                style={{
+                                    width: this.ChildDrag ? '10000' : '100%',
+                                    height: this.ChildDrag ? '10000' : '100%',
+                                    left: this.ChildDrag ? '-5000' : 0,
+                                    top: this.ChildDrag ? '-5000' : 0,
+                                    position: 'absolute',
+                                    background: '#0009930',
+                                    pointerEvents: 'all',
+                                }}
+                            />
+                        ) : null}
+                    </>
+                )}
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d443df0f3..eaeb5f933 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -371,10 +371,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @undoBatch
     @action
-    delete = () => {
+    delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
-        selected.forEach(doc => this.props.removeDocument?.(doc));
+        selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc)));
 
         this.cleanupInteractions(false);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -550,11 +550,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         if (this._commandExecuted || (e as any).propagationIsStopped) {
             return;
         }
-        if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd') {
+        if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd' || e.key === 'h') {
             this._commandExecuted = true;
             e.stopPropagation();
             (e as any).propagationIsStopped = true;
-            this.delete();
+            this.delete(e, e.key === 'h');
             e.stopPropagation();
         }
         if ('cbtsSpg'.indexOf(e.key) !== -1) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dc508d95f..2ea0a6f19 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -262,7 +262,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @observable _animateScalingTo = 0;
 
     public get animateScaleTime() {
-        return this._animateScaleTime ?? 300;
+        return this._animateScaleTime ?? 100;
     }
     public get displayName() {
         return 'DocumentView(' + this.props.Document.title + ')';
@@ -295,8 +295,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ':selected' : ''));
     }
     @computed get backgroundBoxColor() {
-        const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb));
-        return thumb ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+        return this.thumbShown() ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
     }
     @computed get docContents() {
         return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents);
@@ -413,6 +412,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
     };
 
+    public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
@@ -446,24 +447,33 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 let clickFunc: undefined | (() => any);
                 if (!this.disableClickScriptFunc && this.onClickHandler?.script) {
                     const { clientX, clientY, shiftKey, altKey, metaKey } = e;
-                    const func = () =>
-                        this.onClickHandler?.script.run(
-                            {
-                                this: this.layoutDoc,
-                                self: this.rootDoc,
-                                _readOnly_: false,
-                                scriptContext: this.props.scriptContext,
-                                documentView: this.props.DocumentView(),
-                                clientX,
-                                clientY,
-                                shiftKey,
-                                altKey,
-                                metaKey,
-                            },
-                            console.log
-                        ).result?.select === true
-                            ? this.props.select(false)
-                            : '';
+                    const func = () => {
+                        // replace default add doc func with this view's add doc func.
+                        // to allow override behaviors for how to display links to undisplayed documents.
+                        // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place
+                        // instead of in the global lightbox
+                        const oldFunc = DocumentViewInternal.addDocTabFunc;
+                        DocumentViewInternal.addDocTabFunc = this.props.addDocTab;
+                        const res =
+                            this.onClickHandler?.script.run(
+                                {
+                                    this: this.layoutDoc,
+                                    self: this.rootDoc,
+                                    _readOnly_: false,
+                                    scriptContext: this.props.scriptContext,
+                                    documentView: this.props.DocumentView(),
+                                    clientX,
+                                    clientY,
+                                    shiftKey,
+                                    altKey,
+                                    metaKey,
+                                },
+                                console.log
+                            ).result?.select === true
+                                ? this.props.select(false)
+                                : '';
+                        DocumentViewInternal.addDocTabFunc = oldFunc;
+                    };
                     clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
                 } else {
                     // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
@@ -1318,9 +1328,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
         return hideCount ? null : <DocumentLinksButton View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
-    @computed get hidden() {
-        return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden);
-    }
     @computed get docViewPath(): DocumentView[] {
         return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
     }
@@ -1497,7 +1504,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
         const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
 
-        return this.hidden ? null : (
+        return (
             <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
                 {!this.props.Document || !this.props.PanelWidth() ? null : (
                     <div
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 22dbc1e80..29943e156 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -2,7 +2,9 @@
     border-radius: inherit;
     width: 100%;
     height: 100%;
-    position: relative;
+    position: absolute;
+    top: 0;
+    left: 0;
     transform-origin: top left;
 
     .imageBox-annotationLayer {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 98df777cb..20e5f5eca 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -505,6 +505,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     }
                 })}
                 style={{
+                    display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
                     width: this.props.PanelWidth() ? undefined : `100%`,
                     height: this.props.PanelWidth() ? undefined : `100%`,
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 6f578a9fc..75847c100 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -183,6 +183,7 @@
                 height: 100%;
                 position: absolute;
                 top: 0;
+                left: 0;
                 body {
                     ::selection {
                         color: white;
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index b31fc01ff..648c579d0 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -181,6 +181,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     height: this._height,
                     position: 'absolute',
                     display: 'inline-block',
+                    left: 0,
+                    top: 0,
                 }}
                 onPointerLeave={this.onPointerLeave}
                 onPointerEnter={this.onPointerEnter}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index fd7fbb333..3b42c41a5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -24,6 +24,27 @@ audiotag:hover {
     transform: scale(2);
     transform-origin: bottom center;
 }
+.formattedTextBox {
+    touch-action: none;
+    background: inherit;
+    padding: 0;
+    border-width: 0px;
+    border-radius: inherit;
+    border-color: $medium-gray;
+    box-sizing: border-box;
+    background-color: inherit;
+    border-style: solid;
+    overflow-y: auto;
+    overflow-x: hidden;
+    color: inherit;
+    display: flex;
+    flex-direction: row;
+    transition: opacity 1s;
+    width: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+}
 
 .formattedTextBox-cont {
     touch-action: none;
@@ -51,6 +72,15 @@ audiotag:hover {
         position: absolute;
     }
 }
+.formattedTextBox-alternateButton {
+    position: absolute;
+    color: white;
+    background: black;
+    right: 0;
+    bottom: 0;
+    width: 15;
+    height: 15;
+}
 
 .formattedTextBox-outer-selected,
 .formattedTextBox-outer {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bbe38cf99..2755d5100 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,9 +1,9 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
 import { isEqual } from 'lodash';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Configuration, OpenAIApi } from 'openai';
 import { baseKeymap, selectAll } from 'prosemirror-commands';
 import { history } from 'prosemirror-history';
 import { inputRules } from 'prosemirror-inputrules';
@@ -1928,6 +1928,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             </div>
         );
     }
+    @computed get overlayAlternateIcon() {
+        const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`];
+        return (
+            <Tooltip
+                title={
+                    <div className="dash-tooltip">
+                        toggle between
+                        <span style={{ color: usePath === undefined ? 'black' : undefined }}>
+                            <em> primary, </em>
+                        </span>
+                        <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
+                            <em>alternate, </em>
+                        </span>
+                        and show
+                        <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
+                            <em> alternate on hover</em>
+                        </span>
+                    </div>
+                }>
+                <div
+                    className="formattedTextBox-alternateButton"
+                    onPointerDown={e =>
+                        setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.props.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))
+                    }
+                    style={{
+                        display: this.props.isContentActive() ? 'block' : 'none',
+                        background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
+                        color: usePath === undefined ? 'black' : 'white',
+                    }}>
+                    <FontAwesomeIcon icon="turn-up" size="lg" />
+                </div>
+            </Tooltip>
+        );
+    }
+    @computed get fieldKey() {
+        const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}-usePath`]);
+        return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `-${usePath.replace(':hover', '')}` : '');
+    }
+    @observable _isHovering = false;
     render() {
         TraceMobx();
         const active = this.props.isContentActive() || this.props.isSelected();
@@ -1944,7 +1983,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
         return styleFromLayoutString?.height === '0px' ? null : (
             <div
-                className="formattedTextBox-cont"
+                className="formattedTextBox"
+                onPointerEnter={action(() => (this._isHovering = true))}
+                onPointerLeave={action(() => (this._isHovering = false))}
                 ref={r =>
                     r?.addEventListener(
                         'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
@@ -1966,6 +2007,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                               width: `${100 / scale}%`,
                               height: `${100 / scale}%`,
                           }),
+                    display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
                     transition: 'inherit',
                     // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
                     color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
@@ -2017,6 +2059,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
                     {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
                     {this.audioHandle}
+                    {this.overlayAlternateIcon}
                 </div>
             </div>
         );
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 70d9ed61f..92f3a69eb 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -107,8 +107,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
                 redo: () => (receiver[prop] = value),
                 undo: () => {
                     const wasUpdate = receiver[UpdatingFromServer];
+                    const wasForce = receiver[ForceServerWrite];
+                    receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet
                     receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable.
                     receiver[prop] = curValue;
+                    receiver[ForceServerWrite] = wasForce;
                     receiver[UpdatingFromServer] = wasUpdate;
                 },
                 prop: prop?.toString(),
-- 
cgit v1.2.3-70-g09d2


From e4c7926a5280222156a38d46a2aa87d442712cf1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 24 Apr 2023 15:22:41 -0400
Subject: fixed interactions with sidebar stacking views.

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

(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 020fe1cb4..eedf639aa 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -324,7 +324,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
-                pointerEvents={this.props.DocumentView?.().props.onClick ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+                pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
                 docViewPath={this.props.docViewPath}
                 fitWidth={this.props.childFitWidth}
-- 
cgit v1.2.3-70-g09d2

