From 58b850cf84e258933cfc075e58311cd122e5fd0d Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 26 Nov 2020 19:53:57 +0530
Subject: some sharing menu changes

---
 src/client/views/nodes/DocumentView.tsx | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e8fcf027e..18ebb569a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -9,7 +9,7 @@ import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from "../../../fields/Schema";
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
+import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, returnVal, Utils, returnFalse } from "../../../Utils";
@@ -42,6 +42,7 @@ import { RadialMenu } from './RadialMenu';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import React = require("react");
 import { List } from '../../../fields/List';
+import { Tooltip } from '@material-ui/core';
 
 export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
 export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
@@ -675,7 +676,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if ((e.target as any)?.closest?.("*.lm_content")) {
                 alert("You can't perform this move most likely because you don't have permission to modify the destination.");
             }
-            else alert("linking to document tabs not yet supported.  Drop link on document content.");
+            else alert("Linking to document tabs not yet supported. Drop link on document content.");
             return;
         }
         const makeLink = action((linkDoc: Doc) => {
@@ -960,10 +961,25 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             {/* {this.allAnchors} */}
             {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || (!this.isSelected() && (this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton)) || this.props.dontRegisterView ? (null) :
                 <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />}
-        </div>
+
+            {!this.props.Document.numUsersShared && !this.props.Document.numGroupsShared ? (null) :
+                <Tooltip title={<> <div className="dash-tooltip">Tap to open sharing menu</div></>}>
+                    <div className="sharingIndicator" onPointerDown={() => SharingManager.Instance.open(undefined, this.props.Document)}>
+                        <FontAwesomeIcon size="lg" icon={this.indicatorIcon} />
+                    </div>
+                </Tooltip >
+
+            }
+        </div >
         );
     }
 
+    get indicatorIcon() {
+        if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas";
+        else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users";
+        else return "user";
+    }
+
     // used to decide whether a link anchor view should be created or not.
     // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
     // would be good to generalize this some way.
-- 
cgit v1.2.3-70-g09d2


From 92c0f12098866a0e1936a74e9e50a9c0c5c1ad27 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 3 Dec 2020 17:13:16 +0530
Subject: greyed out sharing indicator

