aboutsummaryrefslogtreecommitdiff
path: root/src/fields/util.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields/util.ts')
-rw-r--r--src/fields/util.ts304
1 files changed, 171 insertions, 133 deletions
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 8fb35981b..cbb560114 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,20 +1,41 @@
-import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAugment, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing, AclSelfEdit } from "./Doc";
-import { SerializationHelper } from "../client/util/SerializationHelper";
-import { ProxyField, PrefetchProxy } from "./Proxy";
-import { RefField } from "./RefField";
-import { ObjectField } from "./ObjectField";
-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 } from "./Types";
-import { returnZero } from "../Utils";
-import CursorField from "./CursorField";
-import { List } from "./List";
-import { SnappingManager } from "../client/util/SnappingManager";
-import { computedFn } from "mobx-utils";
-import { RichTextField } from "./RichTextField";
+import { UndoManager } from '../client/util/UndoManager';
+import {
+ Doc,
+ FieldResult,
+ UpdatingFromServer,
+ LayoutSym,
+ AclPrivate,
+ AclEdit,
+ AclReadonly,
+ AclAugment,
+ AclSym,
+ DataSym,
+ DocListCast,
+ AclAdmin,
+ HeightSym,
+ WidthSym,
+ updateCachedAcls,
+ AclUnset,
+ DocListCastAsync,
+ ForceServerWrite,
+ Initializing,
+ AclSelfEdit,
+} from './Doc';
+import { SerializationHelper } from '../client/util/SerializationHelper';
+import { ProxyField, PrefetchProxy } from './Proxy';
+import { RefField } from './RefField';
+import { ObjectField } from './ObjectField';
+import { action, observable, runInAction, trace } from 'mobx';
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from './FieldSymbols';
+import { DocServer } from '../client/DocServer';
+import { ComputedField } from './ScriptField';
+import { ScriptCast, StrCast } from './Types';
+import { returnZero } from '../Utils';
+import CursorField from './CursorField';
+import { List } from './List';
+import { SnappingManager } from '../client/util/SnappingManager';
+import { computedFn } from 'mobx-utils';
+import { RichTextField } from './RichTextField';
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -44,7 +65,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
return true;
}
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
target[prop] = value;
return true;
}
@@ -76,15 +97,13 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
- const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
- const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer =
- (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && (value instanceof RichTextField))) &&
- !DocServer.Control.isReadOnly();
+ const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
+ const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly;
+ const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly();
if (writeToDoc) {
if (value === undefined) {
- target.__fieldKeys && (delete target.__fieldKeys[prop]);
+ target.__fieldKeys && delete target.__fieldKeys[prop];
delete target.__fields[prop];
} else {
target.__fieldKeys && (target.__fieldKeys[prop] = true);
@@ -96,16 +115,17 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
//if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
if (writeToServer) {
- if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
- else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
+ if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } });
+ else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } });
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
}
- !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
+ !receiver[Initializing] &&
+ (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
UndoManager.AddEvent({
- redo: () => receiver[prop] = value,
- undo: () => receiver[prop] = curValue,
- prop: prop?.toString()
+ redo: () => (receiver[prop] = value),
+ undo: () => (receiver[prop] = curValue),
+ prop: prop?.toString(),
});
return true;
}
@@ -136,7 +156,6 @@ export function denormalizeEmail(email: string) {
// playgroundMode = !playgroundMode;
// }
-
/**
* Copies parent's acl fields to the child
*/
@@ -145,35 +164,37 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
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 permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
+ key.startsWith('acl') && distributeAcls(key, permission, child);
}
}
/**
* These are the various levels of access a user can have to a document.
- *
+ *
* Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
- *
+ *
* 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 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.
- *
+ *
* None: the document is not shared with that user.
*/
export enum SharingPermissions {
- Admin = "Admin",
- Edit = "Edit",
- SelfEdit = "Self Edit",
- Augment = "Augment",
- View = "View",
- None = "Not Shared"
+ Admin = 'Admin',
+ Edit = 'Edit',
+ SelfEdit = 'Self Edit',
+ Augment = 'Augment',
+ View = 'View',
+ None = 'Not Shared',
}
// return acl from cache or cache the acl and return.
-const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, 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.
@@ -183,33 +204,47 @@ export function GetEffectiveAcl(target: any, user?: string): symbol {
if (target[UpdatingFromServer]) return AclAdmin;
// authored documents are private until an ACL is set.
if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate;
- return 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)
+ return 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 || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
+ if (prop === UpdatingFromServer || prop === Initializing || 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.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable
return GetEffectiveAcl(target);
}
let HierarchyMapping: Map<symbol, number> | undefined;
+let cachedGroups = observable([] as string[]);
+/// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts
+// need to investigate further what caused the mobx update problems and move to a better location.
+const getCachedGroupByNameCache = computedFn(function (name: string) {
+ return cachedGroups.includes(name);
+}, true);
+export function GetCachedGroupByName(name: string) {
+ return getCachedGroupByNameCache(name);
+}
+export function SetCachedGroups(groups: string[]) {
+ runInAction(() => cachedGroups.push(...groups));
+}
function getEffectiveAcl(target: any, user?: string): symbol {
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
- const targetAuthor = (target.__fields?.author || target.author); // target may be a Doc of Proxy, so check __fields.author and .author
+ 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
+ const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author
if (userChecked === targetAuthor || !targetAuthor) return AclAdmin;
- if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin;
+ if (GetCachedGroupByName('Admin')) return AclAdmin;
if (targetAcls && Object.keys(targetAcls).length) {
- HierarchyMapping = HierarchyMapping || new Map<symbol, number>([
- [AclPrivate, 0],
- [AclReadonly, 1],
- [AclAugment, 2],
- [AclSelfEdit, 2.5],
- [AclEdit, 3],
- [AclAdmin, 4]
- ]);
+ HierarchyMapping =
+ HierarchyMapping ||
+ new Map<symbol, number>([
+ [AclPrivate, 0],
+ [AclReadonly, 1],
+ [AclAugment, 2],
+ [AclSelfEdit, 2.5],
+ [AclEdit, 3],
+ [AclAdmin, 4],
+ ]);
let effectiveAcl = AclPrivate;
for (const [key, value] of Object.entries(targetAcls)) {
@@ -217,14 +252,14 @@ function getEffectiveAcl(target: any, user?: string): symbol {
// as a result we need to restore them again during this comparison.
const entity = denormalizeEmail(key.substring(4)); // an individual or a group
if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
- if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) {
+ if (GetCachedGroupByName(entity) || userChecked === entity) {
effectiveAcl = value as symbol;
}
}
}
// 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 = targetAcls["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)
@@ -246,12 +281,12 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
visited.push(target);
const HierarchyMapping = new Map<string, number>([
- ["Not Shared", 0],
- ["Can View", 1],
- ["Can Augment", 2],
- ["Self Edit", 2.5],
- ["Can Edit", 3],
- ["Admin", 4]
+ ['Not Shared', 0],
+ ['Can View', 1],
+ ['Can Augment', 2],
+ ['Self Edit', 2.5],
+ ['Can Edit', 3],
+ ['Admin', 4],
]);
let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
@@ -271,7 +306,6 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
}
if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
-
if (GetEffectiveAcl(dataDoc) === AclAdmin) {
dataDoc[key] = acl;
dataDocChanged = true;
@@ -282,7 +316,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
links.forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
// maps over the children of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? "-all" : "")]).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? '-all' : '')]).map(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
@@ -292,7 +326,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
});
// maps over the annotations of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '-annotations']).map(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
@@ -311,11 +345,11 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
const effectiveAcl = getPropAcl(target, prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) 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, "None"].includes(value))) return true;
+ 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 Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
- if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) {
- if (!prop.startsWith("__")) prop = prop.substring(1);
+ if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
+ if (!prop.startsWith('__')) prop = prop.substring(1);
if (target.__LAYOUT__) {
target.__LAYOUT__[prop] = value;
return true;
@@ -331,17 +365,18 @@ export function getter(target: any, in_prop: string | symbol | number, receiver:
let prop = in_prop;
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 (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) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
if (prop === LayoutSym) return target.__LAYOUT__;
- if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) {
- if (!prop.startsWith("__")) prop = prop.substring(1);
+ if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
+ if (!prop.startsWith('__')) prop = prop.substring(1);
if (target.__LAYOUT__) return target.__LAYOUT__[prop];
}
- if (prop === "then") {//If we're being awaited
+ if (prop === 'then') {
+ //If we're being awaited
return undefined;
}
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
return target.__fields[prop] || target[prop];
}
if (SerializationHelper.IsSerializing()) {
@@ -362,22 +397,21 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
field = res.value;
}
}
- if (field === undefined && !ignoreProto && prop !== "proto") {
- const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
+ if (field === undefined && !ignoreProto && prop !== 'proto') {
+ const proto = getFieldImpl(target, 'proto', receiver, true); //TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
}
return undefined;
}
return field;
-
}
export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
return getFieldImpl(target, prop, undefined, ignoreProto);
}
export function deleteProperty(target: any, prop: string | number | symbol) {
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
delete target[prop];
return true;
}
@@ -389,64 +423,68 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
let lastValue = ObjectField.MakeCopy(value);
return (diff?: any) => {
const op =
- diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } :
- diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
- : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
+ diff?.op === '$addToSet'
+ ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
+ : diff?.op === '$remFromSet'
+ ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
+ : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } };
!op.$set && ((op as any).length = diff.length);
const prevValue = ObjectField.MakeCopy(lastValue as List<any>);
lastValue = ObjectField.MakeCopy(value);
const newValue = ObjectField.MakeCopy(value);
- if (!(value instanceof CursorField) && !(value?.some?.((v: any) => v instanceof CursorField))) {
- !receiver[UpdatingFromServer] && UndoManager.AddEvent(
- diff?.op === "$addToSet" ?
- {
- redo: () => {
- receiver[prop].push(...diff.items.map((item: any) => item.value ? item.value() : item));
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: action(() => {
- // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- prop: ""
- } :
- diff?.op === "$remFromSet" ?
- {
- redo: action(() => {
- diff.items.forEach((item: any) => {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- undo: () => {
- // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: ""
- }
+ if (!(value instanceof CursorField) && !value?.some?.((v: any) => v instanceof CursorField)) {
+ !receiver[UpdatingFromServer] &&
+ UndoManager.AddEvent(
+ diff?.op === '$addToSet'
+ ? {
+ redo: () => {
+ receiver[prop].push(...diff.items.map((item: any) => (item.value ? item.value() : item)));
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ undo: action(() => {
+ // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
+ diff.items.forEach((item: any) => {
+ const ind = receiver[prop].indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ }),
+ prop: '',
+ }
+ : diff?.op === '$remFromSet'
+ ? {
+ redo: action(() => {
+ diff.items.forEach((item: any) => {
+ const ind = receiver[prop].indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ }),
+ undo: () => {
+ // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
+ diff.items.forEach((item: any) => {
+ const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ prop: '',
+ }
: {
- redo: () => {
- receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: () => {
- // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
- receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: ""
- });
+ redo: () => {
+ receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ undo: () => {
+ // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
+ receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ prop: '',
+ }
+ );
}
target[Update](op);
};
-} \ No newline at end of file
+}