From 65448167a4db396b2ee3c56d870233f8ac182eb6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 11 Oct 2020 01:33:28 -0400
Subject: prevented documents that are unshared (ie private to soeone else)
 from showing up as an empty key value in stacking and other views. tried to
 fix an exception when showing links (that wasn't repeatable, so dion't know
 if fix works).

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

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index fa80c8062..493018093 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -134,7 +134,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
             }
 
-            const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
+            const docs = rawdocs.filter(d => !(d instanceof Promise) && Object.keys(d).length).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
 
-- 
cgit v1.2.3-70-g09d2


From 27635402ad810b910557eb1a86c7e85fa281aaee Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 12 Oct 2020 01:05:13 -0400
Subject: switched fonticonbox and colelctionsubview to test GetEffectiveAcl to
 determine if document is available (instead of hack of testing 'author'). 
 made GetEffectiveAcl a computedFn.  No longer create a pushpin when an
 annotation that's linked to text is dragged off a PDF.   fixed undo of
 pushpin navigation (used to call finish() twice).  fixed pushpin navigation
 to conistenly pan & sensibly toggle target

---
 src/client/util/DocumentManager.ts                  |  9 +++++----
 src/client/views/collections/CollectionSubView.tsx  |  5 +++--
 src/client/views/collections/CollectionView.tsx     |  3 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 21 +++++++++++++--------
 src/client/views/nodes/FontIconBox.tsx              |  5 +++--
 src/client/views/pdf/Annotation.tsx                 |  2 ++
 src/client/views/pdf/PDFViewer.tsx                  |  2 +-
 src/fields/util.ts                                  | 14 ++++++++++----
 8 files changed, 39 insertions(+), 22 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 178daf5f0..b37181e57 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -164,13 +164,14 @@ export class DocumentManager {
             const sameContext = annotatedDoc && annotatedDoc === originatingDoc?.context;
             if (originatingDoc?.isPushpin) {
                 const hide = !docView.props.Document.hidden;
-                (!hide || !sameContext) && (docView.props.Document.hidden = !docView.props.Document.hidden);
-                docView.props.focus(docView.props.Document, willZoom, undefined, (notfocused: boolean) => { // bcz: Argh! TODO: Need to add a notFocused argument to the after finish callback function that indicates whether the window had to scroll to show the target
-                    notfocused && hide && (docView.props.Document.hidden = true);
+                docView.props.focus(docView.props.Document, willZoom, undefined, (notfocused: boolean) => { // bcz: Argh! TODO: Need to add a notFocused argument to the after finish callback function that indicates whether the window had to scroll to show the target  
+                    if (notfocused || docView.props.Document.hidden) {
+                        docView.props.Document.hidden = !docView.props.Document.hidden;
+                    }
                     return focusAndFinish();
                     // @ts-ignore   bcz: Argh TODO: Need to add a parameter to focus() everywhere for whether focus should center the target's container in the view or not. // here we don't want to focus the container if the source and target are in the same container
                 }, sameContext);
-                finished?.();
+                //finished?.();
             }
             else {
                 docView.select(false);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 493018093..b282d1e27 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -103,7 +103,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             const { Document, DataDoc } = this.props;
             const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).
                 filter(pair => {  // filter out any documents that have a proto that we don't have permissions to (which we determine by not having any keys
-                    return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && Object.keys(pair.layout.proto).length));
+                    return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length));
                 });
             return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
@@ -134,7 +134,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
             }
 
-            const docs = rawdocs.filter(d => !(d instanceof Promise) && Object.keys(d).length).map(d => d as Doc);
+            const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(d) !== AclPrivate).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
 
@@ -502,4 +502,5 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { OverlayView } from "../OverlayView";
 import { setTimeout } from "timers";
 import { Hypothesis } from "../../util/HypothesisUtils";
