From 0d899471be676f6619244350982630b151bb4b41 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Mar 2023 19:53:49 -0400
Subject: mostly code cleaning.  small bugs

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

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 89b2fbfe3..4d5978548 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -150,7 +150,7 @@ export class CollectionTimeView extends CollectionSubView() {
                     engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
                     fitContentsToBox={returnTrue}
                     childClickScript={this._childClickedScript}
-                    viewDefDivClick={this._viewDefDivClick}
+                    viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
                     //dontScaleFilter={this.dontScaleFilter}
                     layoutEngine={this.layoutEngine}
                 />
-- 
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/CollectionTimeView.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 ae038471f7eda76ecd985e277dda2dc6d27a3be7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 28 Apr 2023 10:57:37 -0400
Subject: fixed open in lightbox from doc decorations.  fixed resizing text
 docs that are not fitWidth => artifact is that pdf's don't preserve aspect
 until they are resize vertically to establish a nativeHeight unless they are
 created with nativeHeight set.

---
 src/client/views/DocumentDecorations.tsx                   | 14 +++++++-------
 src/client/views/collections/CollectionTimeView.tsx        |  2 --
 .../collectionFreeForm/CollectionFreeFormView.tsx          |  5 ++---
 src/client/views/nodes/DocumentView.tsx                    |  3 +--
 4 files changed, 10 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2811c96eb..85d36dbf8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -35,6 +35,7 @@ import { RichTextField } from '../../fields/RichTextField';
 import { LinkFollower } from '../util/LinkFollower';
 import _ = require('lodash');
 import { DocumentManager } from '../util/DocumentManager';
+import { isUndefined } from 'lodash';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -289,12 +290,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
                     Doc.deiconifyView(openDoc);
                 }
-                selectedDocs[0].props.addDocTab(openDoc, OpenWhere.lightbox);
-                // LightboxView.SetLightboxDoc(
-                //     openDoc,
-                //     undefined,
-                //     selectedDocs.slice(1).map(view => view.props.Document)
-                // );
+                LightboxView.SetLightboxDoc(
+                    openDoc,
+                    undefined,
+                    selectedDocs.slice(1).map(view => view.props.Document)
+                );
             }
         }
         SelectionManager.DeselectAll();
@@ -576,7 +576,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     const doc = docView.rootDoc;
                     const refCent = docView.props.ScreenToLocalTransform().transformPoint(scaleRefPt[0], scaleRefPt[1]);
 
-                    if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight)) {
+                    if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight) && doc._nativeWidth !== undefined) {
                         doc._nativeHeight = (NumCast(doc._height) / NumCast(doc._width, 1)) * docView.nativeWidth;
                     }
                     const nwidth = docView.nativeWidth;
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 03c010703..d0f3f2ca5 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -141,7 +141,6 @@ export class CollectionTimeView extends CollectionSubView() {
         }
     };
 
-    dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF;
     @computed get contents() {
         return (
             <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
@@ -151,7 +150,6 @@ export class CollectionTimeView extends CollectionSubView() {
                     fitContentsToBox={returnTrue}
                     childClickScript={this._childClickedScript}
                     viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
-                    //dontScaleFilter={this.dontScaleFilter}
                     layoutEngine={this.layoutEngine}
                 />
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 719a39e8d..bab42e560 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -64,7 +64,6 @@ export type collectionFreeformViewProps = {
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
     getScrollHeight?: () => number | undefined;
-    dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
     dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
     // However, this screws up interactions since only the top layer gets events.  so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
 };
@@ -1296,7 +1295,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
                 bringToFront={this.bringToFront}
                 showTitle={this.props.childShowTitle}
-                dontScaleFilter={this.props.dontScaleFilter}
                 dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
                 pointerEvents={this.pointerEvents}
                 //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
@@ -1328,7 +1326,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 if (this.layoutDoc._isLightbox) {
                     this._lightboxDoc = doc;
                     return true;
-                } else if (this.childDocList?.includes(doc)) {
+                }
+                if (this.childDocList?.includes(doc)) {
                     if (doc.hidden) doc.hidden = false;
                     return true;
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a25e5c42d..4876ef300 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -215,7 +215,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     radialMenu?: String[];
     LayoutTemplateString?: string;
     dontCenter?: 'x' | 'y' | 'xy';
-    dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
@@ -1341,7 +1340,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth));
     }
     @computed get shouldNotScale() {
-        return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+        return (this.fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._viewType as any);
     }
     @computed get effectiveNativeWidth() {
         return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
-- 
cgit v1.2.3-70-g09d2


From 1c24114bbe8f69f61948f7531277305457926498 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 3 May 2023 10:11:06 -0400
Subject: fixed pinning template text documents with content to save and
 restore the actual text.

---
 src/client/util/DocumentManager.ts                  |  2 +-
 src/client/views/DocComponent.tsx                   |  2 +-
 src/client/views/collections/CollectionTimeView.tsx |  7 +++----
 src/client/views/nodes/trails/PresBox.tsx           | 11 ++---------
 src/fields/Doc.ts                                   |  2 +-
 5 files changed, 8 insertions(+), 16 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4542c1c05..3a192f712 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -180,7 +180,7 @@ export class DocumentManager {
 
     static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
         if (!doc) return [];
-        const srcContext = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
+        const srcContext = DocCast(doc.annotationOn, DocCast(doc.context));
         var containerDocContext = srcContext ? [srcContext, doc] : [doc];
         while (
             containerDocContext.length &&
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d60ad68c6..eba55e30c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -216,7 +216,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                             //DocUtils.LeavePushpin(doc);
                             doc._stayInCollection = undefined;
                             doc.context = this.props.Document;
-                            if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
+                            if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
 
                             Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
                         });
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index d0f3f2ca5..c3f77205a 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -7,22 +7,21 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
+import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
+import { PresBox } from '../nodes/trails';
 import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTimeView.scss';
 import React = require('react');
-import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
-import { PresBox } from '../nodes/trails';
 
 @observer
 export class CollectionTimeView extends CollectionSubView() {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 0b780f589..bd2be8f11 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -49,7 +49,6 @@ export interface pinDataTypes {
     clippable?: boolean;
     datarange?: boolean;
     dataview?: boolean;
-    textview?: boolean;
     poslayoutview?: boolean;
     dataannos?: boolean;
 }
@@ -382,14 +381,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType);
         const clippable = [DocumentType.COMPARISON].includes(targetType);
         const datarange = [DocumentType.FUNCPLOT].includes(targetType);
-        const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(targetType) && target?.activeFrame === undefined;
+        const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
         const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined;
-        const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
         const viewType = targetType === DocumentType.COL;
         const filters = true;
         const pivot = true;
         const dataannos = false;
-        return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, datarange, textview, poslayoutview, dataannos };
+        return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
     }
 
     @action
