From 68916ee8daafb03ad2aff3338e18ca419ce888de Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 6 Oct 2020 12:15:49 -0400 Subject: fixed adding documents to check acl of added document(s) correctly --- src/client/views/collections/CollectionView.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections/CollectionView.tsx') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2bdc8e2f3..0ef3dd6cd 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -157,7 +157,7 @@ export class CollectionView extends Touchable { + 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 pushpin = Docs.Create.FontIconDocument({ @@ -186,9 +186,9 @@ export class CollectionView extends Touchable { const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); - const docAcl = GetEffectiveAcl(doc); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || docAcl === AclAdmin) { - const docs = doc instanceof Doc ? [doc] : doc as Doc[]; + const indocs = doc instanceof Doc ? [doc] : doc as Doc[]; + const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin); + if (docs.length) { const targetDataDoc = this.props.Document[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); const toRemove = value.filter(v => docs.includes(v)); -- cgit v1.2.3-70-g09d2 From e66380d8a99c3ee61faeaaebf6e28395332e1d64 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 6 Oct 2020 14:12:06 -0400 Subject: fixed list additions to be truly incremental to allow undo to work --- src/client/views/collections/CollectionView.tsx | 1 - src/fields/List.ts | 2 +- src/fields/util.ts | 28 ++++++++++++++++--------- src/server/websocket.ts | 10 +++++---- 4 files changed, 25 insertions(+), 16 deletions(-) (limited to 'src/client/views/collections/CollectionView.tsx') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 0ef3dd6cd..80e9b41ad 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -196,7 +196,6 @@ export class CollectionView extends Touchable { const ind = (targetDataDoc[this.props.fieldKey] as List).indexOf(doc); - (targetDataDoc[this.props.fieldKey] as List).splice(ind, 0); if (ind !== -1) { Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc); recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); diff --git a/src/fields/List.ts b/src/fields/List.ts index dca8d111c..d9bd54673 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -43,7 +43,7 @@ const listHandlers: any = { } } const res = list.__fields.push(...items); - this[Update]("$addToSet"); + this[Update]({ op: "$addToSet", items }); return res; }), reverse() { diff --git a/src/fields/util.ts b/src/fields/util.ts index 90446f531..b6128f5e6 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -11,6 +11,8 @@ import { ComputedField } from "./ScriptField"; import { ScriptCast, StrCast } from "./Types"; import { returnZero } from "../Utils"; import CursorField from "./CursorField"; +import { undo } from "prosemirror-history"; +import { List } from "./List"; function _readOnlySetter(): never { @@ -371,20 +373,26 @@ export function deleteProperty(target: any, prop: string | number | symbol) { export function updateFunction(target: any, prop: any, value: any, receiver: any) { let current = ObjectField.MakeCopy(value); return (diff?: any) => { - if (diff === "$addToSet") { - diff = { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; - } else { - diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; - } + const op = diff?.op === "$addToSet" ? + { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List(diff.items)) } } + : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; const oldValue = current; const newValue = ObjectField.MakeCopy(value); current = newValue; if (!(value instanceof CursorField) && !(value?.some?.((v: any) => v instanceof CursorField))) { - UndoManager.AddEvent({ - redo() { receiver[prop] = newValue; }, - undo() { receiver[prop] = oldValue; } - }); + UndoManager.AddEvent(diff?.op === "$addToSet" ? + { + redo: () => receiver[prop].push(...(newValue as List)), + undo: action(() => (newValue as List).forEach(doc => { + const ind = receiver[prop].indexOf(doc); + ind !== -1 && receiver[prop].splice(ind, 1); + })) + } + : { + redo: () => receiver[prop] = newValue, + undo: () => receiver[prop] = oldValue + }); } - target[Update](diff); + target[Update](op); }; } \ No newline at end of file diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 3687ef876..221a01308 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -271,14 +271,16 @@ export namespace WebSocket { return typeof value === "string" ? value : value[0]; } - function updateListField(socket: Socket, diff: Diff, results?: Transferable): void { + function updateListField(socket: Socket, diff: Diff, curListItems?: Transferable): void { diff.diff.$set = diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones delete diff.diff.$addToSet; const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; - const list = (results as any).fields?.[updatefield.replace("fields.", "")]?.fields; + const newListItems = diff.diff.$set[updatefield].fields; + const curList = (curListItems as any).fields?.[updatefield.replace("fields.", "")]?.fields; const prelen = diff.diff.$set[updatefield].fields.length; - list?.forEach((item: any) => !diff.diff.$set[updatefield].fields.some((x: any) => x.fieldId === item.fieldId) && diff.diff.$set[updatefield].fields.push(item)); - const sendBack = diff.diff.$set[updatefield].fields.length !== prelen; + let insInd = 0; + curList?.forEach((curItem: any) => !newListItems.some((newItem: any) => newItem.fieldId === curItem.fieldId) && newListItems.splice(insInd++, 0, curItem)); + const sendBack = curList.length !== prelen; Database.Instance.update(diff.id, diff.diff, () => { if (sendBack) { -- cgit v1.2.3-70-g09d2 From 27635402ad810b910557eb1a86c7e85fa281aaee Mon Sep 17 00:00:00 2001 From: bobzel 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/CollectionView.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(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(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 [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 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 { - 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 { 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
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 { 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 { 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= 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([ ["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 04bbff759710d58fa97ce2f0d685ec59b6beb60e Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Oct 2020 12:53:40 -0400 Subject: split getEffectiveAcl code for determining if a field can be modified out into getPropAcl. --- src/client/util/SharingManager.tsx | 4 +-- src/client/views/DocComponent.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- src/fields/util.ts | 44 ++++++++++++++----------- 4 files changed, 28 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections/CollectionView.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 0c8f19eae..914253e3c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -177,7 +177,7 @@ export class SharingManager extends React.Component<{}> { users.forEach(({ user, sharingDoc }) => { if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists }); } }); @@ -273,7 +273,7 @@ export class SharingManager extends React.Component<{}> { distributeAcls(acl, permission as SharingPermissions, doc); if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); - else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); }); } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 98e888538..a55f4adaf 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -164,7 +164,7 @@ export function ViewBoxAnnotatableComponent

{ for (const [key, value] of Object.entries(this.props.Document[AclSym])) { if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index cfd24545b..a27fa5a66 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -140,7 +140,7 @@ export class CollectionView extends Touchable { for (const [key, value] of Object.entries(this.props.Document[AclSym])) { if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); diff --git a/src/fields/util.ts b/src/fields/util.ts index 56736028a..dd0444d61 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -155,32 +155,35 @@ export enum SharingPermissions { } // return acl from cache or cache the acl and return. bcz: Argh! NOT WORKING ... nothing gets invalidated properly.... -const getEffectiveAclCache = computedFn(function (target: any, playgroundProp: boolean, user?: string) { return getEffectiveAcl(target, playgroundProp, user); }, true); +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, in_prop?: string | symbol | number, user?: string): symbol { - if (!target) return AclPrivate; - if (in_prop === UpdatingFromServer) return AclAdmin; // requesting the UpdatingFromServer prop must always go through to keep the local DB consistent - const playgroundProp = in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()) ? true : false; - return getEffectiveAcl(target, playgroundProp, user); +export function GetEffectiveAcl(target: any, user?: string): symbol { + return target ? getEffectiveAcl(target, user) : AclPrivate; } -function getEffectiveAcl(target: any, playgroundProp: boolean, user?: string): symbol { +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 && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable + return GetEffectiveAcl(target); +} + +let HierarchyMapping: Map | 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; - if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; + 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 (target[AclSym] && Object.keys(target[AclSym]).length) { + if (targetAcls && Object.keys(targetAcls).length) { + if (_overrideAcl) return AclEdit; - // if the acl is being overriden or the property being modified is one of the playground fields (which can be freely modified) - if (_overrideAcl || playgroundProp) return AclEdit; - - let effectiveAcl = AclPrivate; - const HierarchyMapping = new Map([ + HierarchyMapping = HierarchyMapping || new Map([ [AclPrivate, 0], [AclReadonly, 1], [AclAddonly, 2], @@ -188,12 +191,13 @@ function getEffectiveAcl(target: any, playgroundProp: boolean, user?: string): s [AclAdmin, 4] ]); - for (const [key, value] of Object.entries(target[AclSym])) { + let effectiveAcl = AclPrivate; + for (const [key, value] of Object.entries(targetAcls)) { // there are issues with storing fields with . in the name, so they are replaced with _ during creation // as a result we need to restore them again during this comparison. const entity = denormalizeEmail(key.substring(4)); // an individual or a group - if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) { - if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { + if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { + if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) { effectiveAcl = value as symbol; if (effectiveAcl === AclAdmin) return effectiveAcl; } @@ -201,8 +205,8 @@ function getEffectiveAcl(target: any, playgroundProp: boolean, user?: string): s } // if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document - const override = target[AclSym]["acl-Override"]; - if (override !== AclUnset && override !== undefined) effectiveAcl = target[AclSym]["acl-Override"]; + const override = targetAcls["acl-Override"]; + if (override !== AclUnset && override !== undefined) effectiveAcl = override; // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl) return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)! < 3 ? AclEdit : effectiveAcl; @@ -282,7 +286,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - const effectiveAcl = GetEffectiveAcl(target, in_prop); + const effectiveAcl = getPropAcl(target, prop); if (effectiveAcl !== AclEdit && 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 -- cgit v1.2.3-70-g09d2