+import { GetEffectiveAcl } from "../../../fields/util";
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 80e9b41ad..cfd24545b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -159,7 +159,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 else {
                     added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => {  // only make a pushpin if we have acl's to edit the document
                         const context = Cast(doc.context, Doc, null);
-                        if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
+                        const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
+                        if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
                             const pushpin = Docs.Create.FontIconDocument({
                                 title: "pushpin", label: "",
                                 icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4d9906f93..4df90e8ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -893,21 +893,24 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 afterFocus && setTimeout(afterFocus, delay);
             } else {
                 const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
-                const offset = annotOn && (contextHgt / 2);
                 const curScroll = NumCast(this.props.Document._scrollTop);
                 let scrollTo = curScroll;
                 if (curScroll + contextHgt < NumCast(doc.y)) {
-                    scrollTo = NumCast(doc.y) + Math.max(NumCast(doc._height), 50) - contextHgt;
+                    scrollTo = NumCast(doc.y) + Math.max(NumCast(doc._height), 100) - contextHgt;
                 } else if (curScroll > NumCast(doc.y)) {
-                    scrollTo = NumCast(doc.y);
+                    scrollTo = Math.max(0, NumCast(doc.y) - 50);
                 }
-                if (curScroll !== scrollTo) {
+                if (curScroll !== scrollTo || this.props.Document._viewTransition) {
                     this.props.Document._scrollPY = this.props.Document._scrollY = scrollTo;
                     delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0;
-                    !dontCenter && delay && this.props.focus(this.props.Document);
+                    !dontCenter && this.props.focus(this.props.Document);
                     afterFocus && setTimeout(afterFocus, delay);
+                } else {
+                    !dontCenter && delay && this.props.focus(this.props.Document);
                     // @ts-ignore
-                } else afterFocus(true);  // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll
+                    afterFocus(true);  // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll
+
+                }
             }
 
         } else {
@@ -929,14 +932,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             willZoom && this.setScaleToZoom(layoutdoc, scale);
             Doc.linkFollowHighlight(doc);
 
+            const notFocused = newPanX === savedState.px && newPanY === savedState.py;
             afterFocus && setTimeout(() => {
-                if (afterFocus?.()) {
+                // @ts-ignore
+                if (afterFocus?.(notFocused)) { // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll
                     this.Document._panX = savedState.px;
                     this.Document._panY = savedState.py;
                     this.Document[this.scaleFieldKey] = savedState.s;
                     this.Document._viewTransition = savedState.pt;
                 }
-            }, 500);
+            }, notFocused ? 0 : 500);
         }
 
     }
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 156256fe5..276c66bb1 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -8,11 +8,12 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { StrCast, Cast, ScriptCast } from '../../../fields/Types';
 import { Utils, setupMoveUpEvents, returnFalse, emptyFunction } from "../../../Utils";
 import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, AclPrivate } from '../../../fields/Doc';
 import { ContextMenu } from '../ContextMenu';
 import { ScriptField } from '../../../fields/ScriptField';
 import { Tooltip } from '@material-ui/core';
 import { DragManager } from '../../util/DragManager';
+import { GetEffectiveAcl } from '../../../fields/util';
 const FontIconSchema = createSchema({
     icon: "string",
 });
@@ -105,7 +106,7 @@ export class FontIconBadge extends React.Component<FontIconBadgeProps> {
 
     render() {
         if (!(this.props.collection instanceof Doc)) return (null);
-        const length = DocListCast(this.props.collection.data).filter(d => Object.keys(d).length).length; // filter out any documents that we can't read
+        const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; //  Object.keys(d).length).length; // filter out any documents that we can't read
         return <div className="fontIconBadge-container" style={{ width: 15, height: 15, top: 12 }} ref={this._notifsRef}>
             <div className="fontIconBadge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}
                 onPointerDown={this.onPointerDown} >
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index e7f901091..a071abd21 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -71,6 +71,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         this._reactionDisposer && this._reactionDisposer();
     }
 
+    @undoBatch
     deleteAnnotation = () => {
         const annotation = DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]);
         const group = FieldValue(Cast(this.props.document.group, Doc));
@@ -86,6 +87,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         PDFMenu.Instance.fadeOut(true);
     }
 