@@ -530,9 +528,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             bestTarget[fkey + '-usePath'] = activeItem.presUsePath;
             setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
         }
-        if ((pinDataTypes?.textview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) {
-            Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
-        }
         if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.presPinLayoutData !== undefined)) {
             changed = true;
             const layoutField = Doc.LayoutFieldKey(bestTarget);
@@ -609,7 +604,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 pinProps.pinData.clippable ||
                 pinProps.pinData.datarange ||
                 pinProps.pinData.dataview ||
-                pinProps.pinData.textview ||
                 pinProps.pinData.poslayoutview ||
                 pinProps?.activeFrame !== undefined;
             const fkey = Doc.LayoutFieldKey(targetDoc);
@@ -621,7 +615,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const fkey = Doc.LayoutFieldKey(targetDoc);
                 pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations']).filter(doc => !doc.unrendered));
             }
-            if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text;
             if (pinProps.pinData.inkable) {
                 pinDoc.presFillColor = targetDoc.fillColor;
                 pinDoc.presColor = targetDoc.color;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 0ec881c48..a9be24c8b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1181,7 +1181,7 @@ export namespace Doc {
         return doc[StrCast(doc.layoutKey, 'layout')];
     }
     export function LayoutFieldKey(doc: Doc): string {
-        return StrCast(Doc.Layout(doc)[StrCast(doc.layoutKey, 'layout')]).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layoutKey
+        return StrCast(Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layoutKey
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
-- 
cgit v1.2.3-70-g09d2


From dbd531233ea7c10130976127361f84281026a308 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 May 2023 12:27:55 -0400
Subject: more fixes for tags - removed #<tag> fields.  just use 'tags' now.

---
 src/client/views/FilterPanel.tsx                                 | 2 +-
 src/client/views/PropertiesView.tsx                              | 6 ++----
 src/client/views/collections/CollectionDockingView.tsx           | 1 -
 src/client/views/collections/CollectionTimeView.tsx              | 2 +-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx    | 2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx        | 1 +
 src/client/views/nodes/formattedText/DashDocView.tsx             | 3 +--
 src/client/views/nodes/formattedText/DashFieldView.tsx           | 8 ++++++--
 src/client/views/nodes/formattedText/EquationView.tsx            | 1 -
 src/client/views/nodes/formattedText/FormattedTextBox.tsx        | 9 ++-------
 src/client/views/nodes/formattedText/RichTextRules.ts            | 2 +-
 src/client/views/nodes/formattedText/SummaryView.tsx             | 1 -
 src/fields/Doc.ts                                                | 3 +--
 13 files changed, 17 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index d17a4ea25..84b2e1407 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -53,7 +53,7 @@ export class FilterPanel extends React.Component<filterProps> {
         this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
         const sortedKeys = Array.from(keys.keys())
             .filter(key => key[0])
-            .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
+            .filter(key => key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
             .sort();
         noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1));
         return [...noviceFields, ...sortedKeys];
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index fff8390b3..0b14c7b10 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -164,7 +164,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
                 const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
                 const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field);
-                const contentElement = key[0] === '#'  ? <></> : (
+                const contentElement = (
                     <EditableView
                         key="editableView"
                         contents={displayContents}
@@ -200,7 +200,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @computed get noviceFields() {
         const noviceReqFields = ['author', 'creationDate', 'tags', '_curPage'];
-        return this.editableFields(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl')), noviceReqFields);
+        return this.editableFields(key => key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl')), noviceReqFields);
     }
 
     @undoBatch
@@ -217,8 +217,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 }
                 return true;
             } else if (value[0] === '#') {
-                const newVal = value + `:'${value}'`;
-                doc[DataSym][value] = value;
                 const tags = StrListCast(doc.tags);
                 if (!tags.includes(value)) {
                     tags.push(value);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4d000542c..bb1f788d4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -481,7 +481,6 @@ export class CollectionDockingView extends CollectionSubView() {
             Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
             this.tabMap.delete(tab);
             tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
-            //tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
             this.stateChanged();
         }
     };
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index c3f77205a..3cdb460a3 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -202,7 +202,7 @@ export class CollectionTimeView extends CollectionSubView() {
         this.childLayoutPairs.map(pair =>
             this._allFacets
                 .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string')
-                .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey[0] !== '#' || fieldKey === '#') && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
+                .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
                 .map(fieldKey => keySet.add(fieldKey))
         );
         Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 25da868e0..91c5144d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1375,7 +1375,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             rotation: Cast(_rotation, 'number'),
             color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
             backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
-            opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
+            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: _width,
             height: _height,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b68bcc263..a8c8a0d2e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -214,6 +214,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: '50% 50%',
                     transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
                     zIndex: this.ZInd,
+                    display: this.sizeProvider?.width ? undefined : 'none',
                     pointerEvents: isInk ? 'none' : undefined,
                 }}>
                 {this.props.renderCutoffProvider(this.props.Document) ? (
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 648c579d0..c05a30d1a 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -45,8 +45,7 @@ export class DashDocView {
         );
     }
     destroy() {
-        this.root.unmount();
-        // ReactDOM.unmountComponentAtNode(this.dom);
+        setTimeout(this.root.unmount);
     }
     selectNode() {}
 }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 72e8aedac..bf6fa2ec6 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -64,7 +64,11 @@ export class DashFieldView {
         );
     }
     destroy() {
-        this.root.unmount();
+        setTimeout(() => {
+            try {
+                this.root.unmount();
+            } catch {}
+        });
     }
     deselectNode() {
         this.dom.classList.remove('ProseMirror-selectednode');
@@ -241,7 +245,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             }
             list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
             list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
-            alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey;
+            alias._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey;
             this.props.tbox.props.addDocTab(alias, OpenWhere.addRight);
         }
     };
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 714ae458c..5e62d94c2 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -33,7 +33,6 @@ export class EquationView {
     setEditor = (editor?: EquationEditor) => (this._editor = editor);
     destroy() {
         this.root.unmount();
-        // ReactDOM.unmountComponentAtNode(this.dom);
     }
     setSelection() {
         this._editor?.mathField.focus();
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1d668d6a9..8a14b18b2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,7 +12,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, StrListCast, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -314,12 +314,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         accumTags.push(node.attrs.fieldKey);
                     }
                 });
