From bf8f127f5aa906280fa12dc1ea2cc01bcf9ff40f Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Sun, 5 Jun 2022 19:41:06 -0400
Subject: refactored writing mode code

---
 src/client/views/nodes/button/FontIconBox.tsx | 134 +++++++++++++-------------
 1 file changed, 69 insertions(+), 65 deletions(-)

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

diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 3e18e86b3..7a527b767 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -706,76 +706,16 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
     else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics";
 });
 
-
-
-
-export function checkInksToGroup() {
-    // console.log("getting here to inks group");
-    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
-        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
-            // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
-            // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
-            const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => {
-                // console.log(inkDoc.x, inkDoc.y);
-            });
-        });
-    }
-}
-
-export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
+/**
+ * Creates the ink grouping once the user leaves the writing mode
+ */
+export function createInkGroup() {
     // TODO nda - if document being added to is a inkGrouping then we can just add to that group
     if (CurrentUserUtils.SelectedTool === InkTool.Write) {
         CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
             // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
             const selected = ffView.unprocessedDocs;
-            // loop through selected an get the bound
-            const bounds: { x: number, y: number, width?: number, height?: number }[] = []
-
-            selected.map(action(d => {
-                const x = NumCast(d.x);
-                const y = NumCast(d.y);
-                const width = d[WidthSym]();
-                const height = d[HeightSym]();
-                bounds.push({ x, y, width, height });
-            }))
-
-            const aggregBounds = aggregateBounds(bounds, 0, 0);
-            const marqViewRef = ffView._marqueeViewRef.current;
-
-            // set the vals for bounds in marqueeView
-            if (marqViewRef) {
-                marqViewRef._downX = aggregBounds.x;
-                marqViewRef._downY = aggregBounds.y;
-                marqViewRef._lastX = aggregBounds.r;
-                marqViewRef._lastY = aggregBounds.b;
-            }
-
-            selected.map(action(d => {
-                const dx = NumCast(d.x);
-                const dy = NumCast(d.y);
-                delete d.x;
-                delete d.y;
-                delete d.activeFrame;
-                delete d._timecodeToShow;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-                delete d._timecodeToHide;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-                // calculate pos based on bounds
-                if (marqViewRef?.Bounds) {
-                    d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
-                    d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
-                }
-                return d;
-            }));
-            ffView.props.removeDocument?.(selected);
-            // TODO: nda - this is the code to actually get a new grouped collection
-            const newCollection = marqViewRef?.getCollection(selected, undefined, [], true);
-            if (newCollection) {
-                newCollection.height = newCollection[HeightSym]();
-                newCollection.width = newCollection[WidthSym]();
-            }
-
-            // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
-            newCollection && ffView.props.addDocument?.(newCollection);
-            // TODO: nda - will probably need to go through and only remove the unprocessed selected docs
+            const newCollection = groupInkDocs(selected, ffView);
             ffView.unprocessedDocs = [];
 
             InkTranscription.Instance.transcribeInk(newCollection, ffView.layoutDoc, selected, false, ffView);
@@ -784,6 +724,70 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
     CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
 }
 
+/**
+ * Creates the groupings for a given list of ink docs on a specific doc view
+ * @param selected: the list of ink docs to create a grouping of
+ * @param docView: the view in which we want the grouping to be created
+ * @param word: optional param if the group we are creating is a word (subgrouping individual words)
+ * @returns a new collection Doc or undefined if the grouping fails
+ */
+export function groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
+    const bounds: { x: number, y: number, width?: number, height?: number }[] = []
+
+    // calculate the necessary bounds from the selected ink docs
+    selected.map(action(d => {
+        const x = NumCast(d.x);
+        const y = NumCast(d.y);
+        const width = d[WidthSym]();
+        const height = d[HeightSym]();
+        bounds.push({ x, y, width, height });
+    }))
+
+    // calculate the aggregated bounds
+    const aggregBounds = aggregateBounds(bounds, 0, 0);
+    const marqViewRef = docView._marqueeViewRef.current;
+
+    // set the vals for bounds in marqueeView
+    if (marqViewRef) {
+        marqViewRef._downX = aggregBounds.x;
+        marqViewRef._downY = aggregBounds.y;
+        marqViewRef._lastX = aggregBounds.r;
+        marqViewRef._lastY = aggregBounds.b;
+    }
+
+    // map through all the selected ink strokes and create the groupings
+    selected.map(action(d => {
+        const dx = NumCast(d.x);
+        const dy = NumCast(d.y);
+        delete d.x;
+        delete d.y;
+        delete d.activeFrame;
+        delete d._timecodeToShow;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+        delete d._timecodeToHide;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+        // calculate pos based on bounds
+        if (marqViewRef?.Bounds) {
+            d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+            d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+        }
+        return d;
+    }));
+    docView.props.removeDocument?.(selected);
+    // Gets a collection based on the selected nodes using a marquee view ref
+    const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+    if (newCollection) {
+        newCollection.height = newCollection[HeightSym]();
+        newCollection.width = newCollection[WidthSym]();
+        // if the grouping we are creating is an individual word
+        if (word) {
+            newCollection.title = word;
+        }
+    }
+
+    // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+    newCollection && docView.props.addDocument?.(newCollection);
+    return newCollection;
+}
+
 
 /** INK
  *      setActiveInkTool
-- 
cgit v1.2.3-70-g09d2


From 2e42d66be1439de347305cfd43c6f7e7042127c6 Mon Sep 17 00:00:00 2001
From: Naafiyan Ahmed <naafiyan@gmail.com>
Date: Sun, 5 Jun 2022 20:12:28 -0400
Subject: refactored writing mode code into InKTranscription

---
 src/client/views/GestureOverlay.tsx           |  8 +--
 src/client/views/InkTranscription.tsx         | 88 ++++++++++++++++++++++++++-
 src/client/views/nodes/button/FontIconBox.tsx | 86 +-------------------------
 3 files changed, 89 insertions(+), 93 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 226983138..1a7bb0808 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -24,7 +24,7 @@ import { RadialMenu } from "./nodes/RadialMenu";
 import HorizontalPalette from "./Palette";
 import { Touchable } from "./Touchable";
 import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import { checkInksToGroup, createInkGroup } from "./nodes/button/FontIconBox";
+import { InkTranscription } from "./InkTranscription";
 
 @observer
 export class GestureOverlay extends Touchable {
@@ -127,7 +127,7 @@ export class GestureOverlay extends Touchable {
             // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
             // and this seems to be the only way of differentiating pen and touch on touch events
             if (pt.radiusX > 1 && pt.radiusY > 1) {
-                createInkGroup();
+                InkTranscription.Instance.createInkGroup();
                 Doc.UserDoc().activeInkTool = InkTool.None;
                 this.prevPoints.set(pt.identifier, pt);
             }
@@ -499,7 +499,7 @@ export class GestureOverlay extends Touchable {
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
             setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => {
                 if (doubleTap) {
-                    createInkGroup();
+                    InkTranscription.Instance.createInkGroup();
                     CurrentUserUtils.SelectedTool = InkTool.None;
                     return;
                 }
@@ -649,8 +649,6 @@ export class GestureOverlay extends Touchable {
                     if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
                     this._points = controlPoints;
                     this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                    // TODO: nda - check inks to group here
-                    checkInksToGroup();
                 }
                 this._points = [];
             }
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 231ab694d..e1a80ae50 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -2,7 +2,7 @@ import * as iink from 'iink-js';
 import { action, observable } from 'mobx';
 import * as React from 'react';
 import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc';
-import { InkData, InkField } from "../../fields/InkField";
+import { InkData, InkField, InkTool } from "../../fields/InkField";
 import { Cast, DateCast, NumCast } from '../../fields/Types';
 import { DocumentType } from "../documents/DocumentTypes";
 import './InkTranscription.scss';
@@ -10,7 +10,7 @@ import { aggregateBounds } from '../../Utils';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentManager } from "../util/DocumentManager";
 import { InkingStroke } from './InkingStroke';
-import { groupInkDocs } from './nodes/button/FontIconBox';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
 
 
 export class InkTranscription extends React.Component {
@@ -174,7 +174,7 @@ export class InkTranscription extends React.Component {
             if (!docView) return;
             const marqViewRef = docView._marqueeViewRef.current;
             if (!marqViewRef) return;
-            groupInkDocs(selected, docView, word);
+            this.groupInkDocs(selected, docView, word);
         });
     }
 
@@ -238,6 +238,88 @@ export class InkTranscription extends React.Component {
             }
         }
     }
+    
+    /**
+ * Creates the ink grouping once the user leaves the writing mode
+ */
+createInkGroup() {
+    // TODO nda - if document being added to is a inkGrouping then we can just add to that group
+    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+            // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+            const selected = ffView.unprocessedDocs;
+            const newCollection = this.groupInkDocs(selected, ffView);
+            ffView.unprocessedDocs = [];
+
+            InkTranscription.Instance.transcribeInk(newCollection, ffView.layoutDoc, selected, false, ffView);
+        });
+    }
+    CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+}
+
+/**
+ * Creates the groupings for a given list of ink docs on a specific doc view
+ * @param selected: the list of ink docs to create a grouping of
+ * @param docView: the view in which we want the grouping to be created
+ * @param word: optional param if the group we are creating is a word (subgrouping individual words)
+ * @returns a new collection Doc or undefined if the grouping fails
+ */
+  groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
+    const bounds: { x: number, y: number, width?: number, height?: number }[] = []
+
+    // calculate the necessary bounds from the selected ink docs
+    selected.map(action(d => {
+        const x = NumCast(d.x);
+        const y = NumCast(d.y);
+        const width = d[WidthSym]();
+        const height = d[HeightSym]();
+        bounds.push({ x, y, width, height });
+    }))
+
+    // calculate the aggregated bounds
+    const aggregBounds = aggregateBounds(bounds, 0, 0);
+    const marqViewRef = docView._marqueeViewRef.current;
+
+    // set the vals for bounds in marqueeView
+    if (marqViewRef) {
+        marqViewRef._downX = aggregBounds.x;
+        marqViewRef._downY = aggregBounds.y;
+        marqViewRef._lastX = aggregBounds.r;
+        marqViewRef._lastY = aggregBounds.b;
+    }
+
+    // map through all the selected ink strokes and create the groupings
+    selected.map(action(d => {
+        const dx = NumCast(d.x);
+        const dy = NumCast(d.y);
+        delete d.x;
+        delete d.y;
+        delete d.activeFrame;
+        delete d._timecodeToShow;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+        delete d._timecodeToHide;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+        // calculate pos based on bounds
+        if (marqViewRef?.Bounds) {
+            d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+            d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+        }
+        return d;
+    }));
+    docView.props.removeDocument?.(selected);
+    // Gets a collection based on the selected nodes using a marquee view ref
+    const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+    if (newCollection) {
+        newCollection.height = newCollection[HeightSym]();
+        newCollection.width = newCollection[WidthSym]();
+        // if the grouping we are creating is an individual word
+        if (word) {
+            newCollection.title = word;
+        }
+    }
+
+    // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+    newCollection && docView.props.addDocument?.(newCollection);
+    return newCollection;
+}
 
     render() {
         return (
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 7a527b767..e16c055e4 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -706,89 +706,6 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
     else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics";
 });
 
-/**
- * Creates the ink grouping once the user leaves the writing mode
- */
-export function createInkGroup() {
-    // TODO nda - if document being added to is a inkGrouping then we can just add to that group
-    if (CurrentUserUtils.SelectedTool === InkTool.Write) {
-        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
-            // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
-            const selected = ffView.unprocessedDocs;
-            const newCollection = groupInkDocs(selected, ffView);
-            ffView.unprocessedDocs = [];
-
-            InkTranscription.Instance.transcribeInk(newCollection, ffView.layoutDoc, selected, false, ffView);
-        });
-    }
-    CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
-}
-
-/**
- * Creates the groupings for a given list of ink docs on a specific doc view
- * @param selected: the list of ink docs to create a grouping of
- * @param docView: the view in which we want the grouping to be created
- * @param word: optional param if the group we are creating is a word (subgrouping individual words)
- * @returns a new collection Doc or undefined if the grouping fails
- */
-export function groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
-    const bounds: { x: number, y: number, width?: number, height?: number }[] = []
-
-    // calculate the necessary bounds from the selected ink docs
-    selected.map(action(d => {
-        const x = NumCast(d.x);
-        const y = NumCast(d.y);
-        const width = d[WidthSym]();
-        const height = d[HeightSym]();
-        bounds.push({ x, y, width, height });
-    }))
-
-    // calculate the aggregated bounds
-    const aggregBounds = aggregateBounds(bounds, 0, 0);
-    const marqViewRef = docView._marqueeViewRef.current;
-
-    // set the vals for bounds in marqueeView
-    if (marqViewRef) {
-        marqViewRef._downX = aggregBounds.x;
-        marqViewRef._downY = aggregBounds.y;
-        marqViewRef._lastX = aggregBounds.r;
-        marqViewRef._lastY = aggregBounds.b;
-    }
-
-    // map through all the selected ink strokes and create the groupings
-    selected.map(action(d => {
-        const dx = NumCast(d.x);
-        const dy = NumCast(d.y);
-        delete d.x;
-        delete d.y;
-        delete d.activeFrame;
-        delete d._timecodeToShow;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-        delete d._timecodeToHide;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-        // calculate pos based on bounds
-        if (marqViewRef?.Bounds) {
-            d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
-            d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
-        }
-        return d;
-    }));
-    docView.props.removeDocument?.(selected);
-    // Gets a collection based on the selected nodes using a marquee view ref
-    const newCollection = marqViewRef?.getCollection(selected, undefined, true);
-    if (newCollection) {
-        newCollection.height = newCollection[HeightSym]();
-        newCollection.width = newCollection[WidthSym]();
-        // if the grouping we are creating is an individual word
-        if (word) {
-            newCollection.title = word;
-        }
-    }
-
-    // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
-    newCollection && docView.props.addDocument?.(newCollection);
-    return newCollection;
-}
-
-
 /** INK
  *      setActiveInkTool
  *      setStrokeWidth
@@ -796,8 +713,7 @@ export function groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, w
  **/
 
 ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) {
-    createInkGroup();
-
+    InkTranscription.Instance?.createInkGroup();
     if (checkResult) {
         return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ?
             Colors.MEDIUM_BLUE : "transparent";
-- 
cgit v1.2.3-70-g09d2


From 21eb25198c27d65e398d11b018a8dc792297e35a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 17 Jun 2022 09:33:01 -0400
Subject: updated fontIconBadge to use 'viewed' list to decrement value when
 items are viewed.  cleaned up how sharedDoc is setup in currentUserUtils to
 allow updates to not require rebuilding DB

---
 src/client/documents/Documents.ts                  | 10 ++-
 src/client/util/CurrentUserUtils.ts                | 75 +++++++++++++---------
 src/client/views/collections/TreeView.tsx          |  2 +-
 src/client/views/nodes/button/FontIconBadge.tsx    | 32 ++++-----
 src/client/views/nodes/button/FontIconBox.tsx      |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 src/fields/Doc.ts                                  |  1 +
 src/fields/Types.ts                                |  4 ++
 8 files changed, 77 insertions(+), 53 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b290b2d58..5f009573e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -98,6 +98,7 @@ export class DocumentOptions {
     allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?");
     childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection ");
     targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? ");
+    userColor?: string; // color associated with a Dash user (seen in header fields of shared documents)
     color?: string; // foreground color data doc
     backgroundColor?: STRt = new StrInfo("background color for data doc");
     _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true);
@@ -190,6 +191,10 @@ export class DocumentOptions {
     childLayoutString?: string; // template string for collection to use to render its children
     childDontRegisterViews?: boolean;
     childHideLinkButton?: boolean; // hide link buttons on all children
+    childContextMenuFilters?: List<ScriptField>;
+    childContextMenuScripts?: List<ScriptField>;
+    childContextMenuLabels?: List<string>;
+    childContextMenuIcons?: List<string>;
     hideLinkButton?: boolean; // whether the blue link counter button should be hidden
     hideDecorationTitle?: boolean;
     hideOpenButton?: boolean;
@@ -198,7 +203,6 @@ export class DocumentOptions {
     hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
     isTemplateDoc?: boolean;
-    watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count
     targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
     templates?: List<string>;
     hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images)
@@ -250,7 +254,8 @@ export class DocumentOptions {
     numBtnType?: string;
     numBtnMax?: number;
     numBtnMin?: number;
-    switchToggle?: boolean;
+    switchToggle?: boolean;    
+    badgeValue?: ScriptField;
 
     //LINEAR VIEW
     linearViewIsExpanded?: boolean; // is linear view expanded
@@ -294,6 +299,7 @@ export class DocumentOptions {
     treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
     treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically
+    treeViewChildDoubleClick?: ScriptField; // 
 
     // Action Button
     buttonMenu?: boolean; // whether a action button should be displayed 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 60bfc165b..b4e18a8bb 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -7,7 +7,7 @@ import { List } from "../../fields/List";
 import { PrefetchProxy } from "../../fields/Proxy";
 import { RichTextField } from "../../fields/RichTextField";
 import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
+import { BoolCast, Cast, DateCast, DocCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
 import { ImageField, nullAudio } from "../../fields/URLField";
 import { SharingPermissions } from "../../fields/util";
 import { Utils } from "../../Utils";
@@ -354,6 +354,7 @@ export class CurrentUserUtils {
     }
 
     static menuBtnDescriptions(doc: Doc) {
+        const badgeValue = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)")
         return [
             { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' },
             { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' },
@@ -361,7 +362,7 @@ export class CurrentUserUtils {
             { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
             { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
             { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' },
-            { title: "Shared  with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
+            { title: "Shared  with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue},
             { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
             { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
         ];
@@ -369,8 +370,9 @@ export class CurrentUserUtils {
 
     static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
         if (doc.menuStack === undefined) {
-            await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId);  // sets up the right sidebar collection for mobile upload documents and sharing
-            const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) =>
+            await this.setupLinkDocs(doc, linkDatabaseId);
+            await this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
+            const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) =>
                 Docs.Create.FontIconDocument({
                     icon,
                     btnType: ButtonType.MenuButton,
@@ -387,7 +389,7 @@ export class CurrentUserUtils {
                     _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
                     _width: 60,
                     _height: 60,
-                    watchedDocuments,
+                    badgeValue,
                     onClick: ScriptField.MakeScript(click, { scriptContext: "any" })
                 })
             );
@@ -900,10 +902,7 @@ export class CurrentUserUtils {
     }
 
     // Sharing sidebar is where shared documents are contained
-    static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
-        if (doc.myPublishedDocs === undefined) {
-            doc.myPublishedDocs = new List<Doc>();
-        }
+    static async setupLinkDocs(doc: Doc, linkDatabaseId: string) {
         if (doc.myLinkDatabase === undefined) {
             let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId);
             if (!linkDocs) {
@@ -915,28 +914,41 @@ export class CurrentUserUtils {
             }
             doc.myLinkDatabase = new PrefetchProxy(linkDocs);
         }
-        // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document
-        if (doc.mySharedDocs === undefined) {
-            let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer");
-            if (!sharedDocs) {
-                sharedDocs = Docs.Create.TreeDocument([], {
-                    title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
-                    _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
-                    _chromeHidden: true, boxShadow: "0 0",
-                    dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
-                }, sharingDocumentId + "outer", sharingDocumentId);
-                (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
-            }
-            if (sharedDocs instanceof Doc) {
-                Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
-                const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
-                const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name });
-                sharedDocs.childContextMenuFilters = new List<ScriptField>([dashboardFilter!,]);
-                sharedDocs.childContextMenuScripts = new List<ScriptField>([addToDashboards!,]);
-                sharedDocs.childContextMenuLabels = new List<string>(["Add to Dashboards",]);
-                sharedDocs.childContextMenuIcons = new List<string>(["user-plus",]);
-            }
-            doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc);
+    }
+    // A user's sharing document is where all documents that are shared to that user are placed.  
+    // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field
+    // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents
+    static async setupSharedDocs(doc: Doc, sharingDocumentId: string) {
+        const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
+        const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name });
+        const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name})
+           
+        const sharedDocOpts:DocumentOptions = {
+            title: "My Shared Docs",
+            userColor: "rgb(202, 202, 202)",
+            childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]),
+            childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
+            childContextMenuLabels: new List<string>(["Add to Dashboards",]),
+            childContextMenuIcons: new List<string>(["user-plus",]),
+            treeViewChildDoubleClick: dblClkScript, 
+         };
+        const sharedRequiredDocOpts:DocumentOptions = { 
+            "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
+            childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
+            // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar
+            _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, 
+            explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
+        };
+        
+        const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer"));
+        if (!(sharedDocs instanceof Doc)) {
+            doc.mySharedDocs = new PrefetchProxy(
+                Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId));
+        } else {
+            Object.entries(sharedRequiredDocOpts).forEach(pair => {
+                const targetDoc = pair[0].startsWith("_") ? sharedDocs as Doc : Doc.GetProto(sharedDocs as Doc);
+                targetDoc[pair[0]] = pair[1];
+            });
         }
     }
 
@@ -1044,6 +1056,7 @@ export class CurrentUserUtils {
         doc.savedFilters = new List<Doc>();
         doc.filterDocCount = 0;
         doc.freezeChildren = "remove|add";
+        doc.myPublishedDocs = doc.myPublishedDocs ?? new List<Doc>();
         doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true });
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
         this.setupDocTemplates(doc); // sets up the template menu of templates
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 8824750a3..59dc5671b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -613,7 +613,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
     }
 
-    onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
+    onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null));
 
     refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document);
     ignoreEvent = (e: any) => {
diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx
index cf86b5e07..df17d603f 100644
--- a/src/client/views/nodes/button/FontIconBadge.tsx
+++ b/src/client/views/nodes/button/FontIconBadge.tsx
@@ -6,31 +6,31 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils
 import { DragManager } from "../../../util/DragManager";
 import "./FontIconBadge.scss";
 
-interface FontIconBadgeProps {
-    collection: Doc | undefined;
+interface FontIconBadgeProps {    
+    value: string | undefined;
 }
 
 @observer
 export class FontIconBadge extends React.Component<FontIconBadgeProps> {
     _notifsRef = React.createRef<HTMLDivElement>();
 
-    onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e,
-            (e: PointerEvent) => {
-                const dragData = new DragManager.DocumentDragData([this.props.collection!]);
-                DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
-                return true;
-            },
-            returnFalse, emptyFunction, false);
-    }
+    // onPointerDown = (e: React.PointerEvent) => {
+    //     setupMoveUpEvents(this, e,
+    //         (e: PointerEvent) => {
+    //             const dragData = new DragManager.DocumentDragData([this.props.collection!]);
+    //             DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
+    //             return true;
+    //         },
+    //         returnFalse, emptyFunction, false);
+    // }
 
     render() {
-        if (!(this.props.collection instanceof Doc)) return (null);
-        const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; //  Object.keys(d).length).length; // filter out any documents that we can't read
+        if (this.props.value === undefined) return (null);
         return <div className="fontIconBadge-container" ref={this._notifsRef}>
-            <div className="fontIconBadge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}
-                onPointerDown={this.onPointerDown} >
-                {length}
+            <div className="fontIconBadge" style={{ "display": "initial" }}
+               // onPointerDown={this.onPointerDown} 
+               >
+                {this.props.value}
             </div>
         </div>;
     }
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index a1b9023f3..97e6eddfe 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
                         {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
                         {menuLabel}
-                        <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
+                        <FontIconBadge value={ScriptCast(this.Document.badgeValue,null)?.script.run({self:this.Document}).result} />
                     </div >
                 );
                 break;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f29b879b3..16a523b40 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -382,11 +382,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
-    // creates links between terms in a document and documents which have a matching Id
+    // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
         const editorView = this._editorView;
         if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
-            const autoLinkTerm = StrCast(target.title).replace(/^@/, "");
+            const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); 
             const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm);
             var alink: Doc | undefined;
             flattened1.forEach((flat, i) => {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6abc27b23..7b72787d1 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1436,6 +1436,7 @@ ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.cop
 ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); });
 ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); });
 ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); });
+ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); });
 ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
 ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
 ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); });
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 7e2aa5681..bf40a0d7b 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -76,6 +76,10 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal
     return defaultVal === null ? undefined : defaultVal;
 }
 
+export function DocCast(field: FieldResult, defaultVal?: Doc) {
+    return Cast(field, Doc, null) ?? defaultVal;
+}
+
 export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
     return Cast(field, "number", defaultVal);
 }
-- 
cgit v1.2.3-70-g09d2

