diff options
Diffstat (limited to 'src/fields')
| -rw-r--r-- | src/fields/Doc.ts | 66 | ||||
| -rw-r--r-- | src/fields/Schema.ts | 4 | ||||
| -rw-r--r-- | src/fields/ScriptField.ts | 4 | ||||
| -rw-r--r-- | src/fields/documentSchemas.ts | 1 | ||||
| -rw-r--r-- | src/fields/util.ts | 47 | 
5 files changed, 86 insertions, 36 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 7e91a7761..267defa4b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -17,11 +17,10 @@ import { RichTextField } from "./RichTextField";  import { ImageField, VideoField, WebField, AudioField, PdfField } from "./URLField";  import { DateField } from "./DateField";  import { listSpec } from "./Schema"; -import { ComputedField } from "./ScriptField"; +import { ComputedField, ScriptField } from "./ScriptField";  import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; -import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util"; +import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl, SharingPermissions } from "./util";  import { LinkManager } from "../client/util/LinkManager"; -import { SharingPermissions } from "../client/util/SharingManager";  import JSZip = require("jszip");  import { saveAs } from "file-saver"; @@ -103,26 +102,28 @@ export const AclPrivate = Symbol("AclOwnerOnly");  export const AclReadonly = Symbol("AclReadOnly");  export const AclAddonly = Symbol("AclAddonly");  export const AclEdit = Symbol("AclEdit"); +export const AclAdmin = Symbol("AclAdmin");  export const UpdatingFromServer = Symbol("UpdatingFromServer"); -const CachedUpdates = Symbol("Cached updates"); +export const CachedUpdates = Symbol("Cached updates");  const AclMap = new Map<string, symbol>([      [SharingPermissions.None, AclPrivate],      [SharingPermissions.View, AclReadonly],      [SharingPermissions.Add, AclAddonly], -    [SharingPermissions.Edit, AclEdit] +    [SharingPermissions.Edit, AclEdit], +    [SharingPermissions.Admin, AclAdmin]  ]);  export function fetchProto(doc: Doc) { -    // if (doc.author !== Doc.CurrentUserEmail) { -    untracked(() => { -        const permissions: { [key: string]: symbol } = {}; +    const permissions: { [key: string]: symbol } = {}; -        Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!); +    Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!); -        if (Object.keys(permissions).length) doc[AclSym] = permissions; -    }); -    // } +    if (Object.keys(permissions).length) doc[AclSym] = permissions; + +    if (GetEffectiveAcl(doc) === AclPrivate) { +        runInAction(() => doc[FieldsSym](true)); +    }      if (doc.proto instanceof Promise) {          doc.proto.then(fetchProto); @@ -142,7 +143,7 @@ export class Doc extends RefField {              has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,              ownKeys: target => {                  const obj = {} as any; -                if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields); +                if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);                  runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);                  return Object.keys(obj);              }, @@ -150,11 +151,11 @@ export class Doc extends RefField {                  if (prop.toString() === "__LAYOUT__") {                      return Reflect.getOwnPropertyDescriptor(target, prop);                  } -                if (prop in target.__fields) { +                if (prop in target.__fieldKeys) {                      return {                          configurable: true,//TODO Should configurable be true?                          enumerable: true, -                        value: target.__fields[prop] +                        value: 0//() => target.__fields[prop])                      };                  }                  return Reflect.getOwnPropertyDescriptor(target, prop); @@ -178,15 +179,23 @@ export class Doc extends RefField {          this.___fields = value;          for (const key in value) {              const field = value[key]; +            field && (this.__fieldKeys[key] = true);              if (!(field instanceof ObjectField)) continue;              field[Parent] = this[Self];              field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);          }      } +    private get __fieldKeys() { return this.___fieldKeys; } +    private set __fieldKeys(value) { +        this.___fieldKeys = value; +    }      @observable      private ___fields: any = {}; +    @observable +    private ___fieldKeys: any = {}; +      private [UpdatingFromServer]: boolean = false;      private [Update] = (diff: any) => { @@ -195,7 +204,14 @@ export class Doc extends RefField {      private [Self] = this;      private [SelfProxy]: any; -    public [FieldsSym] = () => this.___fields; +    public [FieldsSym] = (clear?: boolean) => { +        if (clear) { +            this.___fields = {}; +            this.___fieldKeys = {}; +        } +        return this.___fields; +    } +    @observable      public [AclSym]: { [key: string]: symbol };      public [WidthSym] = () => NumCast(this[SelfProxy]._width);      public [HeightSym] = () => NumCast(this[SelfProxy]._height); @@ -236,11 +252,18 @@ export class Doc extends RefField {                  const fKey = key.substring(7);                  const fn = async () => {                      const value = await SerializationHelper.Deserialize(set[key]); +                    const prev = GetEffectiveAcl(this);                      this[UpdatingFromServer] = true;                      this[fKey] = value; +                    if (fKey.startsWith("ACL")) { +                        fetchProto(this); +                    }                      this[UpdatingFromServer] = false; +                    if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { +                        DocServer.GetRefField(this[Id], true); +                    }                  }; -                if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { +                if (sameAuthor || fKey.startsWith("ACL") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {                      delete this[CachedUpdates][fKey];                      await fn();                  } else { @@ -564,6 +587,11 @@ export namespace Doc {      }      export async function Zip(doc: Doc) { +        // const a = document.createElement("a"); +        // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); +        // a.href = url; +        // a.download = `DocExport-${this.props.Document[Id]}.zip`; +        // a.click();          const { clone, map } = await Doc.MakeClone(doc, true);          function replacer(key: any, value: any) {              if (["cloneOf", "context", "cursors"].includes(key)) return undefined; @@ -575,6 +603,7 @@ export namespace Doc {                      return { fieldId: value[Id], __type: "proxy" };                  }              } +            else if (value instanceof ScriptField) return { script: value.script, __type: "script" };              else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" };              else if (value instanceof ImageField) return { url: value.url.href, __type: "image" };              else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" }; @@ -892,7 +921,6 @@ export namespace Doc {          return doc;      }      export function UnBrushDoc(doc: Doc) { -          if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;          brushManager.BrushedDoc.delete(doc);          brushManager.BrushedDoc.delete(Doc.GetProto(doc)); @@ -1212,7 +1240,7 @@ Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });  Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });  Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });  Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); -Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); +Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });  Scripting.addGlobal(function aliasDocs(field: any) { return Doc.aliasDocs(field); });  Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });  Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts index 98ef3e087..23ac50f74 100644 --- a/src/fields/Schema.ts +++ b/src/fields/Schema.ts @@ -31,7 +31,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu      }      const proto = new Proxy({}, {          get(target: any, prop, receiver) { -            const field = receiver.doc[prop]; +            const field = receiver.doc?.[prop];              if (prop in schema) {                  const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?                  if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec @@ -52,7 +52,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu              return field;          },          set(target: any, prop, value, receiver) { -            receiver.doc[prop] = value; +            receiver.doc && (receiver.doc[prop] = value);  // receiver.doc may be undefined as the result of a change in ACLs              return true;          }      }); diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index bd08b2f32..f55483a5b 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -183,6 +183,10 @@ Scripting.addGlobal(function getIndexVal(list: any[], index: number) {      return list.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any);  }, "returns the value at a given index of a list", "(list: any[], index: number)"); +Scripting.addGlobal(function makeScript(script: string) { +    return ScriptField.MakeScript(script); +}, "returns the value at a given index of a list", "(list: any[], index: number)"); +  export namespace ComputedField {      let useComputed = true;      export function DisableComputedFields() { diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index ddffb56c3..8cf8f47b7 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -90,6 +90,7 @@ export const documentSchema = createSchema({      followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, )       hideLinkButton: "boolean",  // whether the blue link counter button should be hidden      hideAllLinks: "boolean",    // whether all individual blue anchor dots should be hidden +    linkDisplay: "boolean",     // whether a link connection should be shown between link anchor endpoints.      isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations      isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked         isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee) diff --git a/src/fields/util.ts b/src/fields/util.ts index ef66d9633..a62795e64 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,15 +1,15 @@  import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym } 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 { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate } from "./FieldSymbols";  import { DocServer } from "../client/DocServer";  import { ComputedField } from "./ScriptField";  import { ScriptCast, StrCast } from "./Types"; -import { SharingPermissions } from "../client/util/SharingManager"; +import { returnZero } from "../Utils";  function _readOnlySetter(): never { @@ -67,16 +67,21 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number          delete curValue[Parent];          delete curValue[OnUpdate];      } + +    const effectiveAcl = GetEffectiveAcl(target); +      const writeMode = DocServer.getFieldWriteMode(prop as string);      const fromServer = target[UpdatingFromServer];      const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail); -    const writeToDoc = sameAuthor || GetEffectiveAcl(target) === AclEdit || (writeMode !== DocServer.WriteMode.LiveReadonly); -    const writeToServer = (sameAuthor || GetEffectiveAcl(target) === AclEdit || writeMode === DocServer.WriteMode.Default) && !playgroundMode; +    const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly); +    const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !playgroundMode;      if (writeToDoc) {          if (value === undefined) { +            target.__fieldKeys && (delete target.__fieldKeys[prop]);              delete target.__fields[prop];          } else { +            target.__fieldKeys && (target.__fieldKeys[prop] = true);              target.__fields[prop] = value;          }          //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; @@ -126,12 +131,20 @@ export function setGroups(groups: string[]) {      currentUserGroups = groups;  } +export enum SharingPermissions { +    Admin = "Admin", +    Edit = "Can Edit", +    Add = "Can Add", +    View = "Can View", +    None = "Not Shared" +} +  export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { -    if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit; +    if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;      if (target[AclSym] && Object.keys(target[AclSym]).length) { -        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit; +        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin;          if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; @@ -142,7 +155,8 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)              [AclPrivate, 0],              [AclReadonly, 1],              [AclAddonly, 2], -            [AclEdit, 3] +            [AclEdit, 3], +            [AclAdmin, 4]          ]);          for (const [key, value] of Object.entries(target[AclSym])) { @@ -156,7 +170,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)          }          return aclPresent ? effectiveAcl : AclEdit;      } -    return AclEdit; +    return AclAdmin;  }  export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) { @@ -165,7 +179,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc          ["Not Shared", 0],          ["Can View", 1],          ["Can Add", 2], -        ["Can Edit", 3] +        ["Can Edit", 3], +        ["Admin", 4]      ]);      const dataDoc = target[DataSym]; @@ -205,11 +220,11 @@ 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; -    if (GetEffectiveAcl(target, in_prop) !== AclEdit) { -        return true; -    } +    const effectiveAcl = GetEffectiveAcl(target, in_prop); +    if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) 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") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].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 !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {          if (!prop.startsWith("_")) { @@ -229,8 +244,10 @@ 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 === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop];      if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; -    if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; +    if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;      if (prop === LayoutSym) {          return target.__LAYOUT__;      }  | 
