aboutsummaryrefslogtreecommitdiff
path: root/src/fields
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields')
-rw-r--r--src/fields/DateField.ts3
-rw-r--r--src/fields/Doc.ts182
-rw-r--r--src/fields/InkField.ts11
-rw-r--r--src/fields/List.ts61
-rw-r--r--src/fields/ObjectField.ts25
-rw-r--r--src/fields/RefField.ts4
-rw-r--r--src/fields/RichTextUtils.ts17
-rw-r--r--src/fields/Schema.ts6
-rw-r--r--src/fields/SchemaHeaderField.ts25
-rw-r--r--src/fields/ScriptField.ts96
-rw-r--r--src/fields/Types.ts73
-rw-r--r--src/fields/URLField.ts32
-rw-r--r--src/fields/util.ts204
13 files changed, 336 insertions, 403 deletions
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index 1e5222fb6..56a3177f8 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -1,5 +1,5 @@
-import { Deserializable } from '../client/util/SerializationHelper';
import { serializable, date } from 'serializr';
+import { Deserializable } from '../client/util/SerializationHelper';
import { ObjectField } from './ObjectField';
import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
@@ -38,6 +38,7 @@ export class DateField extends ObjectField {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function d(...dateArgs: any[]) {
return new DateField(new (Date as any)(...dateArgs));
});
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 48214cf25..60c6402d4 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,26 +1,20 @@
-import { saveAs } from 'file-saver';
import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { alias, map, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
-import { LinkManager } from '../client/util/LinkManager';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
import { undoable } from '../client/util/UndoManager';
-import { DocumentView } from '../client/views/nodes/DocumentView';
-import { decycle } from '../decycler/decycler';
-import * as JSZipUtils from '../JSZipUtils';
-import { incrementTitleCopy, Utils } from '../Utils';
-import { DateField } from './DateField';
+import { ClientUtils, incrementTitleCopy } from '../ClientUtils';
import {
AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
} from './DocSymbols'; // prettier-ignore
import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
-import { InkField, InkTool } from './InkField';
-import { List, ListFieldName } from './List';
+import { InkTool } from './InkField';
+import { List } from './List';
import { ObjectField } from './ObjectField';
import { PrefetchProxy, ProxyField } from './Proxy';
import { FieldId, RefField } from './RefField';
@@ -28,10 +22,8 @@ import { RichTextField } from './RichTextField';
import { listSpec } from './Schema';
import { ComputedField, ScriptField } from './ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
-import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
-import * as JSZip from 'jszip';
-import { FieldViewProps } from '../client/views/nodes/FieldView';
+
export const LinkedTo = '-linkedTo';
export namespace Field {
/**
@@ -45,7 +37,7 @@ export namespace Field {
export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const valFunc = (field: Field): string => {
+ const valFunc = (field: FieldType): string => {
const res =
field instanceof ComputedField && showComputedValue
? field.value(doc)
@@ -63,7 +55,7 @@ export namespace Field {
};
return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
}
- export function toScriptString(field: Field) {
+ export function toScriptString(field: FieldType) {
switch (typeof field) {
case 'string': if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ". In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
@@ -72,8 +64,8 @@ export namespace Field {
default: return field?.[ToScriptString]?.() ?? 'null';
} // prettier-ignore
}
- export function toJavascriptString(field: Field) {
- var rawjava = '';
+ export function toJavascriptString(field: FieldType) {
+ let rawjava = '';
switch (typeof field) {
case 'string':
@@ -82,7 +74,7 @@ export namespace Field {
break;
default: rawjava = field?.[ToJavascriptString]?.() ?? '';
} // prettier-ignore
- var script = rawjava;
+ let script = rawjava;
// this is a bit hacky, but we treat '^@' references to a published document
// as a kind of macro to include the content of those documents
Doc.MyPublishedDocs.forEach(doc => {
@@ -95,23 +87,23 @@ export namespace Field {
});
return script;
}
- export function toString(field: Field) {
+ export function toString(field: FieldType) {
if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
return field?.[ToString]?.() || '';
}
- export function IsField(field: any): field is Field;
- export function IsField(field: any, includeUndefined: true): field is Field | undefined;
- export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
+ export function IsField(field: any): field is FieldType;
+ export function IsField(field: any, includeUndefined: true): field is FieldType | undefined;
+ export function IsField(field: any, includeUndefined: boolean = false): field is FieldType | undefined {
return ['string', 'number', 'boolean'].includes(typeof field) || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
}
export function Copy(field: any) {
return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
}
}
-export type Field = number | string | boolean | ObjectField | RefField;
+export type FieldType = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
-export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
+export type FieldResult<T extends FieldType = FieldType> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
/**
* Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
@@ -159,7 +151,7 @@ export function updateCachedAcls(doc: Doc) {
if (!doc) return;
const target = (doc as any)?.__fieldTuples ?? doc;
- const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
+ const permissions: { [key: string]: symbol } = !target.author || target.author === ClientUtils.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl));
if (Object.keys(permissions).length || doc[DocAcl]?.length) {
runInAction(() => (doc[DocAcl] = permissions));
@@ -178,7 +170,8 @@ export class Doc extends RefField {
@observable public static GuestDashboard: Doc | undefined = undefined;
@observable public static GuestTarget: Doc | undefined = undefined;
@observable public static GuestMobile: Doc | undefined = undefined;
- public static CurrentUserEmail: string = '';
+
+ public static AddLink: undefined | ((link: Doc, checkExists?: boolean) => void);
public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
@@ -320,7 +313,7 @@ export class Doc extends RefField {
@observable public [Animation]: Opt<Doc> = undefined;
@observable public [Highlight]: boolean = false;
@observable public [Brushed]: boolean = false;
- @observable public [DocViews] = new ObservableSet<DocumentView>();
+ @observable public [DocViews] = new ObservableSet<any /* DocumentView */>();
private [Self] = this;
private [SelfProxy]: any;
@@ -329,7 +322,7 @@ export class Doc extends RefField {
private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
public [Initializing]: boolean = false;
- public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
+ public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
DocServer.UpdateField(this[Id], serverOp);
}
@@ -363,7 +356,7 @@ export class Doc extends RefField {
public async [HandleUpdate](diff: any) {
const set = diff.$set;
- const sameAuthor = this.author === Doc.CurrentUserEmail;
+ const sameAuthor = this.author === ClientUtils.CurrentUserEmail;
if (set) {
for (const key in set) {
const fprefix = 'fields.';
@@ -454,7 +447,7 @@ export namespace Doc {
return doc;
}
}
- export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+ export function GetT<T extends FieldType>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
}
export function isTemplateDoc(doc: Doc) {
@@ -482,7 +475,7 @@ export namespace Doc {
// 2) if the data doc has the field, then it's written there.
// 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding)
//
- export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+ export async function SetInPlace(doc: Doc, key: string, value: FieldType | undefined, defaultProto: boolean) {
if (key.startsWith('_')) key = key.substring(1);
const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined;
const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
@@ -500,7 +493,6 @@ export namespace Doc {
}
return protos;
}
-
/**
* This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
* the values of the properties of a source object into the target.
@@ -512,7 +504,7 @@ export namespace Doc {
* @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
* to a potentially undefined field, where each entry in this mapping is optional.
*/
- export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
+ export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<FieldType>>>, skipUndefineds: boolean = false, isInitializing = false) {
isInitializing && (doc[Initializing] = true);
for (const key in fields) {
if (fields.hasOwnProperty(key)) {
@@ -634,7 +626,7 @@ export namespace Doc {
embedding.createdFrom = doc;
embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
!Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
- embedding.author = Doc.CurrentUserEmail;
+ embedding.author = ClientUtils.CurrentUserEmail;
return embedding;
}
@@ -642,7 +634,7 @@ export namespace Doc {
export function BestEmbedding(doc: Doc) {
const dataDoc = doc[DocData];
const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
- const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
+ const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === ClientUtils.CurrentUserEmail);
bestEmbedding && Doc.AddEmbedding(doc, doc);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -700,7 +692,7 @@ export namespace Doc {
};
const docAtKey = doc[key];
if (key === 'author') {
- assignKey(Doc.CurrentUserEmail);
+ assignKey(ClientUtils.CurrentUserEmail);
} else if (docAtKey instanceof Doc) {
if (pruneDocs.includes(docAtKey)) {
// prune doc and do nothing
@@ -709,7 +701,7 @@ export namespace Doc {
(key.includes('layout[') ||
key.startsWith('layout') || //
['embedContainer', 'annotationOn', 'proto'].includes(key) ||
- (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === Doc.CurrentUserEmail))
+ (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail))
) {
assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
} else {
@@ -771,7 +763,7 @@ export namespace Doc {
const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
const repaired = new Set<Doc>();
const linkedDocs = Array.from(linkMap.values());
- linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true));
+ linkedDocs.map(link => Doc.AddLink?.(link, true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
@@ -790,84 +782,6 @@ export namespace Doc {
return { clone: copy, map: cloneMap, linkMap };
}
- export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
- const { clone, map, linkMap } = await Doc.MakeClone(doc);
- const proms = new Set<string>();
- function replacer(key: any, value: any) {
- if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
- if (value?.__type === 'image') {
- const extension = value.url.replace(/.*\./, '');
- proms.add(value.url.replace('.' + extension, '_o.' + extension));
- return SerializationHelper.Serialize(new ImageField(value.url));
- }
- if (value?.__type === 'pdf') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new PdfField(value.url));
- }
- if (value?.__type === 'audio') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new AudioField(value.url));
- }
- if (value?.__type === 'video') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new VideoField(value.url));
- }
- if (
- value instanceof Doc ||
- value instanceof ScriptField ||
- value instanceof RichTextField ||
- value instanceof InkField ||
- value instanceof CsvField ||
- value instanceof WebField ||
- value instanceof DateField ||
- value instanceof ProxyField ||
- value instanceof ComputedField
- ) {
- return SerializationHelper.Serialize(value);
- }
- if (value instanceof Array && key !== ListFieldName && key !== InkField.InkDataFieldName) return { fields: value, __type: 'list' };
- return value;
- }
-
- const docs: { [id: string]: any } = {};
- const links: { [id: string]: any } = {};
- Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
- Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1]));
- const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
-
- const zip = new JSZip();
- var count = 0;
- const promArr = Array.from(proms)
- .filter(url => url?.startsWith('/files'))
- .map(url => url.replace('/', '')); // window.location.origin));
- console.log(promArr.length);
- if (!promArr.length) {
- zip.file('docs.json', jsonDocs);
- zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
- } else
- promArr.forEach((url, i) => {
- // loading a file and add it in a zip file
- JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
- if (err) throw err; // or handle the error
- // // Generate a directory within the Zip file structure
- // const assets = zip.folder("assets");
- // assets.file(filename, data, {binary: true});
- const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
- zip.file(assetPathOnServer, data, { binary: true });
- console.log(' => ' + url);
- if (++count === promArr.length) {
- zip.file('docs.json', jsonDocs);
- zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
- // 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 _pendingMap = new Set<string>();
//
// Returns an expanded template layout for a target data document if there is a template relationship
@@ -954,7 +868,7 @@ export namespace Doc {
}
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
if (field instanceof RefField) {
if (field instanceof Doc) {
if (key === 'myLinkDatabase') {
@@ -1000,7 +914,7 @@ export namespace Doc {
}
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
if (field instanceof RefField) {
copy[key] = field;
} else if (cfield instanceof ComputedField) {
@@ -1038,7 +952,7 @@ export namespace Doc {
delegate[Initializing] = true;
updateCachedAcls(delegate);
delegate.proto = doc;
- delegate.author = Doc.CurrentUserEmail;
+ delegate.author = ClientUtils.CurrentUserEmail;
Object.keys(doc)
.filter(key => key.startsWith('acl'))
.forEach(key => (delegate[key] = doc[key]));
@@ -1067,7 +981,7 @@ export namespace Doc {
export function ApplyTemplate(templateDoc: Doc) {
if (templateDoc) {
const proto = new Doc();
- proto.author = Doc.CurrentUserEmail;
+ proto.author = ClientUtils.CurrentUserEmail;
const target = Doc.MakeDelegate(proto);
const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout');
const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
@@ -1123,7 +1037,7 @@ export namespace Doc {
// converts a document id to a url path on the server
export function globalServerPath(doc: Doc | string = ''): string {
- return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
+ return ClientUtils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
}
// converts a document id to a url path on the server
export function localServerPath(doc?: Doc): string {
@@ -1426,20 +1340,6 @@ export namespace Doc {
});
}
- export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
- const style: { [key: string]: any } = {};
- const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
- const replacer = (match: any, expr: string, offset: any, string: any) => {
- // bcz: this executes a script to convert a property expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
- };
- divKeys.map((prop: string) => {
- const p = (props as any)[prop];
- typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
- });
- return style;
- }
-
export function Paste(docids: string[], clone: boolean, addDocument: (doc: Doc | Doc[]) => boolean, ptx?: number, pty?: number, newPoint?: number[]) {
DocServer.GetRefFields(docids).then(async fieldlist => {
const list = Array.from(Object.values(fieldlist))
@@ -1505,7 +1405,7 @@ export namespace Doc {
// If they are not remapped, loading the file will overwrite any existing documents with those ids
//
export async function importDocument(file: File, remap = false) {
- const upload = Utils.prepend('/uploadDoc');
+ const upload = ClientUtils.prepend('/uploadDoc');
const formData = new FormData();
if (file) {
formData.append('file', file);
@@ -1518,7 +1418,7 @@ export namespace Doc {
const links = await DocServer.GetRefFields(json.linkids as string[]);
Array.from(Object.keys(links))
.map(key => links[key])
- .forEach(link => link instanceof Doc && LinkManager.Instance.addLink(link));
+ .forEach(link => link instanceof Doc && Doc.AddLink?.(link));
return doc;
}
}
@@ -1617,7 +1517,7 @@ export namespace Doc {
if (hasEntries || !excludeEmptyObjects) {
const resolved = target ?? new Doc();
if (hasEntries) {
- let result: Opt<Field>;
+ let result: Opt<FieldType>;
Object.keys(object).map(key => {
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
@@ -1639,9 +1539,9 @@ export namespace Doc {
* @returns the list mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertList)
*/
- const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
+ const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<FieldType>> => {
const target = new List();
- let result: Opt<Field>;
+ let result: Opt<FieldType>;
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
// otherwise been empty (List or Doc)s will just not be written
@@ -1651,7 +1551,7 @@ export namespace Doc {
}
};
- const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
+ const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<FieldType> => {
if (data === null || data === undefined) {
return undefined;
}
@@ -1719,7 +1619,7 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran
Doc.setDocRangeFilter(container, key, range);
});
ScriptingGlobals.add(function toJavascriptString(str: string) {
- return Field.toJavascriptString(str as Field);
+ return Field.toJavascriptString(str as FieldType);
});
ScriptingGlobals.add(function RtfField() {
return RichTextField.RTFfield();
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index b3e01229a..c4f5f7a24 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -2,6 +2,7 @@ import { Bezier } from 'bezier-js';
import { alias, createSimpleSchema, list, object, serializable } from 'serializr';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
+import { PointData } from '../pen-gestures/GestureTypes';
import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
@@ -16,12 +17,6 @@ export enum InkTool {
PresentationPin = 'presentationpin',
}
-// Defines a point in an ink as a pair of x- and y-coordinates.
-export interface PointData {
- X: number;
- Y: number;
-}
-
export type Segment = Array<Bezier>;
// Defines an ink as an array of points.
@@ -62,10 +57,10 @@ const strokeDataSchema = createSimpleSchema({
'*': true,
});
+export const InkDataFieldName = '__inkData';
@Deserializable('ink')
export class InkField extends ObjectField {
- public static InkDataFieldName = '__inkData';
- @serializable(alias(InkField.InkDataFieldName, list(object(strokeDataSchema))))
+ @serializable(alias(InkDataFieldName, list(object(strokeDataSchema))))
readonly inkData: InkData;
constructor(data: InkData) {
diff --git a/src/fields/List.ts b/src/fields/List.ts
index ec31f7dae..f6e0473ea 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,31 +1,30 @@
import { action, computed, makeObservable, observable } from 'mobx';
import { alias, list, serializable } from 'serializr';
-import { DocServer } from '../client/DocServer';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper';
-import { Field } from './Doc';
+import { Field, FieldType } from './Doc';
import { FieldTuples, Self, SelfProxy } from './DocSymbols';
import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
-import { ObjectField } from './ObjectField';
+import { ObjGetRefFields, ObjectField } from './ObjectField';
import { ProxyField } from './Proxy';
import { RefField } from './RefField';
import { listSpec } from './Schema';
import { Cast } from './Types';
import { containedFieldChangedHandler, deleteProperty, getter, setter } from './util';
-function toObjectField(field: Field) {
+function toObjectField(field: FieldType) {
return field instanceof RefField ? new ProxyField(field) : field;
}
-function toRealField(field: Field) {
+function toRealField(field: FieldType) {
return field instanceof ProxyField ? field.value : field;
}
-type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
+type StoredType<T extends FieldType> = T extends RefField ? ProxyField<T> : T;
export const ListFieldName = 'fields';
@Deserializable('list')
-class ListImpl<T extends Field> extends ObjectField {
+class ListImpl<T extends FieldType> extends ObjectField {
static listHandlers: any = {
/// Mutator methods
copyWithin() {
@@ -44,14 +43,14 @@ class ListImpl<T extends Field> extends ObjectField {
this[SelfProxy][FieldChanged]?.();
return field;
},
- push: action(function (this: ListImpl<any>, ...items: any[]) {
- items = items.map(toObjectField);
+ push: action(function (this: ListImpl<any>, ...itemsIn: any[]) {
+ const items = itemsIn.map(toObjectField);
const list = this[Self];
- const length = list.__fieldTuples.length;
+ const { length } = list.__fieldTuples;
for (let i = 0; i < items.length; i++) {
const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
+ // TODO Error checking to make sure parent doesn't already exist
if (item instanceof ObjectField) {
item[Parent] = list;
item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], i + length, item);
@@ -77,21 +76,21 @@ class ListImpl<T extends Field> extends ObjectField {
this[SelfProxy][FieldChanged]?.();
return res;
},
- splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ splice: action(function (this: any, start: number, deleteCount: number, ...itemsIn: any[]) {
this[Self].__realFields; // coerce retrieving entire array
- items = items.map(toObjectField);
+ const items = itemsIn.map(toObjectField);
const list = this[Self];
const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
for (let i = 0; i < items.length; i++) {
const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
+ // TODO Error checking to make sure parent doesn't already exist
+ // TODO Need to change indices of other fields in array
if (item instanceof ObjectField) {
item[Parent] = list;
item[FieldChanged] = containedFieldChangedHandler(this, i + start, item);
}
}
- let hintArray: { val: any; index: number }[] = [];
+ const hintArray: { val: any; index: number }[] = [];
for (let i = start; i < start + deleteCount; i++) {
hintArray.push({ val: list.__fieldTuples[i], index: i });
}
@@ -107,13 +106,13 @@ class ListImpl<T extends Field> extends ObjectField {
);
return res.map(toRealField);
}),
- unshift(...items: any[]) {
- items = items.map(toObjectField);
+ unshift(...itemsIn: any[]) {
+ const items = itemsIn.map(toObjectField);
const list = this[Self];
for (let i = 0; i < items.length; i++) {
const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
+ // TODO Error checking to make sure parent doesn't already exist
+ // TODO Need to change indices of other fields in array
if (item instanceof ObjectField) {
item[Parent] = list;
item[FieldChanged] = containedFieldChangedHandler(this, i, item);
@@ -131,9 +130,8 @@ class ListImpl<T extends Field> extends ObjectField {
includes(valueToFind: any, fromIndex: number) {
if (valueToFind instanceof RefField) {
return this[Self].__realFields.includes(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
}
+ return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
},
indexOf(valueToFind: any, fromIndex: number) {
if (valueToFind instanceof RefField) {
@@ -151,9 +149,8 @@ class ListImpl<T extends Field> extends ObjectField {
lastIndexOf(valueToFind: any, fromIndex: number) {
if (valueToFind instanceof RefField) {
return this[Self].__realFields.lastIndexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
}
+ return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
},
slice(begin: number, end: number) {
this[Self].__realFields;
@@ -244,7 +241,7 @@ class ListImpl<T extends Field> extends ObjectField {
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target[FieldTuples]) {
return {
- configurable: true, //TODO Should configurable be true?
+ configurable: true, // TODO Should configurable be true?
enumerable: true,
};
}
@@ -255,11 +252,12 @@ class ListImpl<T extends Field> extends ObjectField {
throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
},
});
- this[SelfProxy] = list as any as List<Field>; // bcz: ugh .. don't know how to convince typesecript that list is a List
+ this[SelfProxy] = list as any as List<FieldType>; // bcz: ugh .. don't know how to convince typesecript that list is a List
if (fields) {
this[SelfProxy].push(...fields);
}
- return list;
+ // eslint-disable-next-line no-constructor-return
+ return list; // need to return the proxy here, otherwise we don't get any of our list handler functions
}
[key: number]: T | (T extends RefField ? Promise<T> : never);
@@ -271,7 +269,7 @@ class ListImpl<T extends Field> extends ObjectField {
// if we find any ProxyFields that don't have a current value, then
// start the server request for all of them
if (unrequested.length) {
- const batchPromise = DocServer.GetRefFields(unrequested.map(p => p.fieldId));
+ const batchPromise = ObjGetRefFields(unrequested.map(p => p.fieldId));
// as soon as we get the fields from the server, set all the list values in one
// action to generate one React dom update.
const allSetPromise = batchPromise.then(action(pfields => unrequested.map(toReq => toReq.setValue(pfields[toReq.fieldId]))));
@@ -308,7 +306,7 @@ class ListImpl<T extends Field> extends ObjectField {
@observable
private [FieldTuples]: StoredType<T>[] = [];
private [Self] = this;
- private [SelfProxy]: List<Field>; // also used in utils.ts even though it won't be found using find all references
+ private [SelfProxy]: List<FieldType>; // also used in utils.ts even though it won't be found using find all references
[ToJavascriptString]() {
return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`;
@@ -320,10 +318,11 @@ class ListImpl<T extends Field> extends ObjectField {
return `[${(this as any).map((field: any) => Field.toString(field))}]`;
}
}
-export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
-export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
+export type List<T extends FieldType> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
+export const List: { new <T extends FieldType>(fields?: T[]): List<T> } = ListImpl as any;
ScriptingGlobals.add('List', List);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function compareLists(l1: any, l2: any) {
const L1 = Cast(l1, listSpec('string'), []);
const L2 = Cast(l2, listSpec('string'), []);
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index e1b5b036c..6c70adc1d 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -1,26 +1,35 @@
-import { RefField } from './RefField';
-import { FieldChanged, Parent, Copy, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { Field } from './Doc';
+import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
+import { RefField } from './RefField';
export abstract class ObjectField {
// prettier-ignore
public [FieldChanged]?: (diff?: { op: '$addToSet' | '$remFromSet' | '$set';
- items: Field[] | undefined;
+ items: FieldType[] | undefined;
length: number | undefined;
hint?: any }, serverOp?: any) => void;
+ // eslint-disable-next-line no-use-before-define
public [Parent]?: RefField | ObjectField;
abstract [Copy](): ObjectField;
abstract [ToJavascriptString](): string;
abstract [ToScriptString](): string;
abstract [ToString](): string;
-}
-
-export namespace ObjectField {
- export function MakeCopy<T extends ObjectField>(field: T) {
+ static MakeCopy<T extends ObjectField>(field: T) {
return field?.[Copy]();
}
}
+export type FieldType = number | string | boolean | ObjectField | RefField; // bcz: hack for now .. must match the type definition in Doc.ts .. put here to avoid import cycles
+// eslint-disable-next-line import/no-mutable-exports
+export let ObjGetRefField: (id: string, force?: boolean) => Promise<RefField | undefined>;
+// eslint-disable-next-line import/no-mutable-exports
+export let ObjGetRefFields: (ids: string[]) => Promise<{ [id: string]: RefField | undefined }>;
+
+export function SetObjGetRefField(func: (id: string, force?: boolean) => Promise<RefField | undefined>) {
+ ObjGetRefField = func;
+}
+export function SetObjGetRefFields(func: (ids: string[]) => Promise<{ [id: string]: RefField | undefined }>) {
+ ObjGetRefFields = func;
+}
ScriptingGlobals.add(ObjectField);
diff --git a/src/fields/RefField.ts b/src/fields/RefField.ts
index 01828dd14..1ce81368a 100644
--- a/src/fields/RefField.ts
+++ b/src/fields/RefField.ts
@@ -1,7 +1,7 @@
-import { serializable, primitive, alias } from 'serializr';
+import { alias, primitive, serializable } from 'serializr';
import { Utils } from '../Utils';
-import { Id, HandleUpdate, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
import { afterDocDeserialize } from '../client/util/SerializationHelper';
+import { HandleUpdate, Id, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
export type FieldId = string;
export abstract class RefField {
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index b84a91709..d75d66bf8 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,21 +1,22 @@
import { AssertionError } from 'assert';
+import * as Color from 'color';
import { docs_v1 } from 'googleapis';
import { Fragment, Mark, Node } from 'prosemirror-model';
import { sinkListItem } from 'prosemirror-schema-list';
import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
-import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
-import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { ClientUtils, DashColor } from '../ClientUtils';
+import { Utils } from '../Utils';
import { DocServer } from '../client/DocServer';
-import { Docs, DocUtils } from '../client/documents/Documents';
import { Networking } from '../client/Network';
+import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
+import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { DocUtils, Docs } from '../client/documents/Documents';
import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
import { schema } from '../client/views/nodes/formattedText/schema_rts';
-import { DashColor, Utils } from '../Utils';
import { Doc, Opt } from './Doc';
import { Id } from './FieldSymbols';
import { RichTextField } from './RichTextField';
import { Cast, StrCast } from './Types';
-import * as Color from 'color';
export namespace RichTextUtils {
const delimiter = '\n';
@@ -140,8 +141,8 @@ export namespace RichTextUtils {
inlineObjectMap.set(object.objectId!, {
title: embeddedObject.title || `Imported Image from ${document.title}`,
width,
- url: Utils.prepend(_m.client),
- agnostic: Utils.prepend(agnostic.client),
+ url: ClientUtils.prepend(_m.client),
+ agnostic: ClientUtils.prepend(agnostic.client),
});
}
}
@@ -401,7 +402,7 @@ export namespace RichTextUtils {
DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
linkDoc.link_anchor_2 = exported;
}
- url = Utils.shareUrl(exported[Id]);
+ url = ClientUtils.shareUrl(exported[Id]);
}
}
value = { url };
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index f5e64ae1f..364899dc7 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -1,5 +1,5 @@
import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from './Types';
-import { Doc, Field } from './Doc';
+import { Doc, FieldType } from './Doc';
import { ObjectField } from './ObjectField';
import { RefField } from './RefField';
import { SelfProxy } from './DocSymbols';
@@ -99,11 +99,11 @@ export function createSchema<T extends Interface>(schema: T): T & { proto: ToCon
return schema as any;
}
-export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
+export function listSpec<U extends ToConstructor<FieldType>>(type: U): ListSpec<ToType<U>> {
return { List: type as any }; //TODO Types
}
-export function defaultSpec<T extends ToConstructor<Field>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
+export function defaultSpec<T extends ToConstructor<FieldType>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
return {
type: type as any,
defaultVal,
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index fb4dc4e5b..0a8dd1d9e 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -1,9 +1,19 @@
+import { primitive, serializable } from 'serializr';
+import { ScriptingGlobals, scriptingGlobal } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, primitive } from 'serializr';
+import { Copy, FieldChanged, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString, FieldChanged, ToJavascriptString } from './FieldSymbols';
-import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView';
+
+export enum ColumnType {
+ Number,
+ String,
+ Boolean,
+ Date,
+ Image,
+ RTF,
+ Enumeration,
+ Any,
+}
export const PastelSchemaPalette = new Map<string, string>([
// ["pink1", "#FFB4E8"],
@@ -69,13 +79,14 @@ export class SchemaHeaderField extends ObjectField {
@serializable(primitive())
desc: boolean | undefined; // boolean determines sort order, undefined when no sort
+ // eslint-disable-next-line default-param-last
constructor(heading: string = '', color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) {
super();
this.heading = heading;
this.color = color;
- this.type = type ? type : 0;
- this.width = width ? width : -1;
+ this.type = type ?? 0;
+ this.width = width ?? -1;
this.desc = desc;
this.collapsed = collapsed;
}
@@ -124,6 +135,8 @@ export class SchemaHeaderField extends ObjectField {
return `SchemaHeaderField`;
}
}
+
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function schemaHeaderField(heading: string, color: string, type: number, width: number, desc?: boolean, collapsed?: boolean) {
return new SchemaHeaderField(heading, color, type, width, desc, collapsed);
});
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 8b51088b2..8a3787768 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,17 +1,16 @@
import { action, makeObservable, observable } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr';
-import { DocServer } from '../client/DocServer';
-import { CompiledScript, CompileScript, ScriptOptions, Transformer } from '../client/util/Scripting';
-import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { PropSchema, SKIP, createSimpleSchema, custom, map, object, primitive, serializable } from 'serializr';
import { numberRange } from '../Utils';
-import { Doc, Field, FieldResult, Opt } from './Doc';
+import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT';
+import { CompileScript, CompiledScript, ScriptOptions, Transformer } from '../client/util/Scripting';
+import { ScriptingGlobals, scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Deserializable, autoObject } from '../client/util/SerializationHelper';
+import { Doc, Field, FieldType, FieldResult, Opt } from './Doc';
import { Copy, FieldChanged, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
import { List } from './List';
-import { ObjectField } from './ObjectField';
+import { ObjGetRefField, ObjectField } from './ObjectField';
import { Cast, StrCast } from './Types';
-import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT';
function optional(propSchema: PropSchema) {
return custom(
@@ -44,7 +43,9 @@ const scriptSchema = createSimpleSchema({
originalScript: true,
});
-function finalizeScript(script: ScriptField) {
+// eslint-disable-next-line no-use-before-define
+function finalizeScript(scriptIn: ScriptField) {
+ const script = scriptIn;
const comp = CompileScript(script.script.originalScript, script.script.options);
if (!comp.compiled) {
throw new Error("Couldn't compile loaded script");
@@ -54,11 +55,13 @@ function finalizeScript(script: ScriptField) {
if (!compset.compiled) {
throw new Error("Couldn't compile setter script");
}
- (script as any).setterscript = compset;
+ script.setterscript = compset;
}
return comp;
}
-async function deserializeScript(script: ScriptField) {
+// eslint-disable-next-line no-use-before-define
+async function deserializeScript(scriptIn: ScriptField) {
+ const script = scriptIn;
if (script.captures) {
const captured: any = {};
(script.script.options as ScriptOptions).capturedVariables = captured;
@@ -68,13 +71,16 @@ async function deserializeScript(script: ScriptField) {
const val = capture.split(':')[1];
if (val === 'true') captured[key] = true;
else if (val === 'false') captured[key] = false;
- else if (val.startsWith('ID->')) captured[key] = await DocServer.GetRefField(val.replace('ID->', ''));
+ else if (val.startsWith('ID->')) captured[key] = await ObjGetRefField(val.replace('ID->', ''));
else if (!isNaN(Number(val))) captured[key] = Number(val);
else captured[key] = val;
})
- ).then(() => ((script as any).script = finalizeScript(script)));
+ ).then(() => {
+ script.script = finalizeScript(script);
+ });
} else {
- (script as any).script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script);
+ // eslint-disable-next-line no-use-before-define
+ script.script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script);
}
}
@@ -84,9 +90,9 @@ export class ScriptField extends ObjectField {
@serializable
readonly rawscript: string | undefined;
@serializable(object(scriptSchema))
- readonly script: CompiledScript;
+ script: CompiledScript;
@serializable(object(scriptSchema))
- readonly setterscript: CompiledScript | undefined;
+ setterscript: CompiledScript | undefined;
@serializable
@observable
_cachedResult: FieldResult = undefined;
@@ -131,6 +137,7 @@ export class ScriptField extends ObjectField {
[ToString]() {
return this.script.originalScript;
}
+ // eslint-disable-next-line default-param-last
public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) {
return CompileScript(script, {
params: {
@@ -150,20 +157,23 @@ export class ScriptField extends ObjectField {
});
}
+ // eslint-disable-next-line default-param-last
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
+ // eslint-disable-next-line default-param-last
public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
- public static CallGpt(queryText: string, setVal: (val: FieldResult) => void, target: Doc) {
+ public static CallGpt(queryTextIn: string, setVal: (val: FieldResult) => void, target: Doc) {
+ let queryText = queryTextIn;
if (typeof queryText === 'string' && setVal) {
while (queryText.match(/\(this\.[a-zA-Z_]*\)/)?.length) {
const fieldRef = queryText.split('(this.')[1].replace(/\).*/, '');
- queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as Field));
+ queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as FieldType));
}
setVal(`Chat Pending: ${queryText}`);
gptAPICall(queryText, GPTCallType.COMPLETION).then(result => {
@@ -198,24 +208,29 @@ export class ComputedField extends ScriptField {
}
_lastComputedResult: FieldResult;
- value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ??
- computedFn((doc: Doc) =>
- this.script.compiled &&
- this.script.run( {
- this: doc,
- //value: '',
- _setCacheResult_: this.setCacheResult,
- _last_: this._lastComputedResult,
- _readOnly_: true,
- },
- console.log
- ).result
- )(doc)
- ); // prettier-ignore
+ value = (doc: Doc) => {
+ this._lastComputedResult =
+ this._cachedResult ??
+ computedFn(() =>
+ this.script.compiled &&
+ this.script.run(
+ {
+ this: doc,
+ // value: '',
+ _setCacheResult_: this.setCacheResult,
+ _last_: this._lastComputedResult,
+ _readOnly_: true,
+ },
+ console.log
+ ).result
+ )(); // prettier-ignore
+ return this._lastComputedResult;
+ };
- [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this.value(doc) }; } // prettier-ignore
+ [ToValue](doc: Doc) { return ComputedField.useComputed ? { value: this.value(doc) } : undefined; } // prettier-ignore
[Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
+ // eslint-disable-next-line default-param-last
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables });
const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined;
@@ -225,7 +240,7 @@ export class ComputedField extends ScriptField {
public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) {
if (!doc[`${fieldKey}_indexed`]) {
- const flist = new List<number>(numberRange(curTimecode + 1).map(i => undefined) as any as number[]);
+ const flist = new List<number>(numberRange(curTimecode + 1).map(() => undefined) as any as number[]);
flist[curTimecode] = Cast(doc[fieldKey], 'number', null);
doc[`${fieldKey}_indexed`] = flist;
}
@@ -235,7 +250,7 @@ export class ComputedField extends ScriptField {
}
public static MakeInterpolatedString(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
if (!doc[`${fieldKey}_`]) {
- const flist = new List<string>(numberRange(curTimecode + 1).map(i => undefined) as any as string[]);
+ const flist = new List<string>(numberRange(curTimecode + 1).map(() => undefined) as any as string[]);
flist[curTimecode] = StrCast(doc[fieldKey]);
doc[`${fieldKey}_indexed`] = flist;
}
@@ -244,9 +259,9 @@ export class ComputedField extends ScriptField {
return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
}
public static MakeInterpolatedDataField(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
- if (doc[`${fieldKey}`] instanceof List) return;
+ if (doc[`${fieldKey}`] instanceof List) return undefined;
if (!doc[`${fieldKey}_indexed`]) {
- const flist = new List<Field>(numberRange(curTimecode + 1).map(i => undefined) as any as Field[]);
+ const flist = new List<FieldType>(numberRange(curTimecode + 1).map(() => undefined) as any as FieldType[]);
flist[curTimecode] = Field.Copy(doc[fieldKey]);
doc[`${fieldKey}_indexed`] = flist;
}
@@ -257,11 +272,13 @@ export class ComputedField extends ScriptField {
false,
{}
);
- return (doc[`${fieldKey}`] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined);
+ doc[fieldKey] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+ return doc[fieldKey];
}
}
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function setIndexVal(list: any[], index: number, value: any) {
while (list.length <= index) list.push(undefined);
list[index] = value;
@@ -271,6 +288,7 @@ ScriptingGlobals.add(
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function getIndexVal(list: any[], index: number, defaultVal: Opt<number> = undefined) {
return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), defaultVal);
},
@@ -279,12 +297,14 @@ ScriptingGlobals.add(
);
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function makeScript(script: string) {
return ScriptField.MakeScript(script);
},
'returns the value at a given index of a list',
'(list: any[], index: number)'
);
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function dashCallChat(setVal: (val: FieldResult) => void, target: Doc, queryText: string) {
ScriptField.CallGpt(queryText, setVal, target);
}, 'calls chat gpt for the query string and then calls setVal with the result');
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 337e8ca21..6ed94d341 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,5 +1,5 @@
import { DateField } from './DateField';
-import { Doc, Field, FieldResult, Opt } from './Doc';
+import { Doc, FieldType, FieldResult, Opt } from './Doc';
import { List } from './List';
import { ProxyField } from './Proxy';
import { RefField } from './RefField';
@@ -7,54 +7,55 @@ import { RichTextField } from './RichTextField';
import { ScriptField } from './ScriptField';
import { CsvField, ImageField, WebField } from './URLField';
+export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+
+export type DefaultFieldConstructor<T extends FieldType> = {
+ type: ToConstructor<T>;
+ defaultVal: T;
+};
+// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T extends FieldType> = { List: ToConstructor<T> };
+
+export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => any);
+
export type ToType<T extends InterfaceValue> = T extends 'string'
? string
: T extends 'number'
- ? number
- : T extends 'boolean'
- ? boolean
- : T extends ListSpec<infer U>
- ? List<U>
- : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
- T extends DefaultFieldConstructor<infer _U>
- ? never
- : T extends { new (...args: any[]): List<Field> }
- ? never
- : T extends { new (...args: any[]): infer R }
- ? R
- : T extends (doc?: Doc) => infer R
- ? R
- : never;
-
-export type ToConstructor<T extends Field> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+ ? number
+ : T extends 'boolean'
+ ? boolean
+ : T extends ListSpec<infer U>
+ ? List<U>
+ : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+ T extends DefaultFieldConstructor<infer _U>
+ ? never
+ : T extends { new (...args: any[]): List<FieldType> }
+ ? never
+ : T extends { new (...args: any[]): infer R }
+ ? R
+ : T extends (doc?: Doc) => infer R
+ ? R
+ : never;
export type ToInterface<T extends Interface> = {
[P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
};
-// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
-export type ListSpec<T extends Field> = { List: ToConstructor<T> };
-
-export type DefaultFieldConstructor<T extends Field> = {
- type: ToConstructor<T>;
- defaultVal: T;
-};
-
// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : [];
export type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
-
-export type InterfaceValue = ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field> | ((doc?: Doc) => any);
-//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
+// TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
export interface Interface {
[key: string]: InterfaceValue;
// [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
}
-export type WithoutRefField<T extends Field> = T extends RefField ? never : T;
+export type WithoutRefField<T extends FieldType> = T extends RefField ? never : T;
-export type CastCtor = ToConstructor<Field> | ListSpec<Field>;
+export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;
+
+type WithoutList<T extends FieldType> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
@@ -116,18 +117,16 @@ export function ImageCast(field: FieldResult, defaultVal: ImageField | null = nu
return Cast(field, ImageField, defaultVal);
}
-type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
-
-export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
-export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
-export function FieldValue<T extends Field>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
+export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
+export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>;
+export function FieldValue<T extends FieldType>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
return field instanceof Promise || field === undefined ? defaultValue : field;
}
export interface PromiseLike<T> {
then(callback: (field: Opt<T>) => void): void;
}
-export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
+export function PromiseValue<T extends FieldType>(field: FieldResult<T>): PromiseLike<Opt<T>> {
if (field instanceof Promise) return field as Promise<Opt<T>>;
return {
then(cb: (field: Opt<T>) => void) {
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 87334ad16..c6c51957d 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -1,18 +1,14 @@
+import { custom, serializable } from 'serializr';
+import { ClientUtils } from '../ClientUtils';
+import { scriptingGlobal } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, custom } from 'serializr';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
-import { ToScriptString, ToString, Copy, ToJavascriptString } from './FieldSymbols';
-import { scriptingGlobal } from '../client/util/ScriptingGlobals';
-import { Utils } from '../Utils';
function url() {
return custom(
- function (value: URL) {
- return value?.origin === window.location.origin ? value.pathname : value?.href;
- },
- function (jsonValue: string) {
- return new URL(jsonValue, window.location.origin);
- }
+ (value: URL) => (value?.origin === window.location.origin ? value.pathname : value?.href),
+ (jsonValue: string) => new URL(jsonValue, window.location.origin)
);
}
@@ -24,26 +20,28 @@ export abstract class URLField extends ObjectField {
constructor(url: URL);
constructor(url: URL | string) {
super();
- if (typeof url === 'string') {
- url = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin);
- }
- this.url = url;
+ this.url =
+ typeof url !== 'string'
+ ? url // it's an URL
+ : url.startsWith('http')
+ ? new URL(url)
+ : new URL(url, window.location.origin);
}
[ToScriptString]() {
- if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+ if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
return `new ${this.constructor.name}("${this.url.pathname}")`;
}
return `new ${this.constructor.name}("${this.url?.href}")`;
}
[ToJavascriptString]() {
- if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+ if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
return `new ${this.constructor.name}("${this.url.pathname}")`;
}
return `new ${this.constructor.name}("${this.url?.href}")`;
}
[ToString]() {
- if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+ if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
return this.url.pathname;
}
return this.url?.href;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ad592391e..f87c200c1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,12 +1,11 @@
-import { $mobx, action, observable, runInAction, trace, values } from 'mobx';
+import { $mobx, action, observable, runInAction, trace } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { returnZero } from '../Utils';
+import { ClientUtils, returnZero } from '../ClientUtils';
import { DocServer } from '../client/DocServer';
-import { LinkManager } from '../client/util/LinkManager';
import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
-import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
+import { Doc, DocListCast, FieldType, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, DirectLinks, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
import { FieldChanged, Id, Parent, ToValue } from './FieldSymbols';
import { List } from './List';
import { ObjectField } from './ObjectField';
@@ -16,24 +15,44 @@ import { RichTextField } from './RichTextField';
import { SchemaHeaderField } from './SchemaHeaderField';
import { ComputedField } from './ScriptField';
import { DocCast, ScriptCast, StrCast } from './Types';
-import { BaseException } from 'pdfjs-dist/types/src/shared/util';
+
+/**
+ * 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.
+ * Unset: Remove a sharing permission (eg., used )
+ */
+export enum SharingPermissions {
+ Admin = 'Admin',
+ Edit = 'Edit',
+ Augment = 'Augment',
+ View = 'View',
+ None = 'Not-Shared',
+}
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
}
-var tracing = false;
+// eslint-disable-next-line prefer-const
+let tracing = false;
export function TraceMobx() {
tracing && trace();
}
-const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+export const _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+
+const _setterImpl = action((target: any, prop: string | symbol | number, valueIn: any, receiver: any): boolean => {
if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') {
- target[prop] = value;
+ target[prop] = valueIn;
return true;
}
- value = value?.[SelfProxy] ?? value; // convert any Doc type values to Proxy's
+ let value = valueIn?.[SelfProxy] ?? valueIn; // convert any Doc type values to Proxy's
const curValue = target.__fieldTuples[prop];
if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
@@ -50,6 +69,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
throw new Error("Can't put the same object in multiple documents at the same time");
}
value[Parent] = receiver;
+ // eslint-disable-next-line no-use-before-define
value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value);
}
if (curValue instanceof ObjectField) {
@@ -59,11 +79,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value);
+ // eslint-disable-next-line no-use-before-define
const effectiveAcl = GetEffectiveAcl(target);
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
- const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
+ const sameAuthor = fromServer || receiver.author === ClientUtils.CurrentUserEmail;
const writeToDoc =
sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField);
const writeToServer =
@@ -95,7 +116,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
(!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
UndoManager.AddEvent(
{
- redo: () => (receiver[prop] = value),
+ redo: () => {
+ receiver[prop] = value;
+ },
undo: () => {
const wasUpdate = receiver[UpdatingFromServer];
const wasForce = receiver[ForceServerWrite];
@@ -131,57 +154,16 @@ export function denormalizeEmail(email: string) {
return email.replace(/__/g, '.');
}
-/**
- * Copies parent's acl fields to the child
- */
-export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) {
- [...Object.keys(parent), ...(Doc.CurrentUserEmail !== parent.author ? ['acl-Owner'] : [])]
- .filter(key => key.startsWith('acl'))
- .forEach(key => {
- // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private.
- // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key];
- const parAcl = ReverseHierarchyMap.get(StrCast(key === 'acl-Owner' ? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Edit) : parent[key]))?.acl;
- if (parAcl) {
- const sharePermission = HierarchyMapping.get(parAcl)?.name;
- sharePermission && distributeAcls(key === 'acl-Owner' ? `acl-${normalizeEmail(StrCast(parent.author))}` : key, sharePermission, child, undefined, false, layoutOnly);
- }
- });
-}
-
-/**
- * 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.
- *
- * Unset: Remove a sharing permission (eg., used )
- */
-export enum SharingPermissions {
- Admin = 'Admin',
- Edit = '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);
+// eslint-disable-next-line no-use-before-define
+const getEffectiveAclCache = computedFn((target: any, user?: string) => getEffectiveAcl(target, user), true);
/**
* Calculates the effective access right to a document for the current user.
*/
export function GetEffectiveAcl(target: any, user?: string): symbol {
if (!target) return AclPrivate;
- if (target[UpdatingFromServer] || Doc.CurrentUserEmail === 'guest') return AclAdmin;
+ if (target[UpdatingFromServer] || ClientUtils.CurrentUserEmail === 'guest') return AclAdmin;
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)
}
@@ -191,10 +173,9 @@ export function GetPropAcl(target: any, prop: string | symbol | number) {
return GetEffectiveAcl(target);
}
-let cachedGroups = observable([] as string[]);
-const getCachedGroupByNameCache = computedFn(function (name: string) {
- return cachedGroups.includes(name);
-}, true);
+const cachedGroups = observable([] as string[]);
+const getCachedGroupByNameCache = computedFn((name: string) => cachedGroups.includes(name), true);
+
export function GetCachedGroupByName(name: string) {
return getCachedGroupByNameCache(name);
}
@@ -205,10 +186,10 @@ function getEffectiveAcl(target: any, user?: string): symbol {
const targetAcls = target[DocAcl];
if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin;
- const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
+ const userChecked = user || ClientUtils.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
if (targetAcls && Object.keys(targetAcls).length) {
let effectiveAcl = AclPrivate;
- for (const [key, value] of Object.entries(targetAcls)) {
+ Object.entries(targetAcls).forEach(([key, value]) => {
// 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
@@ -217,7 +198,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
effectiveAcl = value as symbol;
}
}
- }
+ });
return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
}
@@ -236,9 +217,9 @@ function getEffectiveAcl(target: any, user?: string): symbol {
* @param allowUpgrade whether permissions can be made less restrictive
* @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well)
*/
-export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
- const selfKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
- if (!visited) visited = [] as Doc[];
+// eslint-disable-next-line default-param-last
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited: Doc[] = [], allowUpgrade?: boolean, layoutOnly = false) {
+ const selfKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail)}`;
if (!target || visited.includes(target) || key === selfKey) return;
visited.push(target);
@@ -249,23 +230,21 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) {
// propagate ACLs to links, children, and annotations
- LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false));
+ dataDoc[DirectLinks].forEach(link => distributeAcls(key, acl, link, visited, !!allowUpgrade));
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
- distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
- d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
+ distributeAcls(key, acl, d, visited, !!allowUpgrade);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
});
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
- distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
- d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
+ distributeAcls(key, acl, d, visited, !!allowUpgrade);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
});
Object.keys(target) // share expanded layout templates (eg, for presElementBox'es )
.filter(lkey => lkey.includes('layout[') && DocCast(target[lkey]))
- .map(lkey => {
- distributeAcls(key, acl, DocCast(target[lkey]), visited, allowUpgrade ? true : false);
- });
+ .forEach(lkey => distributeAcls(key, acl, DocCast(target[lkey]), visited, !!allowUpgrade));
if (GetEffectiveAcl(dataDoc) === AclAdmin) {
dataDoc[key] = acl;
@@ -285,7 +264,23 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
dataDocChanged && updateCachedAcls(dataDoc);
}
-export var _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+/**
+ * Copies parent's acl fields to the child
+ */
+export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) {
+ [...Object.keys(parent), ...(ClientUtils.CurrentUserEmail !== parent.author ? ['acl-Owner'] : [])]
+ .filter(key => key.startsWith('acl'))
+ .forEach(key => {
+ // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private.
+ // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key];
+ const parAcl = ReverseHierarchyMap.get(StrCast(key === 'acl-Owner' ? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Edit) : parent[key]))?.acl;
+ if (parAcl) {
+ const sharePermission = HierarchyMapping.get(parAcl)?.name;
+ sharePermission && distributeAcls(key === 'acl-Owner' ? `acl-${normalizeEmail(StrCast(parent.author))}` : key, sharePermission, child, undefined, false, layoutOnly);
+ }
+ });
+}
+
/**
* sets a callback function to be called whenever a value is assigned to the specified field key.
* For example, this is used to "publish" documents with titles that start with '@'
@@ -299,13 +294,13 @@ export function SetPropSetterCb(prop: string, setter: ((target: any, value: any)
//
// target should be either a Doc or ListImpl. receiver should be a Proxy<Doc> Or List.
//
-export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
- if (!in_prop) {
+export function setter(target: any, inProp: string | symbol | number, value: any, receiver: any): boolean {
+ if (!inProp) {
console.log('WARNING: trying to set an empty property. This should be fixed. ');
return false;
}
- let prop = in_prop;
- const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
+ let prop = inProp;
+ const effectiveAcl = inProp === 'constructor' || typeof inProp === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true;
// if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
@@ -319,12 +314,24 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
}
if (target.__fieldTuples[prop] instanceof ComputedField) {
if (target.__fieldTuples[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
- return ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
+ return !!ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success;
}
}
return _setter(target, prop, value, receiver);
}
+function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any {
+ const field = target.__fieldTuples[prop];
+ const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys
+ if (value) return value.value;
+ if (field === undefined && !ignoreProto && prop !== 'proto') {
+ const proto = getFieldImpl(target, 'proto', proxy, true); // TODO tfs: instead of proxy 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, prop, proxy, ignoreProto);
+ }
+ }
+ return field;
+}
export function getter(target: any, prop: string | symbol, proxy: any): any {
// prettier-ignore
switch (prop) {
@@ -336,6 +343,7 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
case $mobx: return target.__fieldTuples[prop];
case DocLayout: return target.__LAYOUT__;
case Height: case Width: if (GetEffectiveAcl(target) === AclPrivate) return returnZero;
+ // eslint-disable-next-line no-fallthrough
default :
if (typeof prop === 'symbol') return target[prop];
if (prop.startsWith('isMobX')) return target[prop];
@@ -343,23 +351,11 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
if (GetEffectiveAcl(target) === AclPrivate && prop !== 'author') return undefined;
}
- const layout_prop = prop.startsWith('_') ? prop.substring(1) : undefined;
- if (layout_prop && target.__LAYOUT__) return target.__LAYOUT__[layout_prop];
- return getFieldImpl(target, layout_prop ?? prop, proxy);
+ const layoutProp = prop.startsWith('_') ? prop.substring(1) : undefined;
+ if (layoutProp && target.__LAYOUT__) return target.__LAYOUT__[layoutProp];
+ return getFieldImpl(target, layoutProp ?? prop, proxy);
}
-function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any {
- const field = target.__fieldTuples[prop];
- const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys
- if (value) return value.value;
- if (field === undefined && !ignoreProto && prop !== 'proto') {
- const proto = getFieldImpl(target, 'proto', proxy, true); //TODO tfs: instead of proxy 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, prop, proxy, ignoreProto);
- }
- }
- return field;
-}
export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
return getFieldImpl(target, prop, target[SelfProxy], ignoreProto);
}
@@ -382,10 +378,10 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
// were replaced. Based on this specification, an Undo event is setup that will save enough information about the ObjectField to be
// able to undo and redo the partial change.
//
-export function containedFieldChangedHandler(container: List<Field> | Doc, prop: string | number, liveContainedField: ObjectField) {
+export function containedFieldChangedHandler(container: List<FieldType> | Doc, prop: string | number, liveContainedField: ObjectField) {
let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField;
- return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => {
- const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: Field) => SerializationHelper.Serialize(item)) });
+ return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => {
+ const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: FieldType) => SerializationHelper.Serialize(item)) });
// prettier-ignore
const serverOp = diff?.op === '$addToSet'
? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length }
@@ -401,8 +397,8 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
UndoManager.AddEvent(
{
redo: () => {
- //console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
- (container as any)[prop as any]?.push(...(diff.items || [])?.map((item: any) => item.value ?? item));
+ // console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
+ (container as any)[prop as any]?.push(...((diff.items || [])?.map((item: any) => item.value ?? item) ?? []));
lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
},
undo: action(() => {
@@ -416,7 +412,7 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
});
lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
}),
- prop: 'add ' + diff.items?.length + ' items to list',
+ prop: 'add ' + (diff.items?.length ?? 0) + ' items to list',
},
diff?.items
);
@@ -447,12 +443,14 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
});
lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
},
- prop: 'remove ' + diff.items?.length + ' items from list',
+ prop: 'remove ' + (diff.items?.length ?? 0) + ' items from list',
},
diff?.items
);
} else {
- const setFieldVal = (val: Field | undefined) => (container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as Field));
+ const setFieldVal = (val: FieldType | undefined) => {
+ container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as FieldType);
+ };
UndoManager.AddEvent(
{
redo: () => {