+    @undoBatch
     pinToPres = () => {
         const group = FieldValue(Cast(this.props.document.group, Doc));
         const isPinned = group && Doc.isDocPinned(group) ? true : false;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 78b95b385..d8be3defd 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -194,7 +194,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 if (scrollY !== undefined) {
                     (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
                     if (this.props.renderDepth === -1 && scrollY >= 0) {
-                        if (!this._mainCont.current) setTimeout(() => smoothScroll(1000, this._mainCont.current!, scrollY || 0));
+                        if (!this._mainCont.current) setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, scrollY || 0));
                         else smoothScroll(1000, this._mainCont.current, scrollY || 0);
                         this.Document._scrollPY = undefined;
                     }
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 881f301f3..b68d961b1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -13,6 +13,7 @@ import { returnZero } from "../Utils";
 import CursorField from "./CursorField";
 import { List } from "./List";
 import { SnappingManager } from "../client/util/SnappingManager";
+import { computedFn } from "mobx-utils";
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
@@ -139,7 +140,7 @@ export function denormalizeEmail(email: string) {
  * 
  * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
  * 
- * Add: a user with add access to a document can add documents/annotations to that document but cannot edit or delete anything.
+ * Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything.
  * 
  * View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
  * 
@@ -148,7 +149,7 @@ export function denormalizeEmail(email: string) {
 export enum SharingPermissions {
     Admin = "Admin",
     Edit = "Can Edit",
-    Add = "Can Add",
+    Add = "Can Augment",
     View = "Can View",
     None = "Not Shared"
 }
@@ -157,6 +158,11 @@ export enum SharingPermissions {
  * Calculates the effective access right to a document for the current user.
  */
 export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol {
+    return computedFn(function (target: any, in_prop?: string | symbol | number, user?: string) {
+        return getEffectiveAcl(target, in_prop, user);
+    }, true)(target, in_prop, user);
+}
+function getEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol {
     if (!target) return AclPrivate;
 
     // all changes received fromt the server must be processed as Admin
@@ -219,7 +225,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
     const HierarchyMapping = new Map<string, number>([
         ["Not Shared", 0],
         ["Can View", 1],
-        ["Can Add", 2],
+        ["Can Augment", 2],
         ["Can Edit", 3],
         ["Admin", 4]
     ]);
@@ -281,7 +287,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
 
     // 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, "None"].includes(value))) return true;
-    // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
+    // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
 
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
-- 
cgit v1.2.3-70-g09d2


From 8ebf3cb0ac7a023aa47a5264d74c3edaebf28b1b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 12 Oct 2020 17:22:26 -0400
Subject: updated getEffectiveAcl to be a computedFn.  got rid of OverrideAcl
 in favor of just using UpdatingFromServer

---
 src/client/DocServer.ts                            |  2 +-
 src/client/util/SerializationHelper.ts             |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 ++---
 src/fields/Doc.ts                                  | 20 +++++++-----
 src/fields/util.ts                                 | 36 +++++++++-------------
 6 files changed, 33 insertions(+), 37 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index d936f6e2a..00f9877c3 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,6 +1,6 @@
 import * as io from 'socket.io-client';
 import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, fetchProto, FieldsSym, UpdatingFromServer } from '../fields/Doc';
+import { Opt, Doc, UpdatingFromServer } from '../fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../fields/RefField';
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 19b217726..00ac6e521 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -43,7 +43,7 @@ export namespace SerializationHelper {
         }
 
         if (!obj.__type) {
-            if (ClientUtils.RELEASE) {
+            if (true || ClientUtils.RELEASE) {
                 console.warn("No property 'type' found in JSON.");
                 return undefined;
             } else {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b282d1e27..f3e563422 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -134,7 +134,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
             }
 
-            const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(d) !== AclPrivate).map(d => d as Doc);
+            const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index da797eda0..4f8f46111 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin, UpdatingFromServer } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import applyDevTools = require("prosemirror-dev-tools");
 import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_acl, GetEffectiveAcl } from '../../../../fields/util';
+import { TraceMobx, GetEffectiveAcl } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
@@ -805,9 +805,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     tr = tr.addMark(pos, pos + node.nodeSize, link);
                 }
             });
-            OVERRIDE_acl(true);
+            this.dataDoc[UpdatingFromServer] = true;  // need to allow permissions for adding links to readonly/augment only documents
             this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
