From d1e4d4a9daae4f037cf8b22e1011d97680a01e27 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Tue, 2 May 2023 17:44:00 -0400
Subject: update

properties view, document decorations, and sharing manager show + use correct acl
---
 .../views/nodes/formattedText/FormattedTextBox.tsx     | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a2c1195cb..0e4330fa5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -19,7 +19,7 @@ import { RichTextField } from '../../../../fields/RichTextField';
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
+import { GetEffectiveAcl, normalizeEmail, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from '../../../DocServer';
@@ -295,11 +295,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
-            const effectiveAcl = GetEffectiveAcl(dataDoc);
-
+            // const effectiveAcl = GetEffectiveAcl(dataDoc);
+            const effectiveAcl = GetEffectiveAcl(this.rootDoc);
+            
             const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
-
-            if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
+            
+            if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
                 const accumTags = [] as string[];
                 state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
                     if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
@@ -1689,7 +1690,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         e.stopPropagation();
         for (var i = state.selection.from; i <= state.selection.to; i++) {
             const node = state.doc.resolve(i);
-            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
                 e.preventDefault();
             }
         }
@@ -1708,7 +1709,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             default:
                 if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
             case ' ':
-                [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) &&
+                if (e.code == "Space"){
+                    break;
+                }
+                [AclEdit, AclAugment, AclSelfEdit, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
                     this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
         }
         this.startUndoTypingBatch();
-- 
cgit v1.2.3-70-g09d2


From 50876028b994044f2a9426a4efb85a363f0d7168 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Thu, 4 May 2023 10:46:20 -0400
Subject: properties view + sharing manager are consistent, and all text
 updates properly

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/SharingManager.scss                |   5 +
 src/client/util/SharingManager.tsx                 | 120 ++++-----------------
 src/client/views/DocComponent.tsx                  |  10 +-
 src/client/views/DocumentDecorations.scss          |   3 +-
 src/client/views/DocumentDecorations.tsx           |   6 +-
 src/client/views/Main.tsx                          |   2 +-
 src/client/views/MarqueeAnnotator.tsx              |   4 +-
 src/client/views/PropertiesView.scss               |   4 +-
 src/client/views/PropertiesView.tsx                |  93 ++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |   9 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |  12 +--
 src/fields/Doc.ts                                  |   3 -
 src/fields/util.ts                                 |   6 +-
 14 files changed, 81 insertions(+), 198 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f5510d7e1..9c67f1f37 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -506,7 +506,7 @@ export namespace Docs {
                         description: '',
                         showCaption: 'description',
                         backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
-                        _removeDropProperties: new List(['isLinkButton']),
+                        // _removeDropsties: new List(['isLinkButton']),
                     },
                 },
             ],
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index f47c48805..3cab77816 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -23,6 +23,11 @@
             z-index: 999;
         }
 
