aboutsummaryrefslogtreecommitdiff
path: root/src/fields
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields')
-rw-r--r--src/fields/CursorField.ts41
-rw-r--r--src/fields/Doc.ts173
-rw-r--r--src/fields/DocSymbols.ts5
-rw-r--r--src/fields/FieldLoader.tsx5
-rw-r--r--src/fields/FieldSymbols.ts2
-rw-r--r--src/fields/IconField.ts26
-rw-r--r--src/fields/List.ts463
-rw-r--r--src/fields/ListSpec.ts0
-rw-r--r--src/fields/ObjectField.ts15
-rw-r--r--src/fields/PresField.ts6
-rw-r--r--src/fields/Proxy.ts4
-rw-r--r--src/fields/SchemaHeaderField.ts14
-rw-r--r--src/fields/Types.ts9
-rw-r--r--src/fields/documentSchemas.ts20
-rw-r--r--src/fields/util.ts329
15 files changed, 558 insertions, 554 deletions
diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts
index a8a2859d2..46f5a8e1c 100644
--- a/src/fields/CursorField.ts
+++ b/src/fields/CursorField.ts
@@ -1,44 +1,43 @@
-import { ObjectField } from "./ObjectField";
-import { observable } from "mobx";
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, createSimpleSchema, object, date } from "serializr";
-import { OnUpdate, ToScriptString, ToString, Copy } from "./FieldSymbols";
+import { ObjectField } from './ObjectField';
+import { observable } from 'mobx';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, createSimpleSchema, object, date } from 'serializr';
+import { FieldChanged, ToScriptString, ToString, Copy } from './FieldSymbols';
export type CursorPosition = {
- x: number,
- y: number
+ x: number;
+ y: number;
};
export type CursorMetadata = {
- id: string,
- identifier: string,
- timestamp: number
+ id: string;
+ identifier: string;
+ timestamp: number;
};
export type CursorData = {
- metadata: CursorMetadata,
- position: CursorPosition
+ metadata: CursorMetadata;
+ position: CursorPosition;
};
const PositionSchema = createSimpleSchema({
x: true,
- y: true
+ y: true,
});
const MetadataSchema = createSimpleSchema({
id: true,
identifier: true,
- timestamp: true
+ timestamp: true,
});
const CursorSchema = createSimpleSchema({
metadata: object(MetadataSchema),
- position: object(PositionSchema)
+ position: object(PositionSchema),
});
-@Deserializable("cursor")
+@Deserializable('cursor')
export default class CursorField extends ObjectField {
-
@serializable(object(CursorSchema))
readonly data: CursorData;
@@ -50,7 +49,7 @@ export default class CursorField extends ObjectField {
setPosition(position: CursorPosition) {
this.data.position = position;
this.data.metadata.timestamp = Date.now();
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
[Copy]() {
@@ -58,9 +57,9 @@ export default class CursorField extends ObjectField {
}
[ToScriptString]() {
- return "invalid";
+ return 'invalid';
}
[ToString]() {
- return "invalid";
+ return 'invalid';
}
-} \ No newline at end of file
+}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 92ae64567..ceacb8a08 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -20,8 +20,6 @@ import {
AclEdit,
AclPrivate,
AclReadonly,
- AclSelfEdit,
- AclUnset,
Animation,
CachedUpdates,
DirectLinks,
@@ -38,11 +36,10 @@ import {
Initializing,
Self,
SelfProxy,
- Update,
UpdatingFromServer,
Width,
} from './DocSymbols';
-import { Copy, HandleUpdate, Id, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
import { InkField, InkTool } from './InkField';
import { List, ListFieldName } from './List';
import { ObjectField } from './ObjectField';
@@ -53,7 +50,7 @@ import { listSpec } from './Schema';
import { ComputedField, ScriptField } from './ScriptField';
import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
-import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util';
+import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, containedFieldChangedHandler } from './util';
import JSZip = require('jszip');
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -126,21 +123,18 @@ export enum aclLevel {
unshared = 0,
viewable = 1,
augmentable = 2,
- selfEditable = 2.5,
editable = 3,
admin = 4,
}
// prettier-ignore
-export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermissions }> = new Map([
- [AclPrivate, { level: aclLevel.unshared, name: SharingPermissions.None }],
- [AclReadonly, { level: aclLevel.viewable, name: SharingPermissions.View }],
- [AclAugment, { level: aclLevel.augmentable, name: SharingPermissions.Augment}],
- [AclSelfEdit, { level: aclLevel.selfEditable, name: SharingPermissions.SelfEdit }],
- [AclEdit, { level: aclLevel.editable, name: SharingPermissions.Edit }],
- [AclAdmin, { level: aclLevel.admin, name: SharingPermissions.Admin }],
- [AclUnset, { level: aclLevel.unset, name: SharingPermissions.Unset }],
+export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermissions; image: string }> = new Map([
+ [AclPrivate, { level: aclLevel.unshared, name: SharingPermissions.None, image: '▲' }],
+ [AclReadonly, { level: aclLevel.viewable, name: SharingPermissions.View, image: '♦' }],
+ [AclAugment, { level: aclLevel.augmentable, name: SharingPermissions.Augment, image: '⬟' }],
+ [AclEdit, { level: aclLevel.editable, name: SharingPermissions.Edit, image: '⬢' }],
+ [AclAdmin, { level: aclLevel.admin, name: SharingPermissions.Admin, image: '⬢' }],
]);
-export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol }> = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0] }]));
+export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol; image: string }> = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }]));
// caches the document access permissions for the current user.
// this recursively updates all protos as well.
@@ -244,22 +238,6 @@ export class Doc extends RefField {
public static get MyFilesystem() {
return DocCast(Doc.UserDoc().myFilesystem);
}
- public static get MyFileOrphans() {
- return DocCast(Doc.UserDoc().myFileOrphans);
- }
- public static AddFileOrphan(doc: Doc) {
- if (
- doc &&
- Doc.MyFileOrphans instanceof Doc &&
- Doc.IsDataProto(doc) &&
- !Doc.IsSystem(doc) &&
- ![DocumentType.CONFIG, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(doc.type as any) &&
- !doc.isFolder &&
- !doc.annotationOn
- ) {
- Doc.AddDocToList(Doc.MyFileOrphans, undefined, doc);
- }
- }
public static get MyTools() {
return DocCast(Doc.UserDoc().myTools);
}
@@ -340,9 +318,10 @@ export class Doc extends RefField {
for (const key in value) {
const field = value[key];
field !== undefined && (this[FieldKeys][key] = true);
- if (!(field instanceof ObjectField)) continue;
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ if (field instanceof ObjectField) {
+ field[Parent] = this[Self];
+ field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field);
+ }
}
}
@@ -351,7 +330,7 @@ export class Doc extends RefField {
/// all of the raw acl's that have been set on this document. Use GetEffectiveAcl to determine the actual ACL of the doc for editing
@observable public [DocAcl]: { [key: string]: symbol } = {};
@observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
- @observable public [DirectLinks]: Set<Doc> = new Set();
+ @observable public [DirectLinks] = new ObservableSet<Doc>();
@observable public [Animation]: Opt<Doc>;
@observable public [Highlight]: boolean = false;
static __Anim(Doc: Doc) {
@@ -363,12 +342,13 @@ export class Doc extends RefField {
private [ForceServerWrite]: boolean = false;
public [Initializing]: boolean = false;
- private [Update] = (diff: any) => {
- (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff);
- };
-
private [Self] = this;
private [SelfProxy]: any;
+ public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
+ if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
+ DocServer.UpdateField(this[Id], serverOp);
+ }
+ };
public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
public [Width] = () => NumCast(this[SelfProxy]._width);
public [Height] = () => NumCast(this[SelfProxy]._height);
@@ -382,7 +362,8 @@ export class Doc extends RefField {
return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
}
@computed get __LAYOUT__(): Doc | undefined {
- const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
+ const self = this[SelfProxy];
+ const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null);
if (templateLayoutDoc) {
let renderFieldKey: any;
const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
@@ -391,7 +372,7 @@ export class Doc extends RefField {
} else {
return Cast(layoutField, Doc, null);
}
- return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
+ return Cast(self[renderFieldKey + '_layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
}
return undefined;
}
@@ -480,6 +461,9 @@ export namespace Doc {
// });
// }
+ export function SetContainer(doc: Doc, container: Doc) {
+ doc.embedContainer = container;
+ }
export function RunCachedUpdate(doc: Doc, field: string) {
const update = doc[CachedUpdates][field];
if (update) {
@@ -573,7 +557,7 @@ export namespace Doc {
// compare whether documents or their protos match
export function AreProtosEqual(doc?: Doc, other?: Doc) {
- return doc && other && Doc.GetProto(doc) === Doc.GetProto(other);
+ return doc && other && (doc === other || Doc.GetProto(doc) === Doc.GetProto(other));
}
// Gets the data document for the document. Note: this is mis-named -- it does not specifically
@@ -691,7 +675,7 @@ export namespace Doc {
Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
}
embedding.createdFrom = doc;
- embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = NumCast(Doc.GetProto(doc).proto_embeddingId) + 1;
+ embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1;
embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`);
embedding.author = Doc.CurrentUserEmail;
@@ -701,7 +685,8 @@ export namespace Doc {
}
export function BestEmbedding(doc: Doc) {
- const bestEmbedding = Doc.GetProto(doc) ? DocListCast(doc.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail) : doc;
+ const bestEmbedding = Doc.GetProto(doc) ? [doc, ...DocListCast(doc.proto_embeddings)].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail) : doc;
+ bestEmbedding && Doc.AddDocToList(Doc.GetProto(doc), 'protoEmbeddings', doc);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -746,7 +731,9 @@ export namespace Doc {
}
};
const docAtKey = doc[key];
- if (docAtKey instanceof Doc) {
+ if (key === 'author') {
+ assignKey(Doc.CurrentUserEmail);
+ } else if (docAtKey instanceof Doc) {
if (pruneDocs.includes(docAtKey)) {
// prune doc and do nothing
} else if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || ['embedContainer', 'annotationOn', 'proto'].includes(key) || ((key === 'link_anchor_1' || key === 'link_anchor_2') && doc.author === Doc.CurrentUserEmail))) {
@@ -776,11 +763,10 @@ export namespace Doc {
linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks));
}
});
- Doc.SetInPlace(copy, 'title', 'CLONE: ' + doc.title, true);
+ Doc.SetInPlace(copy, 'title', '>:' + doc.title, true);
copy.cloneOf = doc;
cloneMap.set(doc[Id], copy);
- Doc.AddFileOrphan(copy);
return copy;
}
export function repairClone(clone: Doc, cloneMap: Map<string, Doc>, visited: Set<Doc>) {
@@ -915,7 +901,7 @@ export namespace Doc {
// If it doesn't find the expanded layout, then it makes a delegate of the template layout and
// saves it on the data doc indexed by the template layout's id.
//
- const expandedLayoutFieldKey = templateField + '-layout[' + templateLayoutDoc[Id] + ']';
+ const expandedLayoutFieldKey = templateField + '_layout[' + templateLayoutDoc[Id] + ']';
let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
@@ -934,6 +920,7 @@ export namespace Doc {
newLayoutDoc.rootDocument = targetDoc;
const dataDoc = Doc.GetProto(targetDoc);
newLayoutDoc.resolvedDataDoc = dataDoc;
+ newLayoutDoc['acl-Guest'] = SharingPermissions.Edit;
if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
}
@@ -982,6 +969,59 @@ export namespace Doc {
return overwrite;
}
+ export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
+ if (infield instanceof List<any>) {
+ infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+ return;
+ }
+ const doc = infield as Doc;
+ if (references.has(doc)) {
+ references.add(doc);
+ return;
+ }
+ const excludeLists = doc.title === 'My Recently Closed' || doc.title === 'My Header Bar' || doc.title === 'My Dashboards';
+ if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return;
+ references.add(doc);
+ Object.keys(doc).forEach(key => {
+ if (key === 'proto') {
+ if (doc.proto instanceof Doc) {
+ Doc.FindReferences(doc.proto, references, system);
+ }
+ } else {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ if (field instanceof RefField) {
+ if (field instanceof Doc) {
+ if (key === 'myLinkDatabase') {
+ field instanceof Doc && references.add(field);
+ // skip docs that have been closed and are scheduled for garbage collection
+ } else {
+ Doc.FindReferences(field, references, system);
+ }
+ }
+ } else if (cfield instanceof ComputedField) {
+ } else if (field instanceof ObjectField) {
+ if (field instanceof Doc) {
+ Doc.FindReferences(field, references, system);
+ } else if (field instanceof List) {
+ !excludeLists && Doc.FindReferences(field, references, system);
+ } else if (field instanceof ProxyField) {
+ if (key === 'myLinkDatabase') {
+ field instanceof Doc && references.add(field);
+ // skip docs that have been closed and are scheduled for garbage collection
+ } else {
+ Doc.FindReferences(field.value, references, system);
+ }
+ } else if (field instanceof PrefetchProxy) {
+ Doc.FindReferences(field.value, references, system);
+ }
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happend...
+ }
+ }
+ });
+ }
+
export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
const copy = new Doc(copyProtoId, true);
updateCachedAcls(copy);
@@ -1019,11 +1059,9 @@ export namespace Doc {
Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy);
}
copy.embedContainer = undefined;
- Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared');
if (retitle) {
copy.title = incrementTitleCopy(StrCast(copy.title));
}
- Doc.AddFileOrphan(copy);
return copy;
}
@@ -1042,7 +1080,6 @@ export namespace Doc {
if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
- Doc.AddFileOrphan(delegate);
return delegate;
}
return undefined;
@@ -1079,7 +1116,6 @@ export namespace Doc {
const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
target.layout_fieldKey = targetKey;
applied && (Doc.GetProto(applied).type = templateDoc.type);
- Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared');
return applied;
}
return undefined;
@@ -1090,7 +1126,7 @@ export namespace Doc {
target[targetKey] = new PrefetchProxy(templateDoc);
} else {
titleTarget && (Doc.GetProto(target).title = titleTarget);
- const setDoc = [AclAdmin, AclEdit].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
+ const setDoc = [AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
setDoc[targetKey] = new PrefetchProxy(templateDoc);
}
}
@@ -1176,7 +1212,7 @@ export namespace Doc {
// the document containing the view layout information - will be the Document itself unless the Document has
// a layout field or 'layout' is given.
export function Layout(doc: Doc, layout?: Doc): Doc {
- const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}-layout[` + layout[Id] + ']'], Doc, null);
+ const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}_layout[` + layout[Id] + ']'], Doc, null);
return overrideLayout || doc[DocLayout] || doc;
}
export function SetLayout(doc: Doc, layout: Doc | string) {
@@ -1308,8 +1344,13 @@ export namespace Doc {
}
export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
- if (linkDoc.link_anchor_2 === anchorDoc || (linkDoc.link_anchor_2 as Doc).annotationOn) return '2';
- return Doc.AreProtosEqual(anchorDoc, (linkDoc.link_anchor_1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.link_anchor_1 as Doc) ? '1' : '2';
+ const linkAnchor2 = DocCast(linkDoc.link_anchor_2);
+ const linkAnchor1 = DocCast(linkDoc.link_anchor_1);
+ if (linkDoc.link_matchEmbeddings) {
+ return [linkAnchor2, linkAnchor2.annotationOn].includes(anchorDoc) ? '2' : '1';
+ }
+ if (Doc.AreProtosEqual(linkAnchor2, anchorDoc) || Doc.AreProtosEqual(linkAnchor2.annotationOn as Doc, anchorDoc)) return '2';
+ return Doc.AreProtosEqual(linkAnchor1, anchorDoc) || Doc.AreProtosEqual(linkAnchor1.annotationOn as Doc, anchorDoc) ? '1' : '2';
}
export function linkFollowUnhighlight() {
@@ -1327,9 +1368,9 @@ export namespace Doc {
UnhighlightWatchers.push(watcher);
} else watcher();
}
- export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) {
+ export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
linkFollowUnhighlight();
- (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect));
+ (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
document.addEventListener('pointerdown', linkFollowUnhighlight);
if (UnhighlightTimer) clearTimeout(UnhighlightTimer);
@@ -1344,11 +1385,11 @@ export namespace Doc {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
return doc[Highlight] || Doc.GetProto(doc)[Highlight];
}
- export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) {
+ export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) {
runInAction(() => {
highlightedDocs.add(doc);
doc[Highlight] = true;
- doc[Animation] = presEffect;
+ doc[Animation] = presentation_effect;
if (dataAndDisplayDocs) {
highlightedDocs.add(Doc.GetProto(doc));
Doc.GetProto(doc)[Highlight] = true;
@@ -1430,6 +1471,8 @@ export namespace Doc {
}
}
+ export const FilterSep = '::';
+
// filters document in a container collection:
// all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
@@ -1439,7 +1482,7 @@ export namespace Doc {
const childFilters = StrListCast(container[filterField]);
runInAction(() => {
for (let i = 0; i < childFilters.length; i++) {
- const fields = childFilters[i].split(':'); // split key:value:modifier
+ const fields = childFilters[i].split(FilterSep); // split key:value:modifier
if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
if (fields[2] === modifiers && modifiers && fields[1] === value) {
if (toggle) modifiers = 'remove';
@@ -1454,7 +1497,7 @@ export namespace Doc {
container[filterField] = undefined;
} else if (modifiers !== 'remove') {
!append && (childFilters.length = 0);
- childFilters.push(key + ':' + value + ':' + modifiers);
+ childFilters.push(key + FilterSep + value + FilterSep + modifiers);
container[filterField] = new List<string>(childFilters);
}
});
@@ -1534,13 +1577,13 @@ export namespace Doc {
// prettier-ignore
export function toIcon(doc?: Doc, isOpen?: boolean) {
- switch (StrCast(doc?.type)) {
+ switch (isOpen !== undefined ? DocumentType.COL: StrCast(doc?.type)) {
case DocumentType.IMG: return 'image';
case DocumentType.COMPARISON: return 'columns';
case DocumentType.RTF: return 'sticky-note';
case DocumentType.COL:
- const folder: IconProp = isOpen ? 'folder-open' : 'folder';
- const chevron: IconProp = isOpen ? 'chevron-down' : 'chevron-right';
+ const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : 'question';
+ const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question';
return !doc?.isFolder ? folder : chevron;
case DocumentType.WEB: return 'globe-asia';
case DocumentType.SCREENSHOT: return 'photo-video';
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 65decc147..088903082 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -1,4 +1,4 @@
-export const Update = Symbol('DocUpdate');
+export const DocUpdated = Symbol('DocUpdated');
export const Self = Symbol('DocSelf');
export const SelfProxy = Symbol('DocSelfProxy');
export const FieldKeys = Symbol('DocFieldKeys');
@@ -13,7 +13,6 @@ export const DocFields = Symbol('DocFields');
export const DocCss = Symbol('DocCss');
export const DocAcl = Symbol('DocAcl');
export const DirectLinks = Symbol('DocDirectLinks');
-export const AclUnset = Symbol('DocAclUnset');
export const AclPrivate = Symbol('DocAclOwnerOnly');
export const AclReadonly = Symbol('DocAclReadOnly');
export const AclAugment = Symbol('DocAclAugment');
@@ -24,3 +23,5 @@ export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
export const Initializing = Symbol('DocInitializing');
export const ForceServerWrite = Symbol('DocForceServerWrite');
export const CachedUpdates = Symbol('DocCachedUpdates');
+
+export const DashVersion = 'v0.5.6';
diff --git a/src/fields/FieldLoader.tsx b/src/fields/FieldLoader.tsx
index 2a7b936f7..a5a71833c 100644
--- a/src/fields/FieldLoader.tsx
+++ b/src/fields/FieldLoader.tsx
@@ -6,10 +6,9 @@ import './FieldLoader.scss';
@observer
export class FieldLoader extends React.Component {
- @observable public static ServerLoadStatus = { requested: 0, retrieved: 0 };
- public static active = false;
+ @observable public static ServerLoadStatus = { requested: 0, retrieved: 0, message: '' };
render() {
- return <div className="fieldLoader">{`Requested: ${FieldLoader.ServerLoadStatus.requested} ... ${FieldLoader.ServerLoadStatus.retrieved} `}</div>;
+ return <div className="fieldLoader">{`${FieldLoader.ServerLoadStatus.message} request: ${FieldLoader.ServerLoadStatus.requested} ... ${FieldLoader.ServerLoadStatus.retrieved} `}</div>;
}
}
diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts
index c381f14f5..0dbeb064b 100644
--- a/src/fields/FieldSymbols.ts
+++ b/src/fields/FieldSymbols.ts
@@ -1,6 +1,6 @@
export const HandleUpdate = Symbol('FieldHandleUpdate');
export const Id = Symbol('FieldId');
-export const OnUpdate = Symbol('FieldOnUpdate');
+export const FieldChanged = Symbol('FieldChanged');
export const Parent = Symbol('FieldParent');
export const Copy = Symbol('FieldCopy');
export const ToValue = Symbol('FieldToValue');
diff --git a/src/fields/IconField.ts b/src/fields/IconField.ts
deleted file mode 100644
index 76c4ddf1b..000000000
--- a/src/fields/IconField.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, primitive } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-
-@Deserializable("icon")
-export class IconField extends ObjectField {
- @serializable(primitive())
- readonly icon: string;
-
- constructor(icon: string) {
- super();
- this.icon = icon;
- }
-
- [Copy]() {
- return new IconField(this.icon);
- }
-
- [ToScriptString]() {
- return "invalid";
- }
- [ToString]() {
- return "ICONfield";
- }
-}
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 033fa569b..f3fcc87f7 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,219 +1,17 @@
-import { action, observable } from 'mobx';
+import { action, computed, observable } from 'mobx';
import { alias, list, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { afterDocDeserialize, autoObject, Deserializable } from '../client/util/SerializationHelper';
-import { FieldTuples, Self, SelfProxy, Update } from './DocSymbols';
import { Field } from './Doc';
-import { Copy, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { FieldTuples, Self, SelfProxy } from './DocSymbols';
+import { Copy, FieldChanged, Parent, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
import { ProxyField } from './Proxy';
import { RefField } from './RefField';
import { listSpec } from './Schema';
import { Cast } from './Types';
-import { deleteProperty, getter, setter, updateFunction } from './util';
-
-const listHandlers: any = {
- /// Mutator methods
- copyWithin() {
- throw new Error('copyWithin not supported yet');
- },
- fill(value: any, start?: number, end?: number) {
- if (value instanceof RefField) {
- throw new Error('fill with RefFields not supported yet');
- }
- const res = this[Self].__fieldTuples.fill(value, start, end);
- this[Update]();
- return res;
- },
- pop(): any {
- const field = toRealField(this[Self].__fieldTuples.pop());
- this[Update]();
- return field;
- },
- push: action(function (this: any, ...items: any[]) {
- items = items.map(toObjectField);
-
- const list = this[Self];
- const length = list.__fieldTuples.length;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + length, item, this);
- }
- }
- const res = list.__fieldTuples.push(...items);
- this[Update]({ op: '$addToSet', items, length: length + items.length });
- return res;
- }),
- reverse() {
- const res = this[Self].__fieldTuples.reverse();
- this[Update]();
- return res;
- },
- shift() {
- const res = toRealField(this[Self].__fieldTuples.shift());
- this[Update]();
- return res;
- },
- sort(cmpFunc: any) {
- this[Self].__realFields(); // coerce retrieving entire array
- const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
- this[Update]();
- return res;
- },
- splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
- this[Self].__realFields(); // coerce retrieving entire array
- items = items.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
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + start, item, this);
- }
- }
- let hintArray: { val: any; index: number }[] = [];
- for (let i = start; i < start + deleteCount; i++) {
- hintArray.push({ val: list.__fieldTuples[i], index: i });
- }
- const res = list.__fieldTuples.splice(start, deleteCount, ...items);
- // the hint object sends the starting index of the slice and the number
- // of elements to delete.
- this[Update](
- items.length === 0 && deleteCount
- ? { op: '$remFromSet', items: removed, hint: { start: start, deleteCount: deleteCount }, length: list.__fieldTuples.length }
- : items.length && !deleteCount && start === list.__fieldTuples.length
- ? { op: '$addToSet', items, length: list.__fieldTuples.length }
- : undefined
- );
- return res.map(toRealField);
- }),
- unshift(...items: any[]) {
- items = items.map(toObjectField);
- const list = this[Self];
- const length = list.__fieldTuples.length;
- 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
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i, item, this);
- }
- }
- const res = this[Self].__fieldTuples.unshift(...items);
- this[Update]();
- return res;
- },
- /// Accessor methods
- concat: action(function (this: any, ...items: any[]) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).concat(...items);
- }),
- includes(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().includes(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
- }
- },
- indexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().indexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
- }
- },
- join(separator: any) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).join(separator);
- },
- lastElement() {
- return this[Self].__realFields().lastElement();
- },
- lastIndexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
- }
- },
- slice(begin: number, end: number) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
- },
-
- /// Iteration methods
- entries() {
- return this[Self].__realFields().entries();
- },
- every(callback: any, thisArg: any) {
- return this[Self].__realFields().every(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- filter(callback: any, thisArg: any) {
- return this[Self].__realFields().filter(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- find(callback: any, thisArg: any) {
- return this[Self].__realFields().find(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- findIndex(callback: any, thisArg: any) {
- return this[Self].__realFields().findIndex(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- forEach(callback: any, thisArg: any) {
- return this[Self].__realFields().forEach(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- map(callback: any, thisArg: any) {
- return this[Self].__realFields().map(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- reduce(callback: any, initialValue: any) {
- return this[Self].__realFields().reduce(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- reduceRight(callback: any, initialValue: any) {
- return this[Self].__realFields().reduceRight(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- some(callback: any, thisArg: any) {
- return this[Self].__realFields().some(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- values() {
- return this[Self].__realFields().values();
- },
- [Symbol.iterator]() {
- return this[Self].__realFields().values();
- },
-};
+import { deleteProperty, getter, setter, containedFieldChangedHandler } from './util';
function toObjectField(field: Field) {
return field instanceof RefField ? new ProxyField(field) : field;
@@ -223,38 +21,221 @@ function toRealField(field: Field) {
return field instanceof ProxyField ? field.value : field;
}
-function listGetter(target: any, prop: string | symbol, receiver: any): any {
- if (listHandlers.hasOwnProperty(prop)) {
- return listHandlers[prop];
- }
- return getter(target, prop, receiver);
-}
-
-interface ListSpliceUpdate<T> {
- type: 'splice';
- index: number;
- added: T[];
- removedCount: number;
-}
-
-interface ListIndexUpdate<T> {
- type: 'update';
- index: number;
- newValue: T;
-}
-
-type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
-
type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
export const ListFieldName = 'fields';
@Deserializable('list')
class ListImpl<T extends Field> extends ObjectField {
+ static listHandlers: any = {
+ /// Mutator methods
+ copyWithin() {
+ throw new Error('copyWithin not supported yet');
+ },
+ fill(value: any, start?: number, end?: number) {
+ if (value instanceof RefField) {
+ throw new Error('fill with RefFields not supported yet');
+ }
+ const res = this[Self].__fieldTuples.fill(value, start, end);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ pop(): any {
+ const field = toRealField(this[Self].__fieldTuples.pop());
+ this[SelfProxy][FieldChanged]?.();
+ return field;
+ },
+ push: action(function (this: ListImpl<any>, ...items: any[]) {
+ items = items.map(toObjectField);
+
+ const list = this[Self];
+ const length = list.__fieldTuples.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //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);
+ }
+ }
+ const res = list.__fieldTuples.push(...items);
+ this[SelfProxy][FieldChanged]?.({ op: '$addToSet', items, length: length + items.length });
+ return res;
+ }),
+ reverse() {
+ const res = this[Self].__fieldTuples.reverse();
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ shift() {
+ const res = toRealField(this[Self].__fieldTuples.shift());
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ sort(cmpFunc: any) {
+ this[Self].__realFields; // coerce retrieving entire array
+ const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ this[Self].__realFields; // coerce retrieving entire array
+ items = items.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
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i + start, item);
+ }
+ }
+ let hintArray: { val: any; index: number }[] = [];
+ for (let i = start; i < start + deleteCount; i++) {
+ hintArray.push({ val: list.__fieldTuples[i], index: i });
+ }
+ const res = list.__fieldTuples.splice(start, deleteCount, ...items);
+ // the hint object sends the starting index of the slice and the number
+ // of elements to delete.
+ this[SelfProxy][FieldChanged]?.(
+ items.length === 0 && deleteCount
+ ? { op: '$remFromSet', items: removed, hint: { start, deleteCount }, length: list.__fieldTuples.length }
+ : items.length && !deleteCount && start === list.__fieldTuples.length
+ ? { op: '$addToSet', items, length: list.__fieldTuples.length }
+ : undefined
+ );
+ return res.map(toRealField);
+ }),
+ unshift(...items: any[]) {
+ items = items.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
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i, item);
+ }
+ }
+ const res = this[Self].__fieldTuples.unshift(...items);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ /// Accessor methods
+ concat: action(function (this: any, ...items: any[]) {
+ this[Self].__realFields;
+ return this[Self].__fieldTuples.map(toRealField).concat(...items);
+ }),
+ includes(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields.includes(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
+ }
+ },
+ indexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields.indexOf(valueToFind, fromIndex);
+ }
+ return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
+ },
+ join(separator: any) {
+ this[Self].__realFields;
+ return this[Self].__fieldTuples.map(toRealField).join(separator);
+ },
+ lastElement() {
+ return this[Self].__realFields.lastElement();
+ },
+ lastIndexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields.lastIndexOf(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
+ }
+ },
+ slice(begin: number, end: number) {
+ this[Self].__realFields;
+ return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
+ },
+
+ /// Iteration methods
+ entries() {
+ return this[Self].__realFields.entries();
+ },
+ every(callback: any, thisArg: any) {
+ return this[Self].__realFields.every(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ filter(callback: any, thisArg: any) {
+ return this[Self].__realFields.filter(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ find(callback: any, thisArg: any) {
+ return this[Self].__realFields.find(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ findIndex(callback: any, thisArg: any) {
+ return this[Self].__realFields.findIndex(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ forEach(callback: any, thisArg: any) {
+ return this[Self].__realFields.forEach(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ map(callback: any, thisArg: any) {
+ return this[Self].__realFields.map(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ reduce(callback: any, initialValue: any) {
+ return this[Self].__realFields.reduce(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ reduceRight(callback: any, initialValue: any) {
+ return this[Self].__realFields.reduceRight(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ some(callback: any, thisArg: any) {
+ return this[Self].__realFields.some(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ values() {
+ return this[Self].__realFields.values();
+ },
+ [Symbol.iterator]() {
+ return this[Self].__realFields.values();
+ },
+ };
+ static listGetter(target: any, prop: string | symbol, receiver: any): any {
+ if (ListImpl.listHandlers.hasOwnProperty(prop)) {
+ return ListImpl.listHandlers[prop];
+ }
+ return getter(target, prop, receiver);
+ }
constructor(fields?: T[]) {
super();
const list = new Proxy<this>(this, {
set: setter,
- get: listGetter,
+ get: ListImpl.listGetter,
ownKeys: target => Object.keys(target.__fieldTuples),
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target[FieldTuples]) {
@@ -270,9 +251,9 @@ class ListImpl<T extends Field> extends ObjectField {
throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
},
});
- this[SelfProxy] = list;
+ this[SelfProxy] = list as any as List<Field>; // bcz: ugh .. don't know how to convince typesecript that list is a List
if (fields) {
- (list as any).push(...fields);
+ this[SelfProxy].push(...fields);
}
return list;
}
@@ -281,7 +262,7 @@ class ListImpl<T extends Field> extends ObjectField {
// this requests all ProxyFields at the same time to avoid the overhead
// of separate network requests and separate updates to the React dom.
- private __realFields() {
+ @computed private get __realFields() {
const unrequested = this[FieldTuples].filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>);
// if we find any ProxyFields that don't have a current value, then
// start the server request for all of them
@@ -305,10 +286,10 @@ class ListImpl<T extends Field> extends ObjectField {
private set __fieldTuples(value) {
this[FieldTuples] = value;
for (const key in value) {
- const field = value[key];
- if (field instanceof ObjectField) {
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ const item = value[key];
+ if (item instanceof ObjectField) {
+ item[Parent] = this[Self];
+ item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], Number(key), item);
}
}
}
@@ -322,16 +303,8 @@ class ListImpl<T extends Field> extends ObjectField {
// @serializable(alias("fields", list(autoObject())))
@observable
private [FieldTuples]: StoredType<T>[] = [];
-
- private [Update] = (diff: any) => {
- // console.log(diff);
- const update = this[OnUpdate];
- // update && update(diff);
- update?.(diff);
- };
-
private [Self] = this;
- private [SelfProxy]: any;
+ private [SelfProxy]: List<Field>; // also used in utils.ts even though it won't be found using find all references
[ToScriptString]() {
return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
diff --git a/src/fields/ListSpec.ts b/src/fields/ListSpec.ts
deleted file mode 100644
index e69de29bb..000000000
--- a/src/fields/ListSpec.ts
+++ /dev/null
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index daa8a7777..b5bc2952a 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -1,9 +1,14 @@
-import { RefField } from "./RefField";
-import { OnUpdate, Parent, Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { ScriptingGlobals } from "../client/util/ScriptingGlobals";
+import { RefField } from './RefField';
+import { FieldChanged, Parent, Copy, ToScriptString, ToString } from './FieldSymbols';
+import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { Field } from './Doc';
export abstract class ObjectField {
- public [OnUpdate]?: (diff?: any) => void;
+ // prettier-ignore
+ public [FieldChanged]?: (diff?: { op: '$addToSet' | '$remFromSet' | '$set';
+ items: Field[] | undefined;
+ length: number | undefined;
+ hint?: any }, serverOp?: any) => void;
public [Parent]?: RefField | ObjectField;
abstract [Copy](): ObjectField;
@@ -17,4 +22,4 @@ export namespace ObjectField {
}
}
-ScriptingGlobals.add(ObjectField); \ No newline at end of file
+ScriptingGlobals.add(ObjectField);
diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts
deleted file mode 100644
index f236a04fd..000000000
--- a/src/fields/PresField.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-//insert code here
-import { ObjectField } from "./ObjectField";
-
-export abstract class PresField extends ObjectField {
-
-} \ No newline at end of file
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 55d1d9ea4..c076f5fe1 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,5 +1,5 @@
import { Deserializable } from '../client/util/SerializationHelper';
-import { FieldWaiting, Opt } from './Doc';
+import { Field, FieldWaiting, Opt } from './Doc';
import { primitive, serializable } from 'serializr';
import { observable, action, runInAction, computed } from 'mobx';
import { DocServer } from '../client/DocServer';
@@ -39,7 +39,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
[ToScriptString]() {
- return 'invalid';
+ return Field.toScriptString(this[ToValue](undefined)?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
}
[ToString]() {
return 'ProxyField';
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index 4b1855cb0..6dde2e5aa 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -1,7 +1,7 @@
import { Deserializable } from '../client/util/SerializationHelper';
import { serializable, primitive } from 'serializr';
import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString, OnUpdate } from './FieldSymbols';
+import { Copy, ToScriptString, ToString, FieldChanged } from './FieldSymbols';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView';
@@ -82,32 +82,32 @@ export class SchemaHeaderField extends ObjectField {
setHeading(heading: string) {
this.heading = heading;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setColor(color: string) {
this.color = color;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setType(type: ColumnType) {
this.type = type;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setWidth(width: number) {
this.width = width;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setDesc(desc: boolean | undefined) {
this.desc = desc;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setCollapsed(collapsed: boolean | undefined) {
this.collapsed = collapsed;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
[Copy]() {
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 251b1149d..69dbe9756 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,11 +1,10 @@
-import { Field, Opt, FieldResult, Doc } from './Doc';
+import { DateField } from './DateField';
+import { Doc, Field, FieldResult, Opt } from './Doc';
import { List } from './List';
import { RefField } from './RefField';
-import { DateField } from './DateField';
-import { ScriptField } from './ScriptField';
-import { URLField, WebField, ImageField, CsvField } from './URLField';
-import { TextField } from '@material-ui/core';
import { RichTextField } from './RichTextField';
+import { ScriptField } from './ScriptField';
+import { CsvField, ImageField, WebField } from './URLField';
export type ToType<T extends InterfaceValue> = T extends 'string'
? string
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 757d507be..e33a17416 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -72,11 +72,11 @@ export const documentSchema = createSchema({
stroke_endMarker: 'string',
stroke_dash: 'string',
textTransform: 'string',
- treeViewOpen: 'boolean', // flag denoting whether the documents sub-tree (contents) is visible or hidden
- treeViewExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree
- treeViewExpandedViewLock: 'boolean', // whether the expanded view can be changed
- treeViewOpenIsTransient: 'boolean', // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
- treeViewType: 'string', // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
+ treeView_Open: 'boolean', // flag denoting whether the documents sub-tree (contents) is visible or hidden
+ treeView_ExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree
+ treeView_ExpandedViewLock: 'boolean', // whether the expanded view can be changed
+ treeView_OpenIsTransient: 'boolean', // ignores the treeView_Open flag (for allowing a view to not be slaved to other views of the document)
+ treeView_Type: 'string', // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
// interaction and linking properties
ignoreClick: 'boolean', // whether documents ignores input clicks (but does not ignore manipulation and other events)
@@ -95,12 +95,12 @@ export const documentSchema = createSchema({
link_displayArrow: 'boolean', // toggles directed arrows
// drag drop properties
- _stayInCollection: 'boolean', // whether document can be dropped into a different collection
+ _dragOnlyWithinContainer: 'boolean', // whether document can be dropped into a different collection
dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
- dropAction: 'string', // override specifying what should happen when this document is dropped (can be "embed", "copy", "move")
- targetDropAction: 'string', // allows the target of a drop event to specify the dropAction ("embed", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
- childDropAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
- removeDropProperties: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
+ dropAction: 'string', // override specifying what should happen when something is dropped on this document (can be "embed", "copy", "move")
+ dragAction: 'string', // override specifying what should happen when this document s dragged (can be "embed", "copy", "move")
+ childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
+ dropPropertiesToRemove: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
});
export const collectionSchema = createSchema({
diff --git a/src/fields/util.ts b/src/fields/util.ts
index f365adf4b..28db77c65 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,23 +1,21 @@
import { $mobx, action, observable, runInAction, trace } from 'mobx';
import { computedFn } from 'mobx-utils';
import { DocServer } from '../client/DocServer';
-import { CollectionViewType } from '../client/documents/DocumentTypes';
import { LinkManager } from '../client/util/LinkManager';
import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
import { returnZero } from '../Utils';
-import CursorField from './CursorField';
-import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, updateCachedAcls } from './Doc';
-import { AclAdmin, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols';
-import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols';
+import { aclLevel, Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, 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';
import { PrefetchProxy, ProxyField } from './Proxy';
import { RefField } from './RefField';
import { RichTextField } from './RichTextField';
import { SchemaHeaderField } from './SchemaHeaderField';
-import { ComputedField, ScriptField } from './ScriptField';
-import { ScriptCast, StrCast } from './Types';
+import { ComputedField } from './ScriptField';
+import { DocCast, ScriptCast, StrCast } from './Types';
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -34,9 +32,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
return true;
}
- if (value !== undefined) {
- value = value[SelfProxy] || value;
- }
+ value = value?.[SelfProxy] ?? value; // 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])) {
// TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
@@ -52,11 +49,11 @@ 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;
- value[OnUpdate] = updateFunction(target, prop, value, receiver);
+ value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value);
}
if (curValue instanceof ObjectField) {
delete curValue[Parent];
- delete curValue[OnUpdate];
+ delete curValue[FieldChanged];
}
const effectiveAcl = GetEffectiveAcl(target);
@@ -64,8 +61,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
- const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly;
- const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly();
+ const writeToDoc =
+ sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField);
+ const writeToServer =
+ !DocServer.Control.isReadOnly() && //
+ (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && value instanceof RichTextField));
if (writeToDoc) {
if (value === undefined) {
@@ -79,14 +79,16 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
}
if (writeToServer) {
- if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } });
- else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } });
+ // prettier-ignore
+ if (value === undefined)
+ (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $unset: { ['fields.' + prop]: '' } });
+ else (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) :value}});
if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target);
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
}
!receiver[Initializing] &&
- !receiver.dontUndo &&
+ !StrListCast(receiver.undoIgnoreFields).includes(prop.toString()) &&
(!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
UndoManager.AddEvent(
{
@@ -106,7 +108,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
);
return true;
}
- return false;
+ return true;
});
let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl;
@@ -129,14 +131,18 @@ export function denormalizeEmail(email: string) {
/**
* Copies parent's acl fields to the child
*/
-export function inheritParentAcls(parent: Doc, child: Doc) {
- return;
- // const dataDoc = parent[DataSym];
- // for (const key of Object.keys(dataDoc)) {
- // // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
- // const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
- // key.startsWith('acl') && distributeAcls(key, permission, child);
- // }
+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);
+ }
+ });
}
/**
@@ -155,13 +161,11 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
* Unset: Remove a sharing permission (eg., used )
*/
export enum SharingPermissions {
- Unset = 'None',
Admin = 'Admin',
Edit = 'Edit',
- SelfEdit = 'Self Edit',
Augment = 'Augment',
View = 'View',
- None = 'Not Shared',
+ None = 'Not-Shared',
}
// return acl from cache or cache the acl and return.
@@ -174,11 +178,11 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
*/
export function GetEffectiveAcl(target: any, user?: string): symbol {
if (!target) return AclPrivate;
- if (target[UpdatingFromServer]) return AclAdmin;
+ if (target[UpdatingFromServer] || Doc.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)
}
-function getPropAcl(target: any, prop: string | symbol | number) {
+export function GetPropAcl(target: any, prop: string | symbol | number) {
if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable
return GetEffectiveAcl(target);
@@ -205,18 +209,13 @@ function getEffectiveAcl(target: any, user?: string): symbol {
// there are issues with storing fields with . in the name, so they are replaced with _ during creation
// as a result we need to restore them again during this comparison.
const entity = denormalizeEmail(key.substring(4)); // an individual or a group
- if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) {
- if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') {
+ if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') {
+ if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) {
effectiveAcl = value as symbol;
}
}
}
- // if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document
- //const override = targetAcls['acl-Override'];
- // if (override !== AclUnset && override !== undefined) effectiveAcl = override;
-
- // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl)
return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
}
// authored documents are private until an ACL is set.
@@ -224,73 +223,72 @@ function getEffectiveAcl(target: any, user?: string): symbol {
if (targetAuthor && targetAuthor !== userChecked) return AclPrivate;
return AclAdmin;
}
+
/**
* Recursively distributes the access right for a user across the children of a document and its annotations.
* @param key the key storing the access right (e.g. acl-groupname)
* @param acl the access right being stored (e.g. "Can Edit")
* @param target the document on which this access right is being set
- * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection)
- * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change
+ * @param visited list of Doc's already distributed to.
+ * @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, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) {
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
+ const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`;
if (!visited) visited = [] as Doc[];
- if (!target || visited.includes(target)) return;
-
- if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) {
- target[key] = acl;
- if (target !== Doc.GetProto(target)) {
- //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???)
- updateCachedAcls(target);
- }
- return;
- }
+ if (!target || visited.includes(target) || key === selfKey) return;
visited.push(target);
- let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
- // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive
- if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) {
- target[key] = acl;
- layoutDocChanged = true;
-
- if (isDashboard) {
- DocListCastAsync(target[Doc.LayoutFieldKey(target)]).then(docs => {
- docs?.forEach(d => distributeAcls(key, acl, d, inheritingFromCollection, visited));
- });
- }
- }
-
let dataDocChanged = false;
const dataDoc = target[DocData];
- if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) {
- if (GetEffectiveAcl(dataDoc) === AclAdmin) {
- dataDoc[key] = acl;
- dataDocChanged = true;
- }
+ const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0;
+ const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0;
+ if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) {
+ // propagate ACLs to links, children, and annotations
- // maps over the links of the document
- LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
+ LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false));
- // maps over the children of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
- distributeAcls(key, acl, d, inheritingFromCollection, visited);
- distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
+ distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
});
- // maps over the annotations of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
- distributeAcls(key, acl, d, inheritingFromCollection, visited);
- distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
+ distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
});
+
+ 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);
+ });
+
+ if (GetEffectiveAcl(dataDoc) === AclAdmin) {
+ dataDoc[key] = acl;
+ dataDocChanged = true;
+ }
+ }
+
+ let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
+ // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive
+ if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !Doc.GetT(target, key, 'boolean', true) || ReverseHierarchyMap.get(StrCast(target[key]))!.level > aclVal)) {
+ target[key] = acl;
+ layoutDocChanged = true;
+ if (dataDoc[key] === undefined) dataDoc[key] = acl;
}
layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made
dataDocChanged && updateCachedAcls(dataDoc);
}
+//
+// 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 {
let prop = in_prop;
- const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : getPropAcl(target, prop);
- if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
+ const effectiveAcl = in_prop === 'constructor' || typeof in_prop === '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;
@@ -357,85 +355,104 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
return true;
}
-export function updateFunction(target: any, prop: any, value: any, receiver: any) {
- let lastValue = ObjectField.MakeCopy(value);
- return (diff?: any) => {
- const op =
- diff?.op === '$addToSet'
- ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
- : diff?.op === '$remFromSet'
- ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)), hint: diff.hint } }
- : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } };
- !op.$set && ((op as any).length = diff.length);
- const prevValue = ObjectField.MakeCopy(lastValue as List<any>);
- lastValue = ObjectField.MakeCopy(value);
- const newValue = ObjectField.MakeCopy(value);
-
- if (!(value instanceof CursorField) && !value?.some?.((v: any) => v instanceof CursorField)) {
- !receiver[UpdatingFromServer] &&
+// this function creates a function that can be used to setup Undo for whenever an ObjectField changes.
+// the idea is that the Doc field setter can only setup undo at the granularity of an entire field and won't even be called if
+// just a part of a field (eg. field within an ObjectField) changes. This function returns a function that can be called
+// whenever an internal ObjectField field changes. It should be passed a 'diff' specification describing the change. Currently,
+// List's are the only true ObjectFields that can be partially modified (ignoring SchemaHeaderFields which should go away).
+// The 'diff' specification that a list can send is limited to indicating that something was added, removed, or that the list contents
+// 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) {
+ 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)) });
+ // prettier-ignore
+ const serverOp = diff?.op === '$addToSet'
+ ? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length }
+ : diff?.op === '$remFromSet'
+ ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint}, length: diff.length }
+ : { $set: { ['fields.' + prop]: liveContainedField ? SerializationHelper.Serialize(liveContainedField) : undefined } };
+
+ if (!(container instanceof Doc) || !container[UpdatingFromServer]) {
+ const prevValue = ObjectField.MakeCopy(lastValue as List<any>);
+ lastValue = ObjectField.MakeCopy(liveContainedField);
+ const newValue = ObjectField.MakeCopy(liveContainedField);
+ if (diff?.op === '$addToSet') {
+ 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));
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ undo: action(() => {
+ // console.log('undo $add: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ const ind =
+ item instanceof SchemaHeaderField //
+ ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading)
+ : (container as any)[prop as any]?.indexOf(item.value ?? item);
+ ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ }),
+ prop: 'add ' + diff.items?.length + ' items to list',
+ },
+ diff?.items
+ );
+ } else if (diff?.op === '$remFromSet') {
UndoManager.AddEvent(
- diff?.op === '$addToSet'
- ? {
- redo: () => {
- console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
- receiver[prop].push(...diff.items.map((item: any) => item.value ?? item));
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: action(() => {
- // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- if (item instanceof SchemaHeaderField) {
- const ind = receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
- ind !== -1 && receiver[prop].splice(ind, 1);
- } else {
- const ind = receiver[prop].indexOf(item.value ?? item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- }
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- prop: 'add ' + diff.items.length + ' items to list',
- }
- : diff?.op === '$remFromSet'
- ? {
- redo: action(() => {
- console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- undo: () => {
- // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- if (item instanceof SchemaHeaderField) {
- const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
- ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item);
- } else {
- const ind = (prevValue as List<any>).indexOf(item.value ?? item);
- ind !== -1 && receiver[prop].indexOf(item.value ?? item) === -1 && receiver[prop].splice(ind, 0, item);
- }
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: 'remove ' + diff.items.length + ' items from list',
- }
- : {
- redo: () => {
- console.log('redo list: ' + prop, receiver[prop]); // bcz: uncomment to log undo
- receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: () => {
- // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
- receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: 'assign list',
- }
+ {
+ redo: action(() => {
+ // console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ const ind =
+ item instanceof SchemaHeaderField //
+ ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading)
+ : (container as any)[prop as any]?.indexOf(item.value ?? item);
+ ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ }),
+ undo: () => {
+ // console.log('undo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ if (item instanceof SchemaHeaderField) {
+ const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
+ ind !== -1 && (container as any)[prop as any].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && (container as any)[prop as any].splice(ind, 0, item);
+ } else {
+ const ind = (prevValue as List<any>).indexOf(item.value ?? item);
+ ind !== -1 && (container as any)[prop as any].indexOf(item.value ?? item) === -1 && (container as any)[prop as any].splice(ind, 0, item);
+ }
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ prop: 'remove ' + diff.items?.length + ' 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));
+ UndoManager.AddEvent(
+ {
+ redo: () => {
+ // console.log('redo list: ' + prop, fieldVal()); // bcz: uncomment to log undo
+ setFieldVal(newValue instanceof ObjectField ? ObjectField.MakeCopy(newValue) : undefined);
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ undo: () => {
+ // console.log('undo list: ' + prop, fieldVal()); // bcz: uncomment to log undo
+ setFieldVal(prevValue instanceof ObjectField ? ObjectField.MakeCopy(prevValue) : undefined);
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ prop: 'set list field',
+ },
+ diff?.items
+ );
+ }
}
- target[Update](op);
+ container[FieldChanged]?.(undefined, serverOp);
};
}