-            OVERRIDE_acl(false);
+            this.dataDoc[UpdatingFromServer] = false;
         }
     }
     componentDidMount() {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 2452ab408..cea09b9c5 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -102,22 +102,28 @@ const AclMap = new Map<string, symbol>([
     [SharingPermissions.Admin, AclAdmin]
 ]);
 
-export function fetchProto(doc: Doc) {
+// caches the document access permissions for the current user.
+// this recursively updates all protos as well.
+export function updateCachedAcls(doc: Doc) {
     if (!doc) return;
     const permissions: { [key: string]: symbol } = {};
 
+    doc[UpdatingFromServer] = true;
     Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
+    doc[UpdatingFromServer] = false;
 
-    if (Object.keys(permissions).length) doc[AclSym] = permissions;
+    if (Object.keys(permissions).length) {
+        doc[AclSym] = permissions;
+    }
 
     if (doc.proto instanceof Promise) {
-        doc.proto.then(fetchProto);
+        doc.proto.then(updateCachedAcls);
         return doc.proto;
     }
 }
 
 @scriptingGlobal
-@Deserializable("Doc", fetchProto).withFields(["id"])
+@Deserializable("Doc", updateCachedAcls).withFields(["id"])
 export class Doc extends RefField {
     constructor(id?: FieldId, forceSave?: boolean) {
         super(id);
@@ -233,17 +239,15 @@ export class Doc extends RefField {
                     const prev = GetEffectiveAcl(this);
                     this[UpdatingFromServer] = true;
                     this[fKey] = value;
+                    this[UpdatingFromServer] = false;
                     if (fKey.startsWith("acl")) {
-                        fetchProto(this);
+                        updateCachedAcls(this);
                     }
-                    this[UpdatingFromServer] = false;
                     if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
                         DocServer.GetRefField(this[Id], true);
                     }
                     // if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) {
-                    //     this[UpdatingFromServer] = true;
                     //     this[FieldsSym](true);
-                    //     this[UpdatingFromServer] = false;
                     // }
                 };
                 if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index dd0444d61..d48011194 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,14 +1,14 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto, AclUnset, DocListCastAsync } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
 import { ObjectField } from "./ObjectField";
-import { action, trace, observable, reaction, computed } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate, ToString, ToScriptString } from "./FieldSymbols";
+import { action, trace, } from "mobx";
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
-import { ScriptCast, StrCast, DateCast, Cast, NumCast } from "./Types";
+import { ScriptCast, StrCast } from "./Types";
 import { returnZero } from "../Utils";
 import CursorField from "./CursorField";
 import { List } from "./List";
@@ -114,10 +114,6 @@ export function makeReadOnly() {
 export function makeEditable() {
     _setter = _setterImpl;
 }
-var _overrideAcl = false;
-export function OVERRIDE_acl(val: boolean) {
-    _overrideAcl = val;
-}
 
 export function normalizeEmail(email: string) {
     return email.replace(/\./g, '__');
@@ -154,18 +150,19 @@ export enum SharingPermissions {
     None = "Not Shared"
 }
 
-// return acl from cache or cache the acl and return. bcz: Argh!  NOT WORKING ... nothing gets invalidated properly....
+// return acl from cache or cache the acl and return.
 const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, user); }, true);
 
 /**
  * Calculates the effective access right to a document for the current user.
  */
 export function GetEffectiveAcl(target: any, user?: string): symbol {
-    return target ? getEffectiveAcl(target, user) : AclPrivate;
+    return !target ? AclPrivate :
+        target[UpdatingFromServer] ? AclAdmin : getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin.  return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
 }
 
 function getPropAcl(target: any, prop: string | symbol | number) {
-    if (prop === UpdatingFromServer) return AclAdmin;  // requesting the UpdatingFromServer prop must always go through to keep the local DB consistent
+    if (prop === UpdatingFromServer || target[UpdatingFromServer] || prop == AclSym) return AclAdmin;  // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
     if (prop && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable
     return GetEffectiveAcl(target);
 }
@@ -173,16 +170,12 @@ function getPropAcl(target: any, prop: string | symbol | number) {
 let HierarchyMapping: Map<symbol, number> | undefined;
 
 function getEffectiveAcl(target: any, user?: string): symbol {
-    if (target[UpdatingFromServer]) return AclAdmin; // all changes received from the server must be processed as Admin
-    // if the current user is the author of the document / the current user is a member of the admin group
-    const userChecked = user || Doc.CurrentUserEmail;
+    const targetAcls = target[AclSym];
+    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
     if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; // target may be a Doc of Proxy, so check __fields.author and .author
     if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin;
-    const targetAcls = target[AclSym];
 
     if (targetAcls && Object.keys(targetAcls).length) {
-        if (_overrideAcl) return AclEdit;
-
         HierarchyMapping = HierarchyMapping || new Map<symbol, number>([
             [AclPrivate, 0],
             [AclReadonly, 1],
@@ -199,7 +192,6 @@ function getEffectiveAcl(target: any, user?: string): symbol {
             if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
                 if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) {
                     effectiveAcl = value as symbol;
-                    if (effectiveAcl === AclAdmin) return effectiveAcl;
                 }
             }
         }
@@ -278,8 +270,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
         });
     }
 
-    layoutDocChanged && fetchProto(target); // updates target[AclSym] when changes to acls have been made
-    dataDocChanged && fetchProto(dataDoc);
+    layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made
+    dataDocChanged && updateCachedAcls(dataDoc);
 }
 
 const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
@@ -312,9 +304,9 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
 export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
     let prop = in_prop;
 
-    if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
+    if (in_prop === AclSym) return target[AclSym];
     if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop];
-    if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
+    if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
     if (prop === LayoutSym) return target.__LAYOUT__;
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
-- 
cgit v1.2.3-70-g09d2