+        .share-title {
+            display: inline-flex;
+            gap: 5px;
+        }
+
         .share-container {
             .share-setup {
                 display: flex;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 5e61f6d3c..40a282db0 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -25,6 +25,7 @@ import { GroupMemberView } from './GroupMemberView';
 import { LinkManager } from './LinkManager';
 import { SelectionManager } from './SelectionManager';
 import './SharingManager.scss';
+import { IconButton, Size } from 'browndash-components';
 
 export interface User {
     email: string;
@@ -159,27 +160,14 @@ export class SharingManager extends React.Component<{}> {
         const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
         const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
         
-        // var docs: Doc[] = [];
-        // const dashboardList = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
-        // const dashboard = dashboardList[0]
-        // docs.push(dashboard!)
-        // const tabs = DocListCast(dashboard?.data)
-        // tabs.forEach(tab => {
-        //     docs.push(tab)
-        //     var newDocs = DocListCast(tab.data)
-        //     newDocs.forEach(newDoc => 
-        //         docs.push(newDoc))
-        // })
-
-        // if (!docs){
-        //     return null;
-        // }
+        // setting the same acl for a docs within the doc being shared
+        var childDocs = DocListCast(target.data)
+        childDocs.map(doc => {
+            this.setInternalSharing(recipient, permission, doc)
+        })
 
         const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
 
-        //var docs: Doc[] = [];
-        docs.push(targetDoc!)
-
         // ! ensures it returns true if document has been shared successfully, false otherwise
         return !docs
             .map(doc => (this.layoutDocAcls ? doc : doc[DataSym]))
@@ -191,7 +179,6 @@ export class SharingManager extends React.Component<{}> {
                 } else {
                     if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
                 }
-                doc = targetDoc
 
                 distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
                 this.setDashboardBackground(doc, permission as SharingPermissions);
@@ -211,19 +198,15 @@ export class SharingManager extends React.Component<{}> {
         const key = normalizeEmail(StrCast(group.title));
         const acl = `acl-${key}`;
         const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
-
-        var docs: Doc[] = [];
-        const dashboardList = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.props.Document);
-        const dashboard = dashboardList[0]
-        const tabs = DocListCast(dashboard?.data)
-        tabs.forEach(tab => {
-            var newDocs = DocListCast(tab.data)
-            newDocs.forEach(newDoc => docs.push(newDoc))
-        })
         
-        if (!docs){
-            return null;
-        }
+        // setting the same acl for a docs within the doc being shared
+        var childDocs = DocListCast(target.data)
+        childDocs.map(doc => {
+            this.setInternalGroupSharing(group, permission, doc)
+        })
+
+        const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
+
         // ! ensures it returns true if document has been shared successfully, false otherwise
         return !docs
             .map(doc => (this.layoutDocAcls ? doc : doc[DataSym]))
@@ -292,6 +275,7 @@ export class SharingManager extends React.Component<{}> {
             const dashboards = DocListCast(Doc.MyDashboards.data);
             docs.forEach(doc => {
                 const isDashboard = dashboards.indexOf(doc) !== -1;
+                this.shareFromPropertiesSidebar(shareWith, permission, DocListCast(doc.data))
                 if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
                 this.setDashboardBackground(doc, permission as SharingPermissions);
             });
@@ -364,30 +348,15 @@ export class SharingManager extends React.Component<{}> {
     //         return;
     //     }
     //     targetDoc["acl-" + PublicKey] = permission;
-    // }
-
-    private get sharingUrl() {
-        if (!this.targetDoc) {
-            return undefined;
-        }
-        const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
-        return `${baseUrl}?sharing=true`;
-    }
+    // }s
 
     /**
      * Copies the Public sharing url to the user's clipboard.
      */
     private copyURL = (e: any) => {
-        Utils.CopyText(this.sharingUrl!)
+        Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]))
     }
 
-    // copy = action(() => {
-    //     if (this.sharingUrl) {
-    //         Utils.CopyText(this.sharingUrl);
-    //         this.copied = true;
-    //     }
-    // });
-
     /**
      * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
      */
@@ -396,7 +365,6 @@ export class SharingManager extends React.Component<{}> {
         if (!uniform) dropdownValues.unshift('-multiple-');
         if (!override) dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
         return dropdownValues
-            .filter(permission => permission != SharingPermissions.SelfEdit && (!Doc.noviceMode || ![SharingPermissions.SelfEdit].includes(permission as any)))
             .map(permission => (
                 <option key={permission} value={permission}>
                     {permission}
@@ -546,19 +514,6 @@ export class SharingManager extends React.Component<{}> {
         // users in common between all docs
         const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc : doc[DataSym])).map(doc => doc?.[AclSym] && Object.keys(doc[AclSym])));
 
-        // including all users on dashboard
-        // SharingManager.Instance.users.forEach(eachUser => {
-        //     var userOnDashboard = true;
-        //     if(Doc.ActiveDashboard){
-        //         if(Doc.ActiveDashboard['acl-'+normalizeEmail(eachUser.user.email)]=='' || Doc.ActiveDashboard['acl-'+normalizeEmail(eachUser.user.email)]==undefined){
-        //             userOnDashboard = false;
-        //         }
-        //     }
-        //     if (userOnDashboard && !commonKeys.includes(`acl-${normalizeEmail(eachUser.user.email)}`)){
-        //         users.push(eachUser.user);
-        //     }
-        // });
-
 
         // the list of users shared with
         const userListContents: (JSX.Element | null)[] = users
@@ -570,19 +525,6 @@ export class SharingManager extends React.Component<{}> {
                 const dashboard = Doc.ActiveDashboard
                 var docToUse = dashboard
 
-                // const tabs = DocListCast(dashboard?.data)
-                // tabs.forEach(tab => {
-                //     if (tab.title == targetDoc.title){
-                //         docToUse = tab
-                //     }
-                //     var newdocsList = DocListCast(tab.data)
-                //     newdocsList.forEach(newDoc => {
-                //         if (newDoc.title == targetDoc.title){
-                //             docToUse = newDoc
-                //         }
-                //     })
-                // })
-
                 docToUse = Doc.GetProto(this.targetDoc!)
 
                 const userKey = `acl-${normalizeEmail(user.email)}`;
@@ -641,22 +583,6 @@ export class SharingManager extends React.Component<{}> {
             return !permissions ? null : (
                 <div key={groupKey} className={'container'}>
                     <div className={'padding'} >{StrCast(group.title)}</div>
-                    <div>
-                        {StrCast(group.title)==='Public' ? (
-                            <div title={"Copy Public URL"} onClick={this.copyURL}>
-                                {/* <IconButton
-                                    size={Size.SMALL}
-                                    isCircle={true}
-                                    hoverStyle="gray"
-                                    onClick={() => this.copyURL}
-                                    icon={<FontAwesomeIcon icon="copy" />}
-                                /> */}
-                                <FontAwesomeIcon icon={'copy'} size={'sm'} />
-                            </div>
-                        ) : (
-                            <div/>
-                        )}
-                    </div>
                     {group instanceof Doc ? (
                         <div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}>
                             <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
@@ -679,19 +605,16 @@ export class SharingManager extends React.Component<{}> {
             <div className={'sharing-interface'}>
                 {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
                 <div className="sharing-contents">
-                    <p className={'share-title'}>
+                    <p className="share-title">
                         <b>Share </b>
                         {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
+                        {/* <FontAwesomeIcon title={"Copy Public URL"} icon={'copy'} size={'sm'} onClick={this.copyURL}/> */}
+                        <IconButton size={Size.SMALL} isCircle={true} tooltip='Copy Public URL' hoverStyle="gray" onClick={this.copyURL}icon={<FontAwesomeIcon icon="copy" />}/>
                     </p>
                     <div className={'close-button'} onClick={this.close}>
                         <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
                     </div>
-                    {/* {this.linkVisible ?
-                        <div>
-                            {this.sharingUrl}
-                        </div> :
-                        (null)} */}
-                    {
+                    { admin ? 
                         <div className="share-container">
                             <div className="share-setup">
                                 <Select
@@ -730,6 +653,7 @@ export class SharingManager extends React.Component<{}> {
                                 )}
                             </div>
                         </div>
+                        : <br></br>
                     }
                     <div className="main-container">
                         <div className={'individual-container'}>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0b92fd864..5a1dae8db 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -222,8 +222,12 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
                     if (effectiveAcl === AclAugment) {
                         added.map(doc => {
-                            if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
                             doc.context = this.props.Document;
+                            const contextDoc = Cast(doc.context, Doc, null)
+                            if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) { 
+                                if (contextDoc) inheritParentAcls(contextDoc, doc);
+                                else if (Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
+                            };
                             if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
                             Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
                         });
@@ -237,7 +241,9 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                                 doc.context = this.props.Document;
                                 if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
 
-                                Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
+                                const contextDoc = Cast(doc.context, Doc, null)
+                                if (contextDoc) inheritParentAcls(contextDoc, doc);
+                                else if (Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
                             });
                         const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
                         if (annoDocs instanceof List) annoDocs.push(...added);
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index ce5378e69..32bcc872a 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -237,8 +237,7 @@ $resizeHandler: 8px;
             color: rgb(71, 71, 71);
             border-color: rgb(71, 71, 71);
         }
-        .documentDecorations-shareEdit,
-        .documentDecorations-shareSelf-Edit{
+        .documentDecorations-shareEdit{
             width: calc(100% + 10px);
             background: rgb(235, 235, 145);
             color: rgb(75, 75, 5);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3e0a1f7a4..b769056d8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -32,6 +32,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
 import { SharingManager } from '../util/SharingManager';
+import { DocServer } from '../DocServer';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -754,9 +755,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             case ("Edit"):
                 shareSymbolIcon = "⬢ ";
                 break;
-            case ("Self-Edit"):
-                shareSymbolIcon = "⬢ ";
-                break;
             case ("Augment"):
                 shareSymbolIcon = "⬟ ";
                 break;
@@ -794,7 +792,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 // const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
                 // return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
                 const effectiveAcl = GetEffectiveAcl(Doc.GetProto(seldocview.rootDoc))
-                return docView.rootDoc.stayInCollection || (effectiveAcl !== AclAdmin && effectiveAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
+                return docView.rootDoc.stayInCollection || (effectiveAcl !== AclAdmin && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
             });
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
             <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 6b18caed0..7da9dc603 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -26,7 +26,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure
     root.render(<FieldLoader />);
     window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
     const info = await CurrentUserUtils.loadCurrentUser();
-    if (info.email === 'guest') DocServer.Control.makeReadOnly();
+    // if (info.email === 'guest') DocServer.Control.makeReadOnly();
     await CurrentUserUtils.loadUserDocument(info.id);
     setTimeout(() => {
         document.getElementById('root')!.addEventListener(
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 30867a774..094c9cc61 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,6 +1,6 @@
 import { action, observable, ObservableMap, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, Opt } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { NumCast } from '../../fields/Types';
@@ -202,7 +202,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => {
         // creates annotation documents for current highlights
         const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
-        const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
+        const annotationDoc = [AclAugment, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
         addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
         return (annotationDoc as Doc) ?? undefined;
     };
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 50bf6ce74..71107a5f9 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -172,8 +172,7 @@
             border-radius: 6px;
             border: 1px solid rgb(71, 71, 71);
         }
-        & .propertiesView-shareDropDownEdit,
-        .propertiesView-shareDropDownSelf-Edit{
+        & .propertiesView-shareDropDownEdit{
             padding: 5px;
             background: rgb(235, 235, 145);
             color: rgb(75, 75, 5);
@@ -426,7 +425,6 @@
         background: inherit;
         border: none;
         background: inherit;
-        // width: 100%;
         text-align: justify; // for Edge
         text-align-last: end;
         &:hover {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index a6a5347ad..984c2dc04 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -369,7 +369,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     changePermissions = (e: any, user: string) => {
         const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc : DocCast(doc)[DataSym]));
         if (user=="Public"){
-            docs[0]['acl-' +user] = e.currentTarget.value as SharingPermissions;
+            // docs[0]['acl-' +user] = e.currentTarget.value as SharingPermissions;
+            SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs);
         }
         else{
 
@@ -385,12 +386,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         if (permission === '-multiple-') dropdownValues.unshift(permission);
         if (user !== 'Override') {
             dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
-            // dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
         }
         return (
             <select className="propertiesView-permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}>
                 {dropdownValues
-                    .filter(permission => permission != SharingPermissions.SelfEdit && (!Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)))
+                    .filter(permission => !Doc.noviceMode || ![SharingPermissions.View].includes(permission as any))
                     .map(permission => (
                         <option className="propertiesView-permisssions-select" key={permission} value={permission}>
                             {' '}{permission}{' '}
@@ -464,9 +464,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             case ("Edit"):
                 dropDownText = "⬢ ";
                 break;
-            case ("Self-Edit"):
-                dropDownText = "⬢ ";
-                break;
             case ("Augment"):
                 dropDownText = "⬟ ";
                 break;
@@ -493,53 +490,36 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         );
     }
 
+    /**
+     * Sorting algorithm to sort users.
+     */
+    sortUsers = (u1: String, u2: String) => {
+        return u1 > u2 ? -1 : u1 === u2 ? 0 : 1;
+    };
+
     /**
      * @returns the sharing and permissions panel.
      */
     @computed get sharingTable() {
-        // const docToUse = this.selectedDocumentView?.rootDoc;
-
-        // const dashboard = Doc.ActiveDashboard
-        // var docToUse = dashboard
-        // const tabs = DocListCast(dashboard?.data)
-        // tabs.forEach(tab => {
-        //     if (tab.title == this.selectedDoc?.title){
-        //         docToUse = tab
-        //     }
-        //     var newdocsList = DocListCast(tab.data)
-        //     newdocsList.forEach(newDoc => {
-        //         if (newDoc.title == this.selectedDoc?.title){
-        //             docToUse = newDoc
-        //         }
-        //     })
-        // })
 
         const docToUse = this.selectedDoc;
-
         if (!docToUse){
             return null;
         }
         // all selected docs
         const docs =
             SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? docToUse : docToUse?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]));
-
         const target = docs[0];
 
-        // tslint:disable-next-line: no-unnecessary-callback-wrapper
-        // const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
-        // const effectiveAcls = GetEffectiveAcl(docToUse)
         const effectiveAcls = GetEffectiveAcl(target)
-
-        // const showAdmin = effectiveAcls.every(acl => acl === AclAdmin);
         const showAdmin= effectiveAcls==AclAdmin || docToUse!['acl-'+normalizeEmail(Doc.CurrentUserEmail)]=='Owner';
 
         // users in common between all docs
-        const commonKeys: string[] = intersection(...docs.map(doc => doc?.[AclSym] && Object.keys(doc[AclSym]).filter(key => key !== 'acl-Me')));
+        // const commonKeys: string[] = intersection(...docs.map(doc => doc?.[AclSym] && Object.keys(doc[AclSym]).filter(key => key !== 'acl-Me')));
+        // const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author);
         const tableEntries = [];
         const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
-        const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author);
 
-        usersAdded.push(target.author);
         SharingManager.Instance.users.forEach(eachUser => {
             var userOnDashboard = true;
             var permission = StrCast(target[`acl-${normalizeEmail(eachUser.user.email)}`])
@@ -548,46 +528,33 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     userOnDashboard = false;
                 }
             }
-            if (userOnDashboard && !usersAdded.includes(eachUser.user.email) && eachUser.user.email!='Public'){
-                tableEntries.unshift(this.sharingItem(eachUser.user.email, showAdmin, permission, false)); // adds each user
+            if (userOnDashboard && !usersAdded.includes(eachUser.user.email) && eachUser.user.email!='Public' && eachUser.user.email!=target.author){
+                // tableEntries.unshift(this.sharingItem(eachUser.user.email, showAdmin, permission, false)); // adds each user
                 usersAdded.push(eachUser.user.email);
             }
         });
-
-        // commonKeys.forEach(user => {
-        //     const userEmail = user.slice(4)
-        //     var userOnDashboard = true;
-        //     if(Doc.ActiveDashboard){
-        //         if(Doc.ActiveDashboard[user]=='' || Doc.ActiveDashboard[user]==undefined){
-        //             userOnDashboard = false;
-        //         }
-        //     }
-        //     if (userOnDashboard && !usersAdded.includes(denormalizeEmail(userEmail)) && userEmail!='Public'){
-        //         tableEntries.unshift(this.sharingItem(denormalizeEmail(userEmail), showAdmin, StrCast(docToUse![`acl-${normalizeEmail((userEmail))}`]), false)); // adds each user
-        //         usersAdded.push(denormalizeEmail(userEmail));
-        //     }
-        // })
-
-        // current user
-        const userEmail = Doc.CurrentUserEmail
-        var userOnDashboard = true;
-        if(Doc.ActiveDashboard){
-            if(Doc.ActiveDashboard['acl-'+normalizeEmail(userEmail)]=='' || Doc.ActiveDashboard['acl-'+normalizeEmail(userEmail)]==undefined){
-                userOnDashboard = false;
-            }
-        }
-        if (userOnDashboard && !usersAdded.includes(denormalizeEmail(userEmail)) && userEmail!='Public'){
-            tableEntries.unshift(this.sharingItem(denormalizeEmail(userEmail), showAdmin, StrCast(target[`acl-${normalizeEmail((userEmail))}`]), false)); // adds each user
-            usersAdded.push(denormalizeEmail(userEmail));
+        
+        usersAdded.sort(this.sortUsers)
+        usersAdded.map(userEmail => {
+            const permission = StrCast(target[`acl-${normalizeEmail(userEmail)}`])
+            tableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission, false)); // adds each user
+        })
+
+        // add current user
+        var userEmail = Doc.CurrentUserEmail
+        if (userEmail == 'guest') userEmail = 'Public';
+        if (!usersAdded.includes(userEmail) && userEmail!='Public' && userEmail!=target.author){
+            tableEntries.unshift(this.sharingItem(userEmail, showAdmin, StrCast(target[`acl-${normalizeEmail((userEmail))}`]), false)); // adds each user
+            usersAdded.push(userEmail);
         }
 
-        // if (ownerSame ) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false); // shift owner to top
-        tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false); // shift owner to top
+        // shift owner to top
+        tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false);
 
         return (
             <div > Sharing Mode
                 <div>{
-                    this.publicACLDropDown(true, StrCast(target['acl-Public']), false)}
+                    this.publicACLDropDown(showAdmin, StrCast(target['acl-Public']), false)}
                 </div>
             <div>  <br></br> Who has access to the Dashboard? </div>
             <div className="propertiesView-sharingTable">{
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0e4330fa5..c073c7468 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -295,12 +295,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
-            // const effectiveAcl = GetEffectiveAcl(dataDoc);
             const effectiveAcl = GetEffectiveAcl(this.rootDoc);
             
             const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
             
-            if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
+            if ([AclEdit, AclAdmin, AclAugment].includes(effectiveAcl)) {
                 const accumTags = [] as string[];
                 state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
                     if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
@@ -1690,7 +1689,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         e.stopPropagation();
         for (var i = state.selection.from; i <= state.selection.to; i++) {
             const node = state.doc.resolve(i);
-            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) ) {
                 e.preventDefault();
             }
         }
@@ -1712,7 +1711,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 if (e.code == "Space"){
                     break;
                 }
-                [AclEdit, AclAugment, AclSelfEdit, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
+                [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
                     this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
         }
         this.startUndoTypingBatch();
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 68b0488a2..64cc7addc 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -4,7 +4,7 @@ import { Schema } from 'prosemirror-model';
 import { splitListItem, wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
 import { liftTarget } from 'prosemirror-transform';
-import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit} from '../../../../fields/Doc';
 import { GetEffectiveAcl } from '../../../../fields/util';
 import { Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
@@ -48,14 +48,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         switch (GetEffectiveAcl(props.DataDoc)) {
             case AclAugment:
                 return false;
-            case AclSelfEdit:
-                for (var i = state.selection.from; i < state.selection.to; i++) {
-                    const marks = state.doc.resolve(i)?.marks?.();
-                    if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) {
-                        return false;
-                    }
-                }
-                break;
         }
         return true;
     };
@@ -259,7 +251,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
 
     //Command to create a blank space
     bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        if (!canEdit(state)) return true;
+        if (GetEffectiveAcl(props.DataDoc)!=AclEdit && GetEffectiveAcl(props.DataDoc)!=AclAugment && GetEffectiveAcl(props.DataDoc)!=AclAdmin) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         dispatch(splitMetadata(marks, state.tr));
         return false;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5e2e40979..68279372a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -102,7 +102,6 @@ export const AclUnset = Symbol('AclUnset');
 export const AclPrivate = Symbol('AclOwnerOnly');
 export const AclReadonly = Symbol('AclReadOnly');
 export const AclAugment = Symbol('AclAugment');
-export const AclSelfEdit = Symbol('AclSelfEdit');
 export const AclEdit = Symbol('AclEdit');
 export const AclAdmin = Symbol('AclAdmin');
 export const UpdatingFromServer = Symbol('UpdatingFromServer');
@@ -115,7 +114,6 @@ export enum aclLevel {
     unshared = 0,
     viewable = 1,
     augmentable = 2,
-    selfEditable = 2.5,
     editable = 3,
     admin = 4,
 }
@@ -124,7 +122,6 @@ export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermis
     [AclPrivate,  { level: aclLevel.unshared,     name: SharingPermissions.None }],
     [AclReadonly, { level: aclLevel.viewable,     name: SharingPermissions.View }],
     [AclAugment,  { level: aclLevel.augmentable,  name: SharingPermissions.Augment}],
-    [AclSelfEdit, { level: aclLevel.selfEditable, name: SharingPermissions.SelfEdit }],
     [AclEdit,     { level: aclLevel.editable,     name: SharingPermissions.Edit }],
     [AclAdmin,    { level: aclLevel.admin,        name: SharingPermissions.Admin }],
     [AclUnset,    { level: aclLevel.unset,        name: SharingPermissions.Unset }],
diff --git a/src/fields/util.ts b/src/fields/util.ts
index c1e1a7111..b0ad4db5d 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -14,7 +14,6 @@ import {
     aclLevel,
     AclPrivate,
     AclReadonly,
-    AclSelfEdit,
     AclSym,
     DataSym,
     Doc,
@@ -87,7 +86,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
     const fromServer = target[UpdatingFromServer];
     const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
     const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAugment || effectiveAcl === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly;
-    const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAugment || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly();
+    const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAugment || effectiveAcl === AclAdmin ) && !DocServer.Control.isReadOnly();
 
     if (writeToDoc) {
         if (value === undefined) {
@@ -171,7 +170,6 @@ export enum SharingPermissions {
     Unset = 'None',
     Admin = 'Admin',
     Edit = 'Edit',
-    SelfEdit = 'Self-Edit',
     Augment = 'Augment',
     View = 'View',
     None = 'Not-Shared',
@@ -330,7 +328,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
     const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : getPropAcl(target, prop);
-    if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
+    if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin ) return true;
     // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
     if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
 
-- 
cgit v1.2.3-70-g09d2


From 3225f565acd8edaac5992f9766dc66ac5547257f Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Thu, 15 Jun 2023 11:18:19 -0400
Subject: more merge fixes

---
 src/client/views/DocumentDecorations.tsx           | 27 ++++++-------
 src/client/views/MarqueeAnnotator.tsx              |  5 ++-
 src/client/views/PropertiesView.tsx                | 24 +++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 25 ++++++------
 .../formattedText/ProsemirrorExampleTransfer.ts    |  2 +-
 src/fields/Doc.ts                                  |  2 +-
 src/fields/util.ts                                 | 47 ++++++++++++----------
 7 files changed, 69 insertions(+), 63 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 0cd92e2cf..9bc4cb48f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,38 +5,37 @@ import { IconButton } from 'browndash-components';
 import { action, computed, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
+import { Utils, aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { DateField } from '../../fields/DateField';
-import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc';
-import { Document } from '../../fields/documentSchemas';
+import { Doc, DocListCast, Field } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
-import { GetEffectiveAcl } from '../../fields/util';
-import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
-import { Docs } from '../documents/Documents';
+import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
+import { GetEffectiveAcl, normalizeEmail } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
 import { LinkFollower } from '../util/LinkFollower';
 import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentButtonBar } from './DocumentButtonBar';
 import './DocumentDecorations.scss';
-import { Colors } from './global/globalEnums';
-import { InkingStroke } from './InkingStroke';
 import { InkStrokeProperties } from './InkStrokeProperties';
+import { InkingStroke } from './InkingStroke';
 import { LightboxView } from './LightboxView';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { Colors } from './global/globalEnums';
 import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import React = require('react');
 import _ = require('lodash');
-import { DocumentManager } from '../util/DocumentManager';
-import { isUndefined } from 'lodash';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -805,7 +804,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             seldocview.props.hideDeleteButton ||
             seldocview.rootDoc.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
-                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DataSym]) : AclEdit;
+                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
                 return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
             });
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 11336d079..7edc1494b 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,6 +1,7 @@
 import { action, observable, ObservableMap, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData,  } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { NumCast } from '../../fields/Types';
@@ -207,7 +208,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     @action
     highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean, summarize?: boolean) => {
         // creates annotation documents for current highlights
-        const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
+        const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DocData]);
         const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
         addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
         return (annotationDoc as Doc) ?? undefined;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 25477f948..abbf9a22e 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -3,39 +3,39 @@ import { IconLookup } from '@fortawesome/fontawesome-svg-core';
 import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox, Tooltip } from '@material-ui/core';
-import { action, computed, Lambda, observable } from 'mobx';
+import { intersection } from 'lodash';
+import { Lambda, action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
-import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, StrListCast } from '../../fields/Doc';
-import { AclAdmin, DocAcl, DocData,  Height,  Width } from '../../fields/DocSymbols';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { Doc, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../fields/Doc';
+import { DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { List } from '../../fields/List';
 import { ComputedField } from '../../fields/ScriptField';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
-import { GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { SharingPermissions, normalizeEmail } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
+import { GroupManager } from '../util/GroupManager';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
 import { Transform } from '../util/Transform';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../util/UndoManager';
 import { EditableView } from './EditableView';
 import { FilterPanel } from './FilterPanel';
-import { Colors } from './global/globalEnums';
 import { InkStrokeProperties } from './InkStrokeProperties';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
 import { PropertiesButtons } from './PropertiesButtons';
 import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector';
 import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
 import './PropertiesView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
-import { GroupManager } from '../util/GroupManager';
-import { intersection } from 'lodash';
+import { Colors } from './global/globalEnums';
+import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
 const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f4cecb1dc..2f31c9c57 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,7 +12,8 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, StrListCast, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { Doc, DocListCast,StrListCast,  Field, Opt } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, Height, Width,ForceServerWrite,  UpdatingFromServer} from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -301,16 +302,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             this._editorView.updateState(state);
 
             const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
-            const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
-            const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField) : undefined; // the actual text in the text box
-            const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
-            const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
-            const json = JSON.stringify(state.toJSON());
+            const newText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+            const newJson = JSON.stringify(state.toJSON());
+            const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
+            const prevLayoutData = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
+            const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
             const effectiveAcl = GetEffectiveAcl(dataDoc);
 
             const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
 
-            if ([AclEdit, AclAdmin, AclAugment].includes(effectiveAcl)) {
+            if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
                 const accumTags = [] as string[];
                 state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
                     if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
@@ -1806,12 +1807,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             default:
                 if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
             case ' ':
-                if (e.code == 'Space') {
-                    break;
+                if (e.code !== 'Space') {
+                    [AclEdit, AclAugment, AclAdmin,].includes(GetEffectiveAcl(this.rootDoc)) &&
+                        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
                 }
-                [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
-                    this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
-        }
+                break;
+            }
         this.startUndoTypingBatch();
     };
     ondrop = (e: React.DragEvent) => {
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index c4a9b4a7e..ecd0bf8aa 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -4,7 +4,7 @@ import { Schema } from 'prosemirror-model';
 import { splitListItem, wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
 import { liftTarget } from 'prosemirror-transform';
-import { AclAdmin, AclAugment, AclEdit} from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit} from '../../../../fields/DocSymbols';
 import { GetEffectiveAcl } from '../../../../fields/util';
 import { Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 88587b3c1..555e768c1 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -145,7 +145,7 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol }>
 export function updateCachedAcls(doc: Doc) {
     if (!doc) return;
 
-    const target = (doc as any)?.__fields ?? doc;
+    const target = (doc as any)?.__fieldTuples ?? doc;
     const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
     Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl));
     if (Object.keys(permissions).length || doc[DocAcl]?.length) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 6058a5a5f..e0f87f805 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -8,27 +8,32 @@ import { UndoManager } from '../client/util/UndoManager';
 import { returnZero } from '../Utils';
 import CursorField from './CursorField';
 import {
-    AclAdmin,
-    AclEdit,
-    aclLevel,
-    AclPrivate,
-    AclSelfEdit,
-    AclSym,
-    DataSym,
     Doc,
+    aclLevel,
     DocListCast,
     DocListCastAsync,
-    ForceServerWrite,
-    HeightSym,
     HierarchyMapping,
-    Initializing,
-    LayoutSym,
     ReverseHierarchyMap,
     updateCachedAcls,
-    UpdatingFromServer,
-    WidthSym,
 } from './Doc';
-import { Id, OnUpdate, Parent, SelfProxy, ToValue, Update } from './FieldSymbols';
+import {
+    AclAdmin,
+    AclEdit,
+    AclPrivate,
+    AclAugment,
+    FieldKeys,
+    DocAcl,
+    DocData,
+    ForceServerWrite,
+    Height,
+    Initializing,
+    DocLayout,
+    UpdatingFromServer,
+    Width,
+    SelfProxy,
+    Update
+} from './DocSymbols';
+import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols';
 import { List } from './List';
 import { ObjectField } from './ObjectField';
 import { PrefetchProxy, ProxyField } from './Proxy';
@@ -150,12 +155,12 @@ export function denormalizeEmail(email: string) {
  */
 export function inheritParentAcls(parent: Doc, child: Doc) {
     return;
-    const dataDoc = parent[DataSym];
-    for (const key of Object.keys(dataDoc)) {
-        // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
-        const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
-        key.startsWith('acl') && distributeAcls(key, permission, child);
-    }
+    // const dataDoc = parent[DocData];
+    // for (const key of Object.keys(dataDoc)) {
+    //     // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
+    //     const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
+    //     key.startsWith('acl') && distributeAcls(key, permission, child);
+    // }
 }
 
 /**
@@ -214,7 +219,7 @@ export function SetCachedGroups(groups: string[]) {
     runInAction(() => cachedGroups.push(...groups));
 }
 function getEffectiveAcl(target: any, user?: string): symbol {
-    const targetAcls = target[AclSym];
+    const targetAcls = target[DocAcl];
     if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin;
 
     const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
-- 
cgit v1.2.3-70-g09d2


From 3899a3cb58965eb71582c9cb5cdc31d27ec91e72 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 15 Jun 2023 12:54:03 -0400
Subject: added drag lock back into document decorations.

---
 src/client/views/DocumentDecorations.tsx                  |  7 +++++++
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 ++++++++------
 2 files changed, 15 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 744d04370..79e3bd64f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -855,6 +855,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         ) : (
             <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}>
                 <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${hideTitle ? '' : this.selectionTitle}`}</span>
+                {!useLock ? null : (
+                    <Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
+                        <div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown} onContextMenu={e => e.preventDefault()}>
+                            <FontAwesomeIcon size="sm" icon="lock" />
+                        </div>
+                    </Tooltip>
+                )}
             </div>
         );
         const sharingMenu = docShareMode ? (
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2f31c9c57..8a0ac2da3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,8 +12,8 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast,StrListCast,  Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, Height, Width,ForceServerWrite,  UpdatingFromServer} from '../../../../fields/DocSymbols';
+import { Doc, DocListCast, StrListCast, Field, Opt } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, Height, Width, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -1788,7 +1788,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         e.stopPropagation();
         for (var i = state.selection.from; i <= state.selection.to; i++) {
             const node = state.doc.resolve(i);
-            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) {
+            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
                 e.preventDefault();
             }
         }
@@ -1808,11 +1808,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
             case ' ':
                 if (e.code !== 'Space') {
-                    [AclEdit, AclAugment, AclAdmin,].includes(GetEffectiveAcl(this.rootDoc)) &&
-                        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+                    [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
+                        this._editorView!.dispatch(
+                            this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
+                        );
                 }
                 break;
-            }
+        }
         this.startUndoTypingBatch();
     };
     ondrop = (e: React.DragEvent) => {
-- 
cgit v1.2.3-70-g09d2


From 5a9af979cc293d0e3843270ee053c24bf0eb6ef5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Jul 2023 16:15:27 -0400
Subject: changed acl inheritance for docking views.

---
 src/client/documents/Documents.ts                  | 10 ++--
 src/client/util/LinkFollower.ts                    |  2 +-
 src/client/util/SharingManager.tsx                 | 70 ++++++++++------------
 src/client/views/DocComponent.tsx                  | 30 +++++-----
 src/client/views/DocumentDecorations.tsx           | 40 ++++++++-----
 .../views/collections/CollectionDockingView.tsx    | 12 ++--
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/TabDocView.tsx        |  2 +-
 src/client/views/collections/TreeView.tsx          |  8 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/ComparisonBox.tsx           |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    | 10 ++--
 src/fields/Doc.ts                                  | 11 +++-
 src/fields/util.ts                                 | 33 +++++-----
 15 files changed, 123 insertions(+), 113 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 06ff95df0..18e8b5940 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -859,7 +859,7 @@ export namespace Docs {
 
             // users placeholderDoc as proto if it exists
             const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true);
-            
+
             if (placeholderDoc) {
                 dataDoc.proto = proto;
             }
@@ -1053,7 +1053,7 @@ export namespace Docs {
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _type_collection: CollectionViewType.Freeform }, id);
-            documents.forEach(d => (d.embedContainer = inst));
+            documents.forEach(d => Doc.SetContainer(d, inst));
             return inst;
         }
 
@@ -1158,7 +1158,9 @@ export namespace Docs {
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, dockingConfig: config }, id);
+            const ret = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, dockingConfig: config }, id);
+            documents.map(c => Doc.SetContainer(c, ret));
+            return ret;
         }
 
         export function DirectoryImportDocument(options: DocumentOptions = {}) {
@@ -1186,7 +1188,7 @@ export namespace Docs {
                 options,
                 id
             );
-            configs.map(c => (c.doc.embedContainer = doc));
+            configs.map(c => Doc.SetContainer(c.doc, doc));
             return doc;
         }
 
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index f74409e42..3e526c4c0 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -103,7 +103,7 @@ export class LinkFollower {
                         Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target);
                         movedTarget = true;
                     }
-                    target.embedContainer = sourceDocParent;
+                    Doc.SetContainer(target, sourceDocParent);
                     const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
                     if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
                         target.x = moveTo[0];
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 534a9cfde..64b2bb5b8 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,12 +1,11 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IconButton, Size } from 'browndash-components';
 import { concat, intersection } from 'lodash';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Select from 'react-select';
 import * as RequestPromise from 'request-promise';
-import { Doc, DocCastAsync, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
 import { AclAdmin, AclPrivate, AclUnset, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
@@ -24,7 +23,6 @@ import { GroupManager, UserOptions } from './GroupManager';
 import { GroupMemberView } from './GroupMemberView';
 import { SelectionManager } from './SelectionManager';
 import './SharingManager.scss';
-import { Docs } from '../documents/Documents';
 
 export interface User {
     email: string;
@@ -166,7 +164,7 @@ export class SharingManager extends React.Component<{}> {
         // or if the 'Override Nested' checkbox is selected
         var childDocs = DocListCast(target.data);
         childDocs.map(doc => {
-            if (this.overrideNested || doc[acl]==undefined){
+            if (this.overrideNested || doc[acl] == undefined) {
                 this.setInternalSharing(recipient, permission, doc);
             }
         });
@@ -207,7 +205,7 @@ export class SharingManager extends React.Component<{}> {
         // or if the 'Override Private' checkbox is selected
         var childDocs = DocListCast(target.data);
         childDocs.map(doc => {
-            if (this.overrideNested || doc[acl]==undefined){
+            if (this.overrideNested || doc[acl] == undefined) {
                 this.setInternalGroupSharing(group, permission, doc);
             }
         });
@@ -288,7 +286,7 @@ export class SharingManager extends React.Component<{}> {
                     this.shareFromPropertiesSidebar(shareWith, permission, DocListCast(doc.data), layout);
                 }
                 if (GetEffectiveAcl(doc) === AclAdmin) {
-                    if ( shareWith == 'Public' && layout) shareWith = 'Public-layout';
+                    if (shareWith == 'Public' && layout) shareWith = 'Public-layout';
                     distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
                 }
                 this.setDashboardBackground(doc, permission as SharingPermissions);
@@ -538,13 +536,12 @@ export class SharingManager extends React.Component<{}> {
                 const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
                 // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
                 let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(Doc.GetProto(targetDoc)[userKey])) : StrCast(targetDoc[userKey]);
-                if (this.layoutDocAcls){
+                if (this.layoutDocAcls) {
                     if (targetDoc[DocAcl][userKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name;
-                    else if (targetDoc['embedContainer'])  permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]);
+                    else if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]);
                     else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[userKey]) : '-multiple-';
-                }
-                else permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
-                
+                } else permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+
                 return !permissions ? null : (
                     <div key={userKey} className={'container'}>
                         <span className={'padding'}>{user.email}</span>
@@ -570,12 +567,11 @@ export class SharingManager extends React.Component<{}> {
         // the owner of the doc and the current user are placed at the top of the user list.
         const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
         var curUserPermission;
-            if (this.layoutDocAcls){
-                if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name;
-                else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]);
-                else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]);
-            }
-            else curUserPermission = StrCast(targetDoc[userKey]);
+        if (this.layoutDocAcls) {
+            if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name;
+            else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]);
+            else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]);
+        } else curUserPermission = StrCast(targetDoc[userKey]);
         // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
         userListContents.unshift(
             sameAuthor ? (
@@ -609,16 +605,15 @@ export class SharingManager extends React.Component<{}> {
                 .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey]));
             // const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
             let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][groupKey] ? HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name : StrCast(Doc.GetProto(targetDoc)[groupKey])) : StrCast(targetDoc[groupKey]);
-                if (this.layoutDocAcls){
-                    if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout';
-                    if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name;
-                    else{
-                        if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public';
-                        if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]);
-                        else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-';
-                    }
+            if (this.layoutDocAcls) {
+                if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout';
+                if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name;
+                else {
+                    if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public';
+                    if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]);
+                    else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-';
                 }
-                else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
+            } else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
 
             return !permissions ? null : (
                 <div key={groupKey} className={'container'}>
@@ -629,7 +624,7 @@ export class SharingManager extends React.Component<{}> {
                             <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
                         </div>
                     ) : null}
-                    <div className={"edit-actions"}>
+                    <div className={'edit-actions'}>
                         {admin || this.myDocAcls ? (
                             <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
                                 {this.sharingOptions(uniform, group.title === 'Override')}
@@ -649,16 +644,15 @@ export class SharingManager extends React.Component<{}> {
                 {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
                 <div className="sharing-contents">
                     <p className="share-title">
-                        <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} >
-                            <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')}/>
+                        <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')}>
+                            <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} />
                         </div>
                         <b>Share </b>
                         {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
                     </p>
                     <button className="share-copy-link" onClick={this.copyURL}>
-                        <FontAwesomeIcon title={"Copy Public URL"} icon={'copy'} size={'sm'} onClick={this.copyURL}/>
-                        &nbsp;
-                        Copy Public URL
+                        <FontAwesomeIcon title={'Copy Public URL'} icon={'copy'} size={'sm'} onClick={this.copyURL} />
+                        &nbsp; Copy Public URL
                     </button>
                     <div className={'close-button'} onClick={this.close}>
                         <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
@@ -682,7 +676,7 @@ export class SharingManager extends React.Component<{}> {
                                         }),
                                     }}
                                 />
-                                <div className='permissions-select'>
+                                <div className="permissions-select">
                                     <select className={`permissions-dropdown-${this.permissions}`} onChange={this.handlePermissionsChange} value={this.permissions}>
                                         {this.sharingOptions(true)}
                                     </select>
@@ -706,9 +700,9 @@ export class SharingManager extends React.Component<{}> {
                             </div>
                         </div>
                     ) : (
-                        <div className='share-container'>
-                            <div className='acl-container'>
-                                <div className='layoutDoc-acls'>
+                        <div className="share-container">
+                            <div className="acl-container">
+                                <div className="layoutDoc-acls">
                                     <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
                                 </div>
                             </div>
@@ -717,7 +711,7 @@ export class SharingManager extends React.Component<{}> {
                     <div className="main-container">
                         <div className={'individual-container'}>
                             <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
-                                <div className='title-individual'>
+                                <div className="title-individual">
                                     Individuals &nbsp;
                                     {this.individualSort === 'ascending' ? (
                                         <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
@@ -732,7 +726,7 @@ export class SharingManager extends React.Component<{}> {
                         </div>
                         <div className={'group-container'}>
                             <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
-                                <div className='title-group'>
+                                <div className="title-group">
                                     Groups &nbsp;
                                     <div className="group-info" onClick={action(() => GroupManager.Instance?.open())}>
                                         <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index f3aa8451a..1d0feec74 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -196,52 +196,52 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
                 aclKeys.forEach(key =>
                     added.forEach(d => {
-                        if (key != 'acl-Me'){
-                            const permissionString = StrCast(Doc.GetProto(this.props.Document)[key])
-                            const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl
-                            const permission = HierarchyMapping.get(permissionSymbol)!.name
-                            distributeAcls(key, permission, Doc.GetProto(d))
+                        if (key != 'acl-Me') {
+                            const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]);
+                            const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl;
+                            const permission = HierarchyMapping.get(permissionSymbol)!.name;
+                            distributeAcls(key, permission, Doc.GetProto(d));
                         }
                     })
                 );
 
                 if (effectiveAcl === AclAugment) {
                     added.map(doc => {
-                        doc.embedContainer = this.props.Document;
+                        Doc.SetContainer(doc, this.props.Document);
                         if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
                         Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
                         const parent = DocCast(doc.embedContainer);
                         doc.embedContainer && inheritParentAcls(parent, doc);
                         for (const key of Object.keys(parent)) {
-                            const symbol = ReverseHierarchyMap.get(StrCast(parent[key]))
-                            if (symbol && key.startsWith('acl')){
+                            const symbol = ReverseHierarchyMap.get(StrCast(parent[key]));
+                            if (symbol && key.startsWith('acl')) {
                                 const sharePermission = HierarchyMapping.get(symbol.acl!)!.name;
                                 const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0];
                                 if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc);
                             }
                         }
                     });
-                    } else {
-                        added
+                } else {
+                    added
                         .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)))
                         .map(doc => {
                             // only make a pushpin if we have acl's to edit the document
                             //DocUtils.LeavePushpin(doc);
                             doc._dragOnlyWithinContainer = undefined;
-                            doc.embedContainer = this.props.Document;
+                            Doc.SetContainer(doc, this.props.Document);
                             if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
                             const parent = DocCast(doc.embedContainer);
                             doc.embedContainer && inheritParentAcls(parent, doc);
                             for (const key of Object.keys(Doc.GetProto(parent))) {
-                                const symbol = ReverseHierarchyMap.get(StrCast(parent[key]))
-                                if (symbol && key.startsWith('acl')){
+                                const symbol = ReverseHierarchyMap.get(StrCast(parent[key]));
+                                if (symbol && key.startsWith('acl')) {
                                     const sharePermission = HierarchyMapping.get(symbol.acl!)!.name;
                                     const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0];
                                     if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc);
                                 }
                             }
-                    });
-                    
+                        });
+
                     const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
                     if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
                     else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4454a3ec1..cdd9e62d8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -201,9 +201,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
         const first = SelectionManager.Views()[0];
         const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc);
-        if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment){
+        if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
             return false;
-        }      
+        }
         const dragDocView = SelectionManager.Views()[0];
         const containers = new Set<Doc | undefined>();
         SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer)));
@@ -766,13 +766,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
         // sharing
         const acl = this.showLayoutAcl ? GetEffectiveLayoutAcl(seldocview.rootDoc) : GetEffectiveAcl(seldocview.rootDoc);
-        const docShareMode = HierarchyMapping.get(acl)!.name
+        const docShareMode = HierarchyMapping.get(acl)!.name;
         const shareMode = StrCast(docShareMode);
         var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
 
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
         const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
-        const hideResizers = ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
+        const hideResizers =
+            ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
         const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating;
         const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
         // if multiple documents have been opened at the same time, then don't show open button
@@ -826,26 +827,26 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const radiusHandleLocation = Math.min(radiusHandle, maxDist);
 
         const sharingMenu = docShareMode ? (
-            <div className='documentDecorations-share' >
+            <div className="documentDecorations-share">
                 <div className={`documentDecorations-share${shareMode}`}>
                     &nbsp;
                     {shareSymbolIcon + ' ' + shareMode}
                     &nbsp;
-                    {!Doc.noviceMode ? 
-                        <div className='checkbox'>
-                            <div className='checkbox-box'>
-                                <input type="checkbox" checked={this.showLayoutAcl} onChange={action(() => (this.showLayoutAcl = !this.showLayoutAcl))} /> 
+                    {!Doc.noviceMode ? (
+                        <div className="checkbox">
+                            <div className="checkbox-box">
+                                <input type="checkbox" checked={this.showLayoutAcl} onChange={action(() => (this.showLayoutAcl = !this.showLayoutAcl))} />
                             </div>
-                            <div className='checkbox-text'> Layout </div>
+                            <div className="checkbox-text"> Layout </div>
                         </div>
-                    : null }
+                    ) : null}
                     &nbsp;
                 </div>
             </div>
         ) : (
             <div />
         );
-        
+
         const titleArea = this._editingTitle ? (
             <input
                 ref={this._keyinput}
@@ -860,8 +861,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 onPointerDown={e => e.stopPropagation()}
             />
         ) : (
-            <div className="documentDecorations-title" key="title" onPointerDown={e => {e.stopPropagation}}>
-                { hideTitle ? null : <span className={`documentDecorations-titleSpan${colorScheme}`} onPointerDown={this.onTitleDown}>{this.selectionTitle}</span>}
+            <div
+                className="documentDecorations-title"
+                key="title"
+                onPointerDown={e => {
+                    e.stopPropagation;
+                }}>
+                {hideTitle ? null : (
+                    <span className={`documentDecorations-titleSpan${colorScheme}`} onPointerDown={this.onTitleDown}>
+                        {this.selectionTitle}
+                    </span>
+                )}
                 {sharingMenu}
                 {!useLock ? null : (
                     <Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
@@ -872,7 +882,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 )}
             </div>
         );
-        
+
         return (
             <div className={`documentDecorations${colorScheme}`} style={{ opacity: this._showNothing ? 0.1 : undefined }}>
                 <div
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 32fb4d8df..ce9eb9f17 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -461,7 +461,7 @@ export class CollectionDockingView extends CollectionSubView() {
             const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc));
             if (newtabdocs.length) {
                 Doc.GetProto(newtab).data = new List<Doc>(newtabdocs);
-                newtabdocs.forEach(ntab => (ntab.embedContainer = newtab));
+                newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab));
             }
             json = json.replace(origtab[Id], newtab[Id]);
             return newtab;
@@ -497,11 +497,11 @@ export class CollectionDockingView extends CollectionSubView() {
     tabCreated = (tab: any) => {
         const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {});
         aclKeys.forEach(key => {
-            if (key != 'acl-Me'){
-                const permissionString = StrCast(Doc.GetProto(this.props.Document)[key])
-                const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl
-                const permission = HierarchyMapping.get(permissionSymbol)!.name
-                distributeAcls(key, permission, Doc.GetProto(tab))
+            if (key != 'acl-Me') {
+                const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]);
+                const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl;
+                const permission = HierarchyMapping.get(permissionSymbol)!.name;
+                distributeAcls(key, permission, Doc.GetProto(tab));
             }
         });
         this.tabMap.add(tab);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4cd3885f5..d78a0e781 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -180,7 +180,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const doAddDoc = (doc: Doc | Doc[]) =>
             (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
                 const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before);
-                res && (doc.embedContainer = this.props.Document);
+                res && Doc.SetContainer(doc, this.props.Document);
                 return res;
             }, true);
         if (this.doc.resolvedDataDoc instanceof Promise) return false;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4d780f46b..3473eee18 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -262,7 +262,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
             pinDoc.presDuration = pinDoc.presDuration ?? 1000;
             pinDoc.groupWithUp = false;
-            pinDoc.embedContainer = curPres;
+            Doc.SetContainer(pinDoc, curPres);
             // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements.  That way the PresBox could configure all of its children at render time
             pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
             pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 8d8d895c6..a2269075d 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -392,7 +392,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             const innerAdd = (doc: Doc) => {
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
                 const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
-                dataIsComputed && (doc.embedContainer = this.doc.embedContainer);
+                dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer);
                 return added;
             };
             return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
@@ -455,7 +455,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     const innerAdd = (doc: Doc) => {
                         const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                         const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
-                        dataIsComputed && (doc.embedContainer = this.doc.embedContainer);
+                        dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer);
                         return added;
                     };
                     return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
@@ -563,7 +563,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 }
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                 const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
-                !dataIsComputed && added && (doc.embedContainer = this.doc.embedContainer);
+                !dataIsComputed && added && Doc.SetContainer(doc, this.doc.embedContainer);
 
                 return added;
             };
@@ -1192,7 +1192,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
                         Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
                         newParent.treeViewOpen = true;
-                        child.embedContainer = treeView.Document;
+                        Doc.SetContainer(child, treeView.Document);
                     }
                 };
                 const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 9b0abc48b..764b1e08a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -392,7 +392,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         newCollection.layout_fitWidth = true;
         newCollection['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
         selected.forEach(d => {
-            d.embedContainer = newCollection;
+            Doc.SetContainer(d, newCollection);
             d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
         });
         this.hideMarquee();
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 2290e0711..ca5ec9389 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -46,7 +46,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (dropEvent.complete.docDragData) {
             const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
             const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
-            droppedDocs.lastElement().embedContainer = this.dataDoc;
+            Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc);
             !added && e.preventDefault();
             e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
             return added;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 202a9f851..e5ea8e0c1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -564,7 +564,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     }
                     if (added) {
                         draggedDoc._freeform_fitContentsToBox = true;
-                        draggedDoc.embedContainer = this.rootDoc;
+                        Doc.SetContainer(draggedDoc, this.rootDoc);
                         const view = this._editorView!;
                         view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
                     }
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index d2273c91c..112a0d87e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -4,7 +4,7 @@ import { Schema } from 'prosemirror-model';
 import { splitListItem, wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
 import { liftTarget } from 'prosemirror-transform';
-import { AclAdmin, AclAugment, AclEdit} from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
 import { GetEffectiveAcl } from '../../../../fields/util';
 import { Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
@@ -50,8 +50,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         switch (GetEffectiveAcl(props.DataDoc)) {
             case AclAugment:
                 const prevNode = state.selection.$cursor.nodeBefore;
-                const prevUser = prevNode.marks[prevNode.marks.length-1].attrs.userid
-                if (prevUser != Doc.CurrentUserEmail){
+                const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
+                if (prevUser != Doc.CurrentUserEmail) {
                     return false;
                 }
         }
@@ -263,7 +263,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         if (props.onKey?.(event, props)) return true;
         if (!canEdit(state)) return true;
-        
+
         if (
             !deleteSelection(state, (tx: Transaction) => {
                 dispatch(updateBullets(tx, schema));
@@ -334,7 +334,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
 
     //Command to create a blank space
     bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        if (GetEffectiveAcl(props.DataDoc)!=AclEdit && GetEffectiveAcl(props.DataDoc)!=AclAugment && GetEffectiveAcl(props.DataDoc)!=AclAdmin) return true;
+        if (GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         dispatch(splitMetadata(marks, state.tr));
         return false;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 99712fb04..9c138d348 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -20,7 +20,6 @@ import {
     AclEdit,
     AclPrivate,
     AclReadonly,
-    AclSelfEdit,
     AclUnset,
     Animation,
     CachedUpdates,
@@ -138,7 +137,7 @@ export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermis
     [AclAdmin,    { level: aclLevel.admin,        name: SharingPermissions.Admin,   image: '⬢' }],
     [AclUnset,    { level: aclLevel.unset,        name: SharingPermissions.Unset,   image: '▲' }],
 ]);
-export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol ; image: string}> = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }]));
+export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol; image: string }> = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }]));
 
 // caches the document access permissions for the current user.
 // this recursively updates all protos as well.
@@ -478,6 +477,14 @@ export namespace Doc {
     //     });
     // }
 
+    export function SetContainer(doc: Doc, container: Doc) {
+        doc.embedContainer = container;
+        if (Doc.GetProto(container).author === doc.author) {
+            Object.keys(Doc.GetProto(container))
+                .filter(key => key.startsWith('acl'))
+                .forEach(key => (doc[key] = Doc.GetProto(container)[key]));
+        }
+    }
     export function RunCachedUpdate(doc: Doc, field: string) {
         const update = doc[CachedUpdates][field];
         if (update) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 0f613d926..48de36efe 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -131,10 +131,10 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
     for (const key of Object.keys(parent)) {
         // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
         // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key];
-        const symbol = ReverseHierarchyMap.get(StrCast(parent[key]))
-        if (symbol){
+        const symbol = ReverseHierarchyMap.get(StrCast(parent[key]));
+        if (symbol) {
             const sharePermission = HierarchyMapping.get(symbol.acl!)!.name;
-            key.startsWith('acl') && distributeAcls(key, sharePermission, child)
+            key.startsWith('acl') && distributeAcls(key, sharePermission, child);
         }
     }
 }
@@ -171,7 +171,7 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
 // return layout acl from cache or chache the acl and return.
 const getEffectiveLayoutAclCache = computedFn(function (target: any, user?: string) {
     return getEffectiveLayoutAcl(target, user);
-    }, true);
+}, true);
 
 /**
  * Calculates the effective access right to a document for the current user.
@@ -183,13 +183,13 @@ export function GetEffectiveAcl(target: any, user?: string): symbol {
 }
 
 /**
-* Calculates the effective access layout right to a document for the current user. By getting the container's effective acl if the layout acl isn't set.
-*/
+ * Calculates the effective access layout right to a document for the current user. By getting the container's effective acl if the layout acl isn't set.
+ */
 export function GetEffectiveLayoutAcl(target: any, user?: string): symbol {
     if (!target) return AclPrivate;
     if (target[UpdatingFromServer]) return AclAdmin;
     return getEffectiveLayoutAclCache(target, user);
-    }
+}
 
 function getPropAcl(target: any, prop: string | symbol | number) {
     if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
@@ -234,9 +234,9 @@ function getEffectiveAcl(target: any, user?: string): symbol {
 }
 
 /**
-* Returns the layout acl that is effective on the document passed through as the target. If no layout acls
-* have been set, it returns the regular acls for the document target is contained in.
-*/
+ * Returns the layout acl that is effective on the document passed through as the target. If no layout acls
+ * have been set, it returns the regular acls for the document target is contained in.
+ */
 function getEffectiveLayoutAcl(target: any, user?: string): symbol {
     const targetAcls = target[DocAcl];
 
@@ -248,27 +248,24 @@ function getEffectiveLayoutAcl(target: any, user?: string): symbol {
             if ((GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') && entity != 'Public') {
                 if (effectiveAcl && HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) {
                     effectiveAcl = value as symbol;
-                }
-                else{
+                } else {
                     effectiveAcl = value as symbol;
                 }
             }
         }
 
-        if (effectiveAcl){
+        if (effectiveAcl) {
             return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
-        }
-        else{
+        } else {
             return GetEffectiveAcl(Doc.GetProto(target['embedContainer']), user);
         }
     }
     // authored documents are private until an ACL is set.
     const targetAuthor = target.__fieldTuples?.author || target.author; // target may be a Doc of Proxy, so check __fieldTuples.author and .author
     if (targetAuthor && targetAuthor !== userChecked) return AclPrivate;
-        return AclAdmin;
+    return AclAdmin;
 }
 
-
 /**
  * Recursively distributes the access right for a user across the children of a document and its annotations.
  * @param key the key storing the access right (e.g. acl-groupname)
@@ -286,7 +283,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
             //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???)
             updateCachedAcls(target);
         }
-        return;
+        //return;
     }
     visited.push(target);
 
-- 
cgit v1.2.3-70-g09d2


From 51d464e67b22939e7fd1422a7175ec9b58f390a7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 13 Jul 2023 19:44:16 -0400
Subject: getting shared links to show up when they are made remotely.  fixing
 text to not generate network events when it's not selected

---
 src/client/util/LinkManager.ts                     | 65 +++++++++++-----------
 src/client/util/SharingManager.tsx                 |  3 +-
 src/client/util/UndoManager.ts                     |  9 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 48 ++++++++--------
 src/fields/Doc.ts                                  |  2 +-
 5 files changed, 71 insertions(+), 56 deletions(-)

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

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ce422f849..c7f092565 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -46,38 +46,41 @@ export class LinkManager {
         LinkManager._instance = this;
         this.createlink_relationshipLists();
         LinkManager.userLinkDBs = [];
-        const addLinkToDoc = (link: Doc) => {
-            Promise.all([link]).then(linkdoc => {
-                const link = DocCast(linkdoc[0]);
-                Promise.all([link.proto]).then(linkproto => {
-                    Promise.all([link.link_anchor_1, link.link_anchor_2]).then(linkdata => {
-                        const a1 = DocCast(linkdata[0]);
-                        const a2 = DocCast(linkdata[1]);
-                        a1 &&
-                            a2 &&
-                            Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then(
-                                action(protos => {
-                                    (protos[0] as Doc)?.[DirectLinks].add(link);
-                                    (protos[1] as Doc)?.[DirectLinks].add(link);
+        // since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
+        // Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
+        // Then add the link to the anchor protos.
+        const addLinkToDoc = (lprom: Doc) =>
+            PromiseValue(lprom).then((link: Opt<Doc>) =>
+                PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) =>
+                    Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
+                        Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
+                            Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
+                                action(lAnchProtoProtos => {
+                                    link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
+                                    link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
                                 })
-                            );
-                    });
-                });
-            });
-        };
-        const remLinkFromDoc = (link: Doc) => {
-            const a1 = link?.link_anchor_1;
-            const a2 = link?.link_anchor_2;
-            Promise.all([a1, a2]).then(
-                action(() => {
-                    if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
-                        Doc.GetProto(a1)[DirectLinks].delete(link);
-                        Doc.GetProto(a2)[DirectLinks].delete(link);
-                        Doc.GetProto(link)[DirectLinks].delete(link);
-                    }
-                })
+                            )
+                        )
+                    )
+                )
             );
-        };
+
+        const remLinkFromDoc = (lprom: Doc) =>
+            PromiseValue(lprom).then((link: Opt<Doc>) =>
+                PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) =>
+                    Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
+                        Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
+                            Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
+                                action(lAnchProtoProtos => {
+                                    link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].delete(link);
+                                    link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].delete(link);
+                                })
+                            )
+                        )
+                    )
+                )
+            );
+
         const watchUserLinkDB = (userLinkDBDoc: Doc) => {
             LinkManager._links.push(...DocListCast(userLinkDBDoc.data));
             const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts.  data structure is not a simple list of Docs, but a list of ProxyField/Fields
@@ -156,7 +159,7 @@ export class LinkManager {
         return this.relatedLinker(anchor);
     } // finds all links that contain the given anchor
     public getAllDirectLinks(anchor: Doc): Doc[] {
-        return Array.from(Doc.GetProto(anchor)[DirectLinks] ?? []);
+        return Array.from(Doc.GetProto(anchor)[DirectLinks]);
     } // finds all links that contain the given anchor
 
     relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 32b0e729c..807e812f2 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -21,6 +21,7 @@ import { SearchBox } from '../views/search/SearchBox';
 import { DocumentManager } from './DocumentManager';
 import { GroupManager, UserOptions } from './GroupManager';
 import { GroupMemberView } from './GroupMemberView';
+import { LinkManager } from './LinkManager';
 import { SelectionManager } from './SelectionManager';
 import './SharingManager.scss';
 import { undoable } from './UndoManager';
@@ -142,7 +143,7 @@ export class SharingManager extends React.Component<{}> {
                     if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
                         if (!this.users.find(user => user.user.email === newUser.email)) {
                             this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
-                            // LinkManager.addLinkDB(sharer.linkDatabase);
+                            LinkManager.addLinkDB(linkDatabase);
                         }
                     }
                 })
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index b59af6656..9a6719ea5 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,5 +1,6 @@
 import { observable, action, runInAction } from 'mobx';
 import { Field } from '../../fields/Doc';
+import { RichTextField } from '../../fields/RichTextField';
 import { Without } from '../../Utils';
 
 function getBatchName(target: any, key: string | symbol): string {
@@ -96,7 +97,13 @@ export namespace UndoManager {
 
     export function AddEvent(event: UndoEvent, value?: any): void {
         if (currentBatch && batchCounter.get() && !undoing) {
-            console.log('                         '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + (value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value)));
+            console.log(
+                '                         '.slice(0, batchCounter.get()) +
+                    'UndoEvent : ' +
+                    event.prop +
+                    ' = ' +
+                    (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value))
+            );
             currentBatch.push(event);
             tempEvents?.push(event);
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e5ea8e0c1..a0eb328a1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -34,7 +34,6 @@ import { DictationManager } from '../../../util/DictationManager';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { MakeTemplate } from '../../../util/DropConverter';
-import { IsFollowLinkScript } from '../../../util/LinkFollower';
 import { LinkManager } from '../../../util/LinkManager';
 import { RTFMarkup } from '../../../util/RTFMarkup';
 import { SelectionManager } from '../../../util/SelectionManager';
@@ -305,11 +304,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const newText = state.doc.textBetween(0, state.doc.content.size, ' \n');
             const newJson = JSON.stringify(state.toJSON());
             const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
-            const prevLayoutData = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
+            const templateData = this.rootDoc !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
             const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
             const effectiveAcl = GetEffectiveAcl(dataDoc);
 
-            const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
+            const removeSelection = (json: string | undefined) => json?.replace(/"selection":.*/, '');
 
             if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
                 const accumTags = [] as string[];
@@ -325,12 +324,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     this._applyingChange = this.fieldKey;
                     const textChange = newText !== prevData?.Text;
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
-                    if ((!prevData && !protoData) || newText || (!newText && !protoData)) {
+                    if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if (removeSelection(newJson) !== removeSelection(prevLayoutData?.Data)) {
+                        if (this.props.isContentActive() && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
                             dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
-                            dataDoc[this.fieldKey + '_noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                            dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
                             textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText });
                             unchanged = false;
                         }
@@ -415,6 +414,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
             tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
+            this.prepareForTyping();
         }
         oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
     };
@@ -1230,6 +1230,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._disposers.selected = reaction(
             () => this.props.isSelected(),
             action(selected => {
+                selected && this.prepareForTyping();
                 if (FormattedTextBox._globalHighlights.has('Bold Text')) {
                     this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
                 }
@@ -1524,24 +1525,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             }
         }
         selectOnLoad && this._editorView!.focus();
-        // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
+        if (this.props.isContentActive()) this.prepareForTyping();
         if (this._editorView) {
             const tr = this._editorView.state.tr;
             const { from, to } = tr.selection;
             // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated.
             if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
-            this._editorView.dispatch(
-                this._editorView.state.tr.setStoredMarks([
-                    ...(this._editorView.state.storedMarks ?? []),
-                    ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
-                    ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
-                    ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
-                    ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
-                    ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
-                    ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
-                    ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
-                ])
-            );
+
             if (FormattedTextBox.PasteOnLoad) {
                 const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
                 FormattedTextBox.PasteOnLoad = undefined;
@@ -1551,6 +1541,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         FormattedTextBox.DontSelectInitialText = false;
     }
 
+    // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
+    prepareForTyping = () => {
+        this._editorView?.dispatch(
+            this._editorView?.state.tr.setStoredMarks([
+                ...(this._editorView.state.storedMarks?.filter(mark => ![schema.marks.em, schema.marks.underline, schema.marks.pFontFamily, schema.marks.pFontSize, schema.marks.strong, schema.marks.pFontColor].includes(mark.type)) ?? []),
+                ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
+                ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
+                ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
+                ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
+                ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
+                ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
+                ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
+            ])
+        );
+    };
+
     componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this.endUndoTypingBatch();
@@ -1831,9 +1837,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             case ' ':
                 if (e.code !== 'Space') {
                     [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
-                        this._editorView!.dispatch(
-                            this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
-                        );
+                        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
                 }
                 break;
         }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index a1aea1563..5a8a6e4b6 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -346,7 +346,7 @@ export class Doc extends RefField {
     /// all of the raw acl's that have been set on this document.  Use GetEffectiveAcl to determine the actual ACL of the doc for editing
     @observable public [DocAcl]: { [key: string]: symbol } = {};
     @observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
-    @observable public [DirectLinks]: Set<Doc> = new Set();
+    @observable public [DirectLinks] = new ObservableSet<Doc>();
     @observable public [Animation]: Opt<Doc>;
     @observable public [Highlight]: boolean = false;
     static __Anim(Doc: Doc) {
-- 
cgit v1.2.3-70-g09d2