-                const curTags = Object.keys(dataDoc).filter(key => key.startsWith('#'));
-                const added = accumTags.filter(tag => !curTags.includes(tag));
-                const removed = curTags.filter(tag => !accumTags.includes(tag));
-                removed.forEach(r => (dataDoc[r] = undefined));
-                added.forEach(a => (dataDoc[a] = a));
-                dataDoc.tags = curTags.length ? new List<string>(curTags) : undefined;
+                dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined;
 
                 let unchanged = true;
                 if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 68b209332..fb929d20b 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -325,7 +325,7 @@ export class RichTextRules {
             new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
                 const tag = match[1];
                 if (!tag) return state.tr;
-                this.Document[DataSym]['#' + tag] = '#' + tag;
+                //this.Document[DataSym]['#' + tag] = '#' + tag;
                 const tags = StrListCast(this.Document[DataSym].tags);
                 if (!tags.includes(tag)) {
                     tags.push(tag);
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 4e75d374c..3355e4529 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -43,7 +43,6 @@ export class SummaryView {
     className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed');
     destroy() {
         this.root.unmount();
-        // ReactDOM.unmountComponentAtNode(this.dom);
     }
     selectNode() {}
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 03ae91c50..b533bd10c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1387,8 +1387,7 @@ export namespace Doc {
         if (typeof value === 'string') {
             value = value.replace(`,${Utils.noRecursionHack}`, '');
         }
-        const tagsString = doc.tags ? ':' + StrListCast(doc.tags).join(':') + ':' : '';
-        const fieldVal = key === '#' ? (tagsString.includes(':#' + value + ':') ? tagsString : undefined) : doc[key];
+        const fieldVal = doc[key];
         if (Cast(fieldVal, listSpec('string'), []).length) {
             const vals = Cast(fieldVal, listSpec('string'), []);
             const docs = vals.some(v => (v as any) instanceof Doc);
-- 
cgit v1.2.3-70-g09d2