---
 src/client/views/nodes/DocumentView.tsx | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 79ee0291d..514130c3d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -964,7 +964,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
             {!this.props.Document.numUsersShared && !this.props.Document.numGroupsShared ? (null) :
                 <Tooltip title={<> <div className="dash-tooltip">Tap to open sharing menu</div></>}>
-                    <div className="sharingIndicator" onPointerDown={() => SharingManager.Instance.open(undefined, this.props.Document)}>
+                    <div className="sharingIndicator"
+                        onPointerDown={() => SharingManager.Instance.open(undefined, this.props.Document)}
+                        style={{ backgroundColor: GetEffectiveAcl(this.props.Document[DataSym]) === AclAdmin ? "#9dca96" : "lightgrey" }}
+                    >
                         <FontAwesomeIcon size="lg" icon={this.indicatorIcon} />
                     </div>
                 </Tooltip >
-- 
cgit v1.2.3-70-g09d2


From 1e3e178ca1c54bb43ba22dcfecaf338adc4abc54 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 18 Dec 2020 11:56:40 +0530
Subject: added something that was removed

---
 src/client/util/GroupManager.tsx        |  2 +-
 src/client/views/nodes/DocumentView.tsx | 11 +++++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

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

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index a9059ff3d..48676a393 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, autorun, computed, Lambda, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import * as React from "react";
 import Select from 'react-select';
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e40b23ea5..02bfa65aa 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,6 +42,7 @@ import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { List } from '../../../fields/List';
 import { Tooltip } from '@material-ui/core';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 
 export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
 export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
@@ -714,6 +715,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
             {this.hideLinkButton ? (null) :
                 <DocumentLinksButton View={this.props.DocumentView} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
+            {!this.props.Document.numUsersShared && !this.props.Document.numGroupsShared ? (null) :
+                <Tooltip title={<> <div className="dash-tooltip">Tap to open sharing menu</div></>}>
+                    <div className="sharingIndicator"
+                        onPointerDown={() => SharingManager.Instance.open(undefined, this.props.Document)}
+                        style={{ backgroundColor: GetEffectiveAcl(this.props.Document[DataSym]) === AclAdmin ? "#9dca96" : "lightgrey" }}
+                    >
+                        <FontAwesomeIcon size="lg" icon={this.indicatorIcon} />
+                    </div>
+                </Tooltip >
+            }
         </div>;
     }
 
-- 
cgit v1.2.3-70-g09d2


From c2bcb4ac83ecafb0bf23f72ed1bc2d05b877da6d Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Sat, 20 Feb 2021 11:39:22 -0500
Subject: filterdocs setup

---
 src/client/util/CurrentUserUtils.ts     | 14 +++++---------
 src/client/views/nodes/DocumentView.tsx |  4 +++-
 2 files changed, 8 insertions(+), 10 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8cb5e3903..2cba99355 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -791,20 +791,16 @@ export class CurrentUserUtils {
         // setup Filter item
         doc.currentFilter === undefined;
         if (doc.currentFilter === undefined) {
-            doc.currentFilter = new PrefetchProxy(Docs.Create.FilterDocument({
-                title: `FilterDoc(${(doc.filterDocCount as number)++})`, _height: 500,
-                treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "none",
+            doc.currentFilter = Docs.Create.FilterDocument({
+                title: "FilterDoc", _height: 20,
+                treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _yPadding: 10, forceActive: true, childDropAction: "none",
                 treeViewTruncateTitleWidth: 90, treeViewPreventOpen: false, ignoreClick: true,
                 lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
-            }));
+            });
             const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
             (doc.currentFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
             (doc.currentFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
         }
-        // const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([]); scriptContext._docFilters = scriptContext._docRangeFilters = undefined;`, { scriptContext: Doc.name });
-        // (doc.myFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
-        // (doc.myFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
-
     }
 
     static setupUserDoc(doc: Doc) {
@@ -836,7 +832,7 @@ export class CurrentUserUtils {
         CurrentUserUtils.setupDashboards(doc);
         CurrentUserUtils.setupPresentations(doc);
         CurrentUserUtils.setupRecentlyClosedDocs(doc);
-        CurrentUserUtils.setupFilterDocs(doc);
+        // CurrentUserUtils.setupFilterDocs(doc);
         CurrentUserUtils.setupUserDoc(doc);
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7fa790768..5afbec7e6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -119,6 +119,8 @@ export interface DocumentViewSharedProps {
     cantBrush?: boolean; // whether the document doesn't show brush highlighting
     pointerEvents?: string;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+    filterSaveCallback?: () => void;
+    myFiltersCallback?: (doc: Doc) => void;
 }
 export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
@@ -1075,7 +1077,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     componentDidMount() {
-        !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+        !BoolCast(this.props.Document?.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
     }
     componentWillUnmount() {
         !this.props.dontRegisterView && DocumentManager.Instance.RemoveView(this);
-- 
cgit v1.2.3-70-g09d2


From 02bceed486ecb03b94c757ae669f69912f282cdd Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Tue, 23 Feb 2021 10:34:47 -0500
Subject: current collection vs dashboard works a bit better - they have
 different filterdocs now

---
 src/client/views/PropertiesView.tsx     | 20 +++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx |  4 ++--
 src/client/views/nodes/FilterBox.tsx    |  6 +++---
 src/fields/Doc.ts                       |  2 +-
 4 files changed, 21 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 388b688cd..a159a9948 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -31,6 +31,7 @@ import "./PropertiesView.scss";
 import { DefaultStyleProvider, FilteringStyleProvider } from "./StyleProvider";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { FilterBox } from "./nodes/FilterBox";
+import { List } from "../../fields/List";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -901,16 +902,25 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     checkFilterDoc() {
-        if (this.filterDoc.type === DocumentType.COL && !this.filterDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.filterDoc!);
+        if (this.filterDoc.type === DocumentType.COL && !this.filterDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.filterDoc);
     }
 
-    saveFilter = () => {
+    createNewFilterDoc = () => {
+        const temp = this.filterDoc._docFilters;
+        this.filterDoc._docFilters = new List<string>();
+        (this.filterDoc.currentFilter as Doc)._docFiltersList = temp;
         this.filterDoc.currentFilter = undefined;
         CurrentUserUtils.setupFilterDocs(this.filterDoc);
     }
 
-    myFiltersSelect = (doc: Doc) => {
+    updateFilterDoc = (doc: Doc) => {
+        const temp = doc._docFiltersList;
+        const otherTemp = this.filterDoc._docFilters;
+        this.filterDoc._docFilters = new List<string>();
+        (this.filterDoc.currentFilter as Doc)._docFiltersList = otherTemp;
         this.filterDoc.currentFilter = doc;
+        doc._docFiltersList = new List<string>();
+        this.filterDoc._docFilters = temp;
     }
 
     @computed get filtersSubMenu() {
@@ -949,8 +959,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                             searchFilterDocs={returnEmptyDoclist}
                             ContainingCollectionView={undefined}
                             ContainingCollectionDoc={undefined}
-                            filterSaveCallback={this.saveFilter}
-                            myFiltersCallback={this.myFiltersSelect}
+                            createNewFilterDoc={this.createNewFilterDoc}
+                            updateFilterDoc={this.updateFilterDoc}
                             docViewPath={returnEmptyDoclist}
                             layerProvider={undefined}
                         />
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f99eb1b3b..ee4df97ed 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -120,8 +120,8 @@ export interface DocumentViewSharedProps {
     cantBrush?: boolean; // whether the document doesn't show brush highlighting
     pointerEvents?: string;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
-    filterSaveCallback?: () => void;
-    myFiltersCallback?: (doc: Doc) => void;
+    createNewFilterDoc?: () => void;
+    updateFilterDoc?: (doc: Doc) => void;
 }
 export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 987939d65..a3a3ec662 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -48,7 +48,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); }
 
     public _filterBoolean = "AND";
-    public static _filterScope = "Current Dashboard";
+    @observable public static _filterScope = "Current Dashboard";
     public _filterSelected = false;
     public _filterMatch = "matched";
     // private myFiltersRef = React.createRef<HTMLDivElement>();
@@ -309,7 +309,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
         const flyout = DocListCast(Doc.UserDoc().savedFilters).map(doc => {
             // console.log("mapping");
             return <>
-                <div className="???" onWheel={e => e.stopPropagation()} style={{ height: 50, border: "2px" }} onPointerDown={() => this.props.myFiltersCallback?.(doc)}>
+                <div className="???" onWheel={e => e.stopPropagation()} style={{ height: 50, border: "2px" }} onPointerDown={() => this.props.updateFilterDoc?.(doc)}>
                     {StrCast(doc.title)}
                 </div>
             </>;
@@ -419,7 +419,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                     </div>
                     <div className="filterBox-saveWrapper">
                         <div className="filterBox-saveBookmark"
-                            onPointerDown={this.props.filterSaveCallback}
+                            onPointerDown={this.props.createNewFilterDoc}
                         >
                             <div>NEW</div>
                         </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9b539d1b7..4cc0925a9 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1065,7 +1065,7 @@ export namespace Doc {
     // based on the modifiers :"check", "x", undefined
     export function setDocFilter(target: Opt<Doc>, key: string, value: any, modifiers?: "remove" | "match" | "check" | "x" | undefined) {
         const container = target ?? FilterBox._filterScope === "Current Collection" ? SelectionManager.Views()[0].Document || CollectionDockingView.Instance.props.Document : CollectionDockingView.Instance.props.Document;
-        const docFilters = Cast(SelectionManager.Views()[0].Document._docFilters, listSpec("string"), []);
+        const docFilters = Cast(container._docFilters, listSpec("string"), []);
         runInAction(() => {
             for (let i = 0; i < docFilters.length; i++) {
                 const fields = docFilters[i].split(":"); // split key:value:modifier
-- 
cgit v1.2.3-70-g09d2


From e675858cfab46300a543b1013c7f88c8233ed7ad Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 6 Apr 2021 11:09:26 -0400
Subject: added ability to remove filters using inline context menu

---
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/TreeView.tsx          | 12 +++++---
 src/client/views/nodes/DocumentView.tsx            |  5 ++--
 src/client/views/nodes/FilterBox.tsx               | 34 ++++++++--------------
 4 files changed, 24 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 959864a60..55fe84835 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -101,7 +101,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         const targetDataDoc = this.doc[DataSym];
         const value = DocListCast(targetDataDoc[this.props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
-        SelectionManager.DeselectAll();
+        if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views().some(dv => Doc.AreProtosEqual(dv.rootDoc, doc)))) SelectionManager.DeselectAll();
         if (result.length !== value.length) {
             const ind = targetDataDoc[this.props.fieldKey].indexOf(doc);
             const prev = ind && targetDataDoc[this.props.fieldKey][ind - 1];
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 5df33afe0..8a5425992 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -21,7 +21,7 @@ import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { EditableView } from "../EditableView";
 import { TREE_BULLET_WIDTH } from '../globalCssVariables.scss';
-import { DocumentView, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewProps, StyleProviderFunc, DocumentViewInternal } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { KeyValueBox } from '../nodes/KeyValueBox';
@@ -483,17 +483,21 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get headerElements() {
-        return this.props.treeViewHideHeaderFields() || Doc.IsSystem(this.doc) ? (null)
+        return this.props.treeViewHideHeaderFields() ? (null)
             : <>
                 <FontAwesomeIcon key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />
-                {this.doc.treeViewExpandedViewLock ? (null) :
+                {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? (null) :
                     <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
                         {this.treeViewExpandedView}
                     </span>}
             </>;
     }
 
-    showContextMenu = (e: React.MouseEvent) => simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+    showContextMenu = (e: React.MouseEvent) => {
+        DocumentViewInternal.SelectOnContextsMenu = false;
+        simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+        DocumentViewInternal.SelectOnContextsMenu = true;
+    }
     contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : this.doc.isFolder ?
         [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] :
         this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1c4008a55..703e78470 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -658,7 +658,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
-            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
+            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
             item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
 
@@ -746,8 +746,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
-        !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+        DocumentViewInternal.SelectOnContextsMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
     }
+    public static SelectOnContextsMenu = true;
 
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 30d7ed187..0a010c105 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -167,7 +167,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
         });
         return { strings: Array.from(valueSet.keys()), rtFields };
     }
-
+    removeFilterDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false;
     public removeFilter = (filterName: string) => {
         const targetDoc = FilterBox.targetDoc;
         const filterDoc = targetDoc.currentFilter as Doc;
@@ -190,8 +190,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                 }
             }
         }
+        return true;
     }
-
     /**
      * Responds to clicking the check box in the flyout menu
      */
@@ -199,22 +199,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
         const targetDoc = FilterBox.targetDoc;
         const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader);
         if (found !== -1) {
-            // comment this bit out later once the x works in treeview
-            (this.dataDoc[this.props.fieldKey] as List<Doc>).splice(found, 1);
-            const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
-            if (docFilter) {
-                let index: number;
-                while ((index = docFilter.findIndex(item => item.split(":")[0] === facetHeader)) !== -1) {
-                    docFilter.splice(index, 1);
-                }
-            }
-            const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string"));
-            if (docRangeFilters) {
-                let index: number;
-                while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === facetHeader)) !== -1) {
-                    docRangeFilters.splice(index, 3);
-                }
-            }
+            this.removeFilter(facetHeader);
         } else {
             const allCollectionDocs = DocListCast((targetDoc.data as any)[0].data);
             const facetValues = this.gatherFieldValues(targetDoc, facetHeader);
@@ -232,7 +217,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
             });
             let newFacet: Opt<Doc>;
             if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) {
-                newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, system: true, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, _forceActive: true, ignoreClick: true });
+                newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, system: true, _stayInCollection: true, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, _forceActive: true, ignoreClick: true });
                 Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
                 newFacet._textBoxPadding = 4;
                 const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
@@ -261,7 +246,11 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                 newFacet.target = targetDoc;
                 newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${facetHeader}")`);
             }
-            newFacet && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
+            this.props.docViewPath()[0].ComponentView
+            const remove = ScriptField.MakeScript(`documentView.props.removeDocument(self)`, { documentView: "any" });
+            newFacet.contextMenuScripts = new List<ScriptField>([remove!]);
+            newFacet.contextMenuLabels = new List<string>(["Remove"]);
+            Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
         }
     }
 
@@ -450,7 +439,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                     whenChildContentsActiveChanged={returnFalse}
                     treeViewHideTitle={true}
                     focus={returnFalse}
-                    treeViewHideHeaderFields={true}
+                    treeViewHideHeaderFields={false}
                     onCheckedClick={this.scriptField}
                     dontRegisterView={true}
                     styleProvider={this.props.styleProvider}
@@ -458,7 +447,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                     docViewPath={this.props.docViewPath}
                     scriptContext={this.props.scriptContext}
                     moveDocument={returnFalse}
-                    removeDocument={returnFalse}
+                    removeDocument={this.removeFilterDoc}
                     addDocument={returnFalse} />
             </div>
             <div className="filterBox-bottom">
@@ -519,6 +508,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string)
     const facetValues = Array.from(set).filter(v => v);
 
     let nonNumbers = 0;
+
     facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
     const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
         const doc = new Doc();
-- 
cgit v1.2.3-70-g09d2


From fcbe1957d985655167ee8365fbb8f2755bbfd206 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 6 Apr 2021 18:51:01 -0400
Subject: cleanup

---
 src/client/views/collections/CollectionView.tsx | 4 +---
 src/client/views/collections/TreeView.tsx       | 4 ++--
 src/client/views/nodes/DocumentView.tsx         | 4 ++--
 3 files changed, 5 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 91f159d8e..4dc0ee3e6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { computed, observable } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
@@ -35,8 +35,6 @@ import { SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
-import { FilterBox } from '../nodes/FilterBox';
-import { listSpec } from '../../../fields/Schema';
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 5cf8a6986..2cce9c2f5 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -493,9 +493,9 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     showContextMenu = (e: React.MouseEvent) => {
-        DocumentViewInternal.SelectOnContextsMenu = false;
+        DocumentViewInternal.SelectAfterContextMenu = false;
         simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
-        DocumentViewInternal.SelectOnContextsMenu = true;
+        DocumentViewInternal.SelectAfterContextMenu = true;
     }
     contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : this.doc.isFolder ?
         [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] :
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 703e78470..5c3d7e415 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -162,6 +162,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 
 @observer
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps, Document>(Document) {
+    public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
     @observable _animateScalingTo = 0;
     @observable _mediaState = 0;
     @observable _pendingDoubleClick = false;
@@ -746,9 +747,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
-        DocumentViewInternal.SelectOnContextsMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+        DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
     }
-    public static SelectOnContextsMenu = true;
 
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
-- 
cgit v1.2.3-70-g09d2


From d021eb6fe792e9c76b139b5a706a90a086f3fe53 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Apr 2021 12:59:22 -0400
Subject: made "search" only appear if context menu is > 15 items.

---
 src/client/views/ContextMenu.tsx                   |  27 ++--
 src/client/views/ContextMenuItem.tsx               |   7 +-
 src/client/views/collections/TreeView.tsx          |  17 ++-
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/nodes/DocumentView.tsx            | 149 +++++++++++----------
 5 files changed, 106 insertions(+), 96 deletions(-)

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

diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 1f7abf61a..d96de72e3 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -15,6 +15,7 @@ export class ContextMenu extends React.Component {
     @observable private _pageY: number = 0;
     @observable private _display: boolean = false;
     @observable private _searchString: string = "";
+    @observable private _showSearch: boolean = false;
     // afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
     @observable private _yRelativeToTop: boolean = true;
     @observable selectedIndex = -1;
@@ -137,11 +138,13 @@ export class ContextMenu extends React.Component {
         return y;
     }
 
+
     @action
-    displayMenu = (x: number, y: number, initSearch = "") => {
+    displayMenu = (x: number, y: number, initSearch = "", showSearch = false) => {
         //maxX and maxY will change if the UI/font size changes, but will work for any amount
         //of items added to the menu
 
+        this._showSearch = showSearch;
         this._pageX = x;
         this._pageY = y;
         this._searchString = initSearch;
@@ -215,11 +218,15 @@ export class ContextMenu extends React.Component {
 
     @computed get menuItems() {
         if (!this._searchString) {
-            return this._items.map(item => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} />);
+            return this._items.map(item => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={item.description} closeMenu={this.closeMenu} />);
         }
         return this.filteredViews;
     }
 
+    @computed get itemsNeedSearch() {
+        return this._showSearch ? 1 : this._items.reduce((p, mi) => p + ((mi as any).noexpand ? 1 : (mi as any).subitems?.length || 1), 0) > 15;
+    }
+
     render() {
         if (!this._display) {
             return null;
@@ -229,12 +236,13 @@ export class ContextMenu extends React.Component {
 
         const contents = (
             <>
-                <span className={"search-icon"}>
-                    <span className="icon-background">
-                        <FontAwesomeIcon icon="search" size="lg" />
-                    </span>
-                    <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
-                </span>
+                {!this.itemsNeedSearch ? (null) :
+                    <span className={"search-icon"}>
+                        <span className="icon-background">
+                            <FontAwesomeIcon icon="search" size="lg" />
+                        </span>
+                        <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
+                    </span>}
                 {this.menuItems}
             </>
         );
@@ -244,8 +252,7 @@ export class ContextMenu extends React.Component {
                     <div className="contextMenu-cont" style={style} ref={measureRef}>
                         {contents}
                     </div>
-                )
-                }
+                )}
             </Measure>
         );
     }
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index e63631161..6fe2abd21 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -4,7 +4,6 @@ import { observer } from "mobx-react";
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { UndoManager } from "../util/UndoManager";
-import { NumberLiteralType } from "typescript";
 
 export interface OriginalMenuProps {
     description: string;
@@ -33,8 +32,8 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
 
     constructor(props: ContextMenuProps | SubmenuProps) {
         super(props);
-        if ("subitems" in this.props) {
-            this.props.subitems?.forEach(i => this._items.push(i));
+        if ((this.props as SubmenuProps).subitems) {
+            (this.props as SubmenuProps).subitems?.forEach(i => this._items.push(i));
         }
     }
 
@@ -107,7 +106,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                     }}>
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
-            if (!("noexpand" in this.props)) {
+            if (!(this.props as SubmenuProps).noexpand) {
                 return <div className="contextMenu-inlineMenu">
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 2c3a6c0d7..43f9627e2 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -483,10 +483,10 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get headerElements() {
-        return this.props.treeViewHideHeaderFields() || Doc.IsSystem(this.doc) ? (null)
+        return this.props.treeViewHideHeaderFields() || (Doc.IsSystem(this.doc) && !this.doc.isFolder) ? (null)
             : <>
                 <FontAwesomeIcon key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />
-                {this.doc.treeViewExpandedViewLock ? (null) :
+                {this.doc.isFolder || this.doc.treeViewExpandedViewLock ? (null) :
                     <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
                         {this.treeViewExpandedView}
                     </span>}
@@ -494,11 +494,14 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     showContextMenu = (e: React.MouseEvent) => simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
-    contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : this.doc.isFolder ?
-        [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] :
-        this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
-            [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }] :
-            [{ script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }]
+    contextMenuItems = () => {
+        const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" };
+        return this.doc.isFolder ? [makeFolder] :
+            Doc.IsSystem(this.doc) ? [] :
+                this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
+                    [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }, makeFolder] :
+                    [{ script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }];
+    }
     onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
     onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d14b68fa7..a4f129b8c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -101,7 +101,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             else if (e.key === ":") {
                 DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y);
 
-                cm.displayMenu(this._downX, this._downY);
+                cm.displayMenu(this._downX, this._downY, undefined, true);
                 e.stopPropagation();
             } else if (e.key === "a" && (e.ctrlKey || e.metaKey)) {
                 e.preventDefault();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 26cf52f17..22543cc8a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -660,88 +660,89 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.props.contextMenuItems?.().forEach(item =>
             item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
 
-        const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
-        const appearance = cm.findByDescription("UI Controls...");
-        const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
-        !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
-        DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
-        !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
-
-        if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
-            !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
-            const existingOnClick = cm.findByDescription("OnClick...");
-            const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
-
-            const zorders = cm.findByDescription("ZOrder...");
-            const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
-            zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
-            zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
-            zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
-            !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
-
-            onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
-            onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
-            onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
-
-            if (!this.Document.annotationOn) {
-                const options = cm.findByDescription("Options...");
-                const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
-                !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
-
-                onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
-                onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
-                !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
-                onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
-                onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
-                onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
-                !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
-            } else if (DocListCast(this.Document.links).length) {
-                onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" });
-                onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" });
-                onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" });
-                !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
+        if (!this.props.Document.isFolder) {
+            const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+            const appearance = cm.findByDescription("UI Controls...");
+            const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
+            !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
+            DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
+            !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
+
+            if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
+                !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
+                const existingOnClick = cm.findByDescription("OnClick...");
+                const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+
+                const zorders = cm.findByDescription("ZOrder...");
+                const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
+                zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
+                zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
+                zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+                !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
+
+                onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
+                onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" });
+                onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+
+                if (!this.Document.annotationOn) {
+                    const options = cm.findByDescription("Options...");
+                    const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+                    !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+
+                    onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+                    onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
+                    !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
+                    onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
+                    onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
+                    onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
+                    !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
+                } else if (DocListCast(this.Document.links).length) {
+                    onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" });
+                    onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" });
+                    onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" });
+                    !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
+                }
             }
-        }
 
-        const funcs: ContextMenuProps[] = [];
-        if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
-            funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
-            funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
-            funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
-            cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
-        }
+            const funcs: ContextMenuProps[] = [];
+            if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
+                funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
+                funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
+                funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
+                cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
+            }
 
-        const more = cm.findByDescription("More...");
-        const moreItems = more && "subitems" in more ? more.subitems : [];
-        if (!Doc.IsSystem(this.rootDoc)) {
-            (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
-            if (!Doc.UserDoc().noviceMode) {
-                moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
-                moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
-
-                if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
-                    moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
-                    moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
-                    moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
+            const more = cm.findByDescription("More...");
+            const moreItems = more && "subitems" in more ? more.subitems : [];
+            if (!Doc.IsSystem(this.rootDoc)) {
+                (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
+                if (!Doc.UserDoc().noviceMode) {
+                    moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
+                    moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
+
+                    if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
+                        moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
+                        moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
+                        moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
+                    }
+                    moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
                 }
-                moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
             }
-        }
-
-        if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
-            moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
-        }
 
-        !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-        cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
+            if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
+                moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
+            }
 
-        const help = cm.findByDescription("Help...");
-        const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
-        !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
-        helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
-        !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
-        cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
+            !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+            cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
 
+            const help = cm.findByDescription("Help...");
+            const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
+            !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
+            helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
+            !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+            cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
+        }
         if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
         !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
-- 
cgit v1.2.3-70-g09d2

