diff options
Diffstat (limited to 'src/fields')
| -rw-r--r-- | src/fields/Doc.ts | 400 | ||||
| -rw-r--r-- | src/fields/DocSymbols.ts | 26 | ||||
| -rw-r--r-- | src/fields/FieldSymbols.ts | 21 | ||||
| -rw-r--r-- | src/fields/List.ts | 92 | ||||
| -rw-r--r-- | src/fields/RichTextUtils.ts | 6 | ||||
| -rw-r--r-- | src/fields/Schema.ts | 78 | ||||
| -rw-r--r-- | src/fields/Types.ts | 5 | ||||
| -rw-r--r-- | src/fields/documentSchemas.ts | 64 | ||||
| -rw-r--r-- | src/fields/util.ts | 112 |
9 files changed, 441 insertions, 363 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 7cc7e0d2a..824aa014b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -9,11 +9,40 @@ import { LinkManager } from '../client/util/LinkManager'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { SelectionManager } from '../client/util/SelectionManager'; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; -import { UndoManager } from '../client/util/UndoManager'; +import { undoable, UndoManager } from '../client/util/UndoManager'; import { decycle } from '../decycler/decycler'; +import * as JSZipUtils from '../JSZipUtils'; import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; import { DateField } from './DateField'; -import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols'; +import { + AclAdmin, + AclAugment, + AclEdit, + AclPrivate, + AclReadonly, + AclSelfEdit, + AclUnset, + Animation, + CachedUpdates, + DirectLinks, + DocAcl, + DocCss, + DocData, + DocFields, + DocLayout, + FieldKeys, + FieldTuples, + ForceServerWrite, + Height, + Highlight, + Initializing, + Self, + SelfProxy, + Update, + UpdatingFromServer, + Width, +} from './DocSymbols'; +import { Copy, HandleUpdate, Id, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols'; import { InkField, InkTool } from './InkField'; import { List, ListFieldName } from './List'; import { ObjectField } from './ObjectField'; @@ -26,7 +55,6 @@ import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Ty import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util'; import JSZip = require('jszip'); -import * as JSZipUtils from '../JSZipUtils'; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key.replace(/^_/, '')); @@ -41,7 +69,7 @@ export namespace Field { 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}"`; + return !field.includes('`') ? `\`${field}\`` : `"${field}"`; case 'number': case 'boolean': return String(field); @@ -107,6 +135,7 @@ export const AclUnset = Symbol('AclUnset'); export const AclPrivate = Symbol('AclOwnerOnly'); export const AclReadonly = Symbol('AclReadOnly'); export const AclAugment = Symbol('AclAugment'); +export const AclSelfEdit = Symbol('AclSelfEdit'); export const AclEdit = Symbol('AclEdit'); export const AclAdmin = Symbol('AclAdmin'); export const UpdatingFromServer = Symbol('UpdatingFromServer'); @@ -137,15 +166,16 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol }> // this recursively updates all protos as well. export function updateCachedAcls(doc: Doc) { if (!doc) return; + const target = (doc as any)?.__fields ?? doc; const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.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[AclSym]?.length) { - runInAction(() => (doc[AclSym] = permissions)); + if (Object.keys(permissions).length || doc[DocAcl]?.length) { + runInAction(() => (doc[DocAcl] = permissions)); } if (doc.proto instanceof Promise) { - doc.proto.then(updateCachedAcls); + doc.proto.then(proto => updateCachedAcls(DocCast(proto))); return doc.proto; } } @@ -207,6 +237,15 @@ export class Doc extends RefField { public static get MyTrails() { return DocCast(Doc.ActiveDashboard?.myTrails); } + public static IsInMyOverlay(doc: Doc) { + return DocListCast(Doc.MyOverlayDocs?.data).includes(doc); + } + public static AddToMyOverlay(doc: Doc) { + Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + } + public static RemFromMyOverlay(doc: Doc) { + Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc); + } public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); } @@ -232,9 +271,9 @@ export class Doc extends RefField { if ( doc && Doc.MyFileOrphans instanceof Doc && - Doc.IsPrototype(doc) && + Doc.IsDataProto(doc) && !Doc.IsSystem(doc) && - ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(doc.type as any) && + ![DocumentType.CONFIG, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(doc.type as any) && !doc.isFolder && !doc.annotationOn ) { @@ -282,25 +321,20 @@ export class Doc extends RefField { set: setter, get: getter, // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter - has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields, + has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fieldTuples, ownKeys: target => { - const obj = {} as any; - if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys); - runInAction(() => (obj.__LAYOUT__ = target.__LAYOUT__)); - return Object.keys(obj); + const keys = GetEffectiveAcl(target) !== AclPrivate ? Object.keys(target[FieldKeys]) : []; + return [...keys, '__LAYOUT__']; }, getOwnPropertyDescriptor: (target, prop) => { - if (prop.toString() === '__LAYOUT__') { + if (prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) { return Reflect.getOwnPropertyDescriptor(target, prop); } - if (prop in target.__fieldKeys) { - return { - configurable: true, //TODO Should configurable be true? - enumerable: true, - value: 0, //() => target.__fields[prop]) - }; - } - return Reflect.getOwnPropertyDescriptor(target, prop); + return { + configurable: true, //TODO Should configurable be true? + enumerable: true, + value: 0, //() => target.__fieldTuples[prop]) + }; }, deleteProperty: deleteProperty, defineProperty: () => { @@ -314,41 +348,35 @@ export class Doc extends RefField { return docProxy; } - proto: Opt<Doc>; [key: string]: FieldResult; @serializable(alias('fields', map(autoObject(), { afterDeserialize: afterDocDeserialize }))) - private get __fields() { - return this.___fields; + private get __fieldTuples() { + return this[FieldTuples]; } - private set __fields(value) { - this.___fields = value; + private set __fieldTuples(value) { + // called by deserializer to set all fields in one shot + this[FieldTuples] = value; for (const key in value) { const field = value[key]; - field !== undefined && (this.__fieldKeys[key] = true); + field !== undefined && (this[FieldKeys][key] = true); if (!(field instanceof ObjectField)) continue; field[Parent] = this[Self]; field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); } } - private get __fieldKeys() { - return this.___fieldKeys; - } - private set __fieldKeys(value) { - this.___fieldKeys = value; - } - @observable private ___fields: any = {}; - @observable private ___fieldKeys: any = {}; + @observable private [FieldTuples]: any = {}; + @observable private [FieldKeys]: any = {}; /// 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 [AclSym]: { [key: string]: symbol } = {}; - @observable public [CssSym]: number = 0; // incrementer denoting a change to CSS layout - @observable public [DirectLinksSym]: Set<Doc> = new Set(); - @observable public [AnimationSym]: Opt<Doc>; - @observable public [HighlightSym]: boolean = false; + @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 [Animation]: Opt<Doc>; + @observable public [Highlight]: boolean = false; static __Anim(Doc: Doc) { // for debugging to print AnimationSym field easily. - return Doc[AnimationSym]; + return Doc[Animation]; } private [UpdatingFromServer]: boolean = false; @@ -361,15 +389,15 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; - public [FieldsSym] = () => this[Self].___fields; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any); - public [WidthSym] = () => NumCast(this[SelfProxy]._width); - public [HeightSym] = () => NumCast(this[SelfProxy]._height); + 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); public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`; public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`; - public get [LayoutSym]() { + public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } - public get [DataSym]() { + public get [DocData](): Doc { const self = this[SelfProxy]; return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } @@ -377,7 +405,7 @@ export class Doc extends RefField { const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); if (templateLayoutDoc) { let renderFieldKey: any; - const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, 'layout')]; + const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')]; if (typeof layoutField === 'string') { renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1]; } else { @@ -506,14 +534,14 @@ export namespace Doc { export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> { return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>; } - export function IsPrototype(doc: Doc) { - return GetT(doc, 'isPrototype', 'boolean', true); + export function IsDataProto(doc: Doc) { + return GetT(doc, 'isDataDoc', 'boolean', true); } export function IsBaseProto(doc: Doc) { - return GetT(doc, 'baseProto', 'boolean', true); + return GetT(doc, 'isBaseProto', 'boolean', true); } export function IsSystem(doc: Doc) { - return GetT(doc, 'system', 'boolean', true); + return GetT(doc, 'isSystem', 'boolean', true); } export function IsDelegateField(doc: Doc, fieldKey: string) { return doc && Get(doc, fieldKey, true) !== undefined; @@ -522,17 +550,17 @@ export namespace Doc { if (key.startsWith('_')) key = key.substring(1); const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined; const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; - const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1; + const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1; if (onDeleg || !hasProto || (!onProto && !defaultProto)) { doc[key] = value; - } else doc.proto![key] = value; + } else hasProto[key] = value; } export function GetAllPrototypes(doc: Doc): Doc[] { const protos: Doc[] = []; let d: Opt<Doc> = doc; while (d) { protos.push(d); - d = FieldValue(d.proto); + d = DocCast(FieldValue(d.proto)); } return protos; } @@ -570,9 +598,9 @@ export namespace Doc { // Gets the data document for the document. Note: this is mis-named -- it does not specifically // return the doc's proto, but rather recursively searches through the proto inheritance chain - // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype'). + // and returns the document who's proto is undefined or whose proto is marked as a data doc ('isDataDoc'). export function GetProto(doc: Doc): Doc { - const proto = doc && (Doc.GetT(doc, 'isPrototype', 'boolean', true) ? doc : doc.proto || doc); + const proto = doc && (Doc.GetT(doc, 'isDataDoc', 'boolean', true) ? doc : DocCast(doc.proto, doc)); return proto === doc ? proto : Doc.GetProto(proto); } export function GetDataDoc(doc: Doc): Doc { @@ -586,7 +614,7 @@ export namespace Doc { let proto: Doc | undefined = doc; while (proto) { Object.keys(proto).forEach(key => results.add(key)); - proto = proto.proto; + proto = DocCast(FieldValue(proto.proto)); } return Array.from(results); @@ -663,7 +691,7 @@ export namespace Doc { const bounds = docList.reduce( (bounds, doc) => { const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + const [bptX, bptY] = [sptX + doc[Width](), sptY + doc[Height]()]; return { x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), @@ -676,25 +704,25 @@ export namespace Doc { return bounds; } - export function MakeAlias(doc: Doc, id?: string) { - const alias = !GetT(doc, 'isPrototype', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); - const layout = Doc.LayoutField(alias); - if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) { - Doc.SetLayout(alias, Doc.MakeAlias(layout)); + export function MakeEmbedding(doc: Doc, id?: string) { + const embedding = !GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); + const layout = Doc.LayoutField(embedding); + if (layout instanceof Doc && layout !== embedding && layout === Doc.Layout(embedding)) { + Doc.SetLayout(embedding, Doc.MakeEmbedding(layout)); } - alias.aliasOf = doc; - alias.aliasNumber = Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1; - alias.title = ComputedField.MakeFunction(`renameAlias(this)`); - alias.author = Doc.CurrentUserEmail; + embedding.createdFrom = doc; + embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = NumCast(Doc.GetProto(doc).proto_embeddingId) + 1; + embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`); + embedding.author = Doc.CurrentUserEmail; - Doc.AddDocToList(Doc.GetProto(doc)[DataSym], 'aliases', alias); + Doc.AddDocToList(Doc.GetProto(doc)[DocData], 'proto_embeddings', embedding); - return alias; + return embedding; } - export function BestAlias(doc: Doc) { - const bestAlias = Doc.GetProto(doc) ? DocListCast(doc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : doc; - return bestAlias ?? Doc.MakeAlias(doc); + export function BestEmbedding(doc: Doc) { + const bestEmbedding = Doc.GetProto(doc) ? DocListCast(doc.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail) : doc; + return bestEmbedding ?? Doc.MakeEmbedding(doc); } // this lists out all the tag ids that can be in a RichTextField that might contain document ids. @@ -741,7 +769,7 @@ export namespace Doc { if (docAtKey instanceof Doc) { if (pruneDocs.includes(docAtKey)) { // prune doc and do nothing - } else if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || ['context', 'annotationOn', 'proto'].includes(key) || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { + } 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))) { assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks)); } else { assignKey(docAtKey); @@ -759,10 +787,11 @@ export namespace Doc { } }) ); - Array.from(doc[DirectLinksSym]).forEach(async link => { + Array.from(doc[DirectLinks]).forEach(async link => { if ( cloneLinks || - ((cloneMap.has(DocCast(link.anchor1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor1)?.annotationOn)?.[Id])) && (cloneMap.has(DocCast(link.anchor2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor2)?.annotationOn)?.[Id]))) + ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id])) && + (cloneMap.has(DocCast(link.link_anchor_2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_2)?.annotationOn)?.[Id]))) ) { linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks)); } @@ -800,7 +829,7 @@ export namespace Doc { export async function MakeClone(doc: Doc, cloneLinks = true, cloneMap: Map<string, Doc> = new Map()) { const linkMap = new Map<string, Doc>(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.context ? [DocCast(doc.context)] : [], cloneLinks); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks); const repaired = new Set<Doc>(); const linkedDocs = Array.from(linkMap.values()); linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true)); @@ -916,7 +945,7 @@ export namespace Doc { if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { - templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype + templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype if (!targetDoc[expandedLayoutFieldKey]) { _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); setTimeout( @@ -955,7 +984,7 @@ export namespace Doc { const field = ProxyField.WithoutProxy(() => doc[key]); if (key === 'proto' && copyProto) { if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) { - overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto); + overwrite[key] = Doc.Overwrite(doc.proto, overwrite.proto); } } else { if (field instanceof RefField) { @@ -976,41 +1005,40 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc { const copy = new Doc(copyProtoId, true); updateCachedAcls(copy); - const exclude = Cast(doc.cloneFieldFilter, listSpec('string'), []); - Object.keys(doc).forEach(key => { - if (exclude.includes(key)) return; - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]); - if (key === 'proto' && copyProto) { - if (doc[key] instanceof Doc) { - copy[key] = Doc.MakeCopy(doc[key]!, false); - } - } else { - if (field instanceof RefField) { - copy[key] = field; - } else if (cfield instanceof ComputedField) { - copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript); - } else if (field instanceof ObjectField) { - copy[key] = - doc[key] instanceof Doc - ? key.includes('layout[') - ? undefined - : doc[key] // reference documents except remove documents that are expanded teplate fields - : ObjectField.MakeCopy(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happend... + const exclude = [...StrListCast(doc.cloneFieldFilter), 'dragFactory_count', 'cloneFieldFilter']; + Object.keys(doc) + .filter(key => !exclude.includes(key)) + .forEach(key => { + if (key === 'proto' && copyProto) { + if (doc.proto instanceof Doc) { + copy[key] = Doc.MakeCopy(doc.proto, false); + } } else { - copy[key] = field; + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]); + if (field instanceof RefField) { + copy[key] = field; + } else if (cfield instanceof ComputedField) { + copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript); + } else if (field instanceof ObjectField) { + copy[key] = + doc[key] instanceof Doc && key.includes('layout[') + ? undefined // remove expanded template field documents + : ObjectField.MakeCopy(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happend... + } else { + copy[key] = field; + } } - } - }); + }); if (copyProto) { - Doc.GetProto(copy).context = undefined; - Doc.GetProto(copy).aliases = new List<Doc>([copy]); + Doc.GetProto(copy).embedContainer = undefined; + Doc.GetProto(copy).proto_embeddings = new List<Doc>([copy]); } else { - Doc.AddDocToList(Doc.GetProto(copy)[DataSym], 'aliases', copy); + Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy); } - copy.context = undefined; + copy.embedContainer = undefined; Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared'); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); @@ -1031,7 +1059,7 @@ export namespace Doc { Object.keys(doc) .filter(key => key.startsWith('acl')) .forEach(key => (delegate[key] = doc[key])); - if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], 'aliases', delegate); + if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate); title && (delegate.title = title); delegate[Initializing] = false; Doc.AddFileOrphan(delegate); @@ -1049,13 +1077,13 @@ export namespace Doc { delegateProto[Initializing] = true; delegateProto.proto = doc; delegateProto.author = Doc.CurrentUserEmail; - delegateProto.isPrototype = true; + delegateProto.isDataDoc = true; title && (delegateProto.title = title); const delegate = new Doc(id, true); delegate[Initializing] = true; delegate.proto = delegateProto; delegate.author = Doc.CurrentUserEmail; - Doc.AddDocToList(delegateProto[DataSym], 'aliases', delegate); + Doc.AddDocToList(delegateProto[DocData], 'proto_embeddings', delegate); delegate[Initializing] = false; delegateProto[Initializing] = false; return delegate; @@ -1067,9 +1095,9 @@ export namespace Doc { const proto = new Doc(); proto.author = Doc.CurrentUserEmail; const target = Doc.MakeDelegate(proto); - const targetKey = StrCast(templateDoc.layoutKey, 'layout'); + const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout'); const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')'); - target.layoutKey = targetKey; + target.layout_fieldKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared'); return applied; @@ -1159,7 +1187,7 @@ export namespace Doc { } export const brushManager = new DocBrush(); - export class DocData { + export class UserDocData { @observable _user_doc: Doc = undefined!; @observable _sharing_doc: Doc = undefined!; @observable _searchQuery: string = ''; @@ -1169,37 +1197,37 @@ export namespace Doc { // 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); - return overrideLayout || doc[LayoutSym] || doc; + return overrideLayout || doc[DocLayout] || doc; } export function SetLayout(doc: Doc, layout: Doc | string) { - doc[StrCast(doc.layoutKey, 'layout')] = layout; + doc[StrCast(doc.layout_fieldKey, 'layout')] = layout; } export function LayoutField(doc: Doc) { - return doc[StrCast(doc.layoutKey, 'layout')]; + return doc[StrCast(doc.layout_fieldKey, 'layout')]; } export function LayoutFieldKey(doc: Doc): string { - return StrCast(Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layoutKey + return StrCast(Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey } export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); } export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { - return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0)); + return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? doc[Width]() : 0)); } export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { if (!doc) return 0; - const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym](); - const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0); + const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[Height]()) / doc[Width](); + const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? doc[Height]() : 0); return NumCast(doc._nativeHeight, nheight || dheight); } export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { - doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width; + doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width; } export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { - doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeHeight'] = height; + doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height; } - const manager = new DocData(); + const manager = new UserDocData(); export function SearchQuery(): string { return manager._searchQuery; } @@ -1269,8 +1297,8 @@ export namespace Doc { const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement(); if (lastBrushed) { for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) { - const a1 = Cast(link.anchor1, Doc, null); - const a2 = Cast(link.anchor2, Doc, null); + const a1 = Cast(link.link_anchor_1, Doc, null); + const a2 = Cast(link.link_anchor_2, Doc, null); if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc) || Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc)) { return DocBrushStatus.linkHighlighted; } @@ -1302,8 +1330,8 @@ export namespace Doc { } export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { - if (linkDoc.anchor2 === anchorDoc || (linkDoc.anchor2 as Doc).annotationOn) return '2'; - return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? '1' : '2'; + 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'; } export function linkFollowUnhighlight() { @@ -1336,16 +1364,16 @@ export namespace Doc { export var highlightedDocs = new ObservableSet<Doc>(); export function IsHighlighted(doc: Doc) { if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false; - return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym]; + return doc[Highlight] || Doc.GetProto(doc)[Highlight]; } export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) { runInAction(() => { highlightedDocs.add(doc); - doc[HighlightSym] = true; - doc[AnimationSym] = presEffect; + doc[Highlight] = true; + doc[Animation] = presEffect; if (dataAndDisplayDocs) { highlightedDocs.add(Doc.GetProto(doc)); - Doc.GetProto(doc)[HighlightSym] = true; + Doc.GetProto(doc)[Highlight] = true; } }); } @@ -1355,8 +1383,8 @@ export namespace Doc { (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => { highlightedDocs.delete(doc); highlightedDocs.delete(Doc.GetProto(doc)); - doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false; - doc[AnimationSym] = undefined; + doc[Highlight] = Doc.GetProto(doc)[Highlight] = false; + doc[Animation] = undefined; }); }); } @@ -1398,29 +1426,29 @@ export namespace Doc { } export function deiconifyView(doc: Doc) { - StrCast(doc.layoutKey).split('_')[1] === 'icon' && setNativeView(doc); + StrCast(doc.layout_fieldKey).split('_')[1] === 'icon' && setNativeView(doc); } export function setNativeView(doc: any) { - const prevLayout = StrCast(doc.layoutKey).split('_')[1]; + const prevLayout = StrCast(doc.layout_fieldKey).split('_')[1]; const deiconify = prevLayout === 'icon' && StrCast(doc.deiconifyLayout) ? 'layout_' + StrCast(doc.deiconifyLayout) : ''; prevLayout === 'icon' && (doc.deiconifyLayout = undefined); - doc.layoutKey = deiconify || 'layout'; + doc.layout_fieldKey = deiconify || 'layout'; } export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: number[]) { if (!container) return; - const docRangeFilters = Cast(container._docRangeFilters, listSpec('string'), []); - for (let i = 0; i < docRangeFilters.length; i += 3) { - if (docRangeFilters[i] === key) { - docRangeFilters.splice(i, 3); + const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []); + for (let i = 0; i < childFiltersByRanges.length; i += 3) { + if (childFiltersByRanges[i] === key) { + childFiltersByRanges.splice(i, 3); break; } } if (range !== undefined) { - docRangeFilters.push(key); - docRangeFilters.push(range[0].toString()); - docRangeFilters.push(range[1].toString()); - container._docRangeFilters = new List<string>(docRangeFilters); + childFiltersByRanges.push(key); + childFiltersByRanges.push(range[0].toString()); + childFiltersByRanges.push(range[1].toString()); + container._childFiltersByRanges = new List<string>(childFiltersByRanges); } } @@ -1429,35 +1457,35 @@ export namespace Doc { // based on the modifiers :"check", "x", undefined export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { if (!container) return; - const filterField = '_' + (fieldPrefix ? fieldPrefix + '-' : '') + 'docFilters'; - const docFilters = Cast(container[filterField], listSpec('string'), []); + const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters'; + const childFilters = StrListCast(container[filterField]); runInAction(() => { - for (let i = 0; i < docFilters.length; i++) { - const fields = docFilters[i].split(':'); // split key:value:modifier + for (let i = 0; i < childFilters.length; i++) { + const fields = childFilters[i].split(':'); // 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'; else return; } - docFilters.splice(i, 1); - container[filterField] = new List<string>(docFilters); + childFilters.splice(i, 1); + container[filterField] = new List<string>(childFilters); break; } } - if (!docFilters.length && modifiers === 'match' && value === undefined) { + if (!childFilters.length && modifiers === 'match' && value === undefined) { container[filterField] = undefined; } else if (modifiers !== 'remove') { - !append && (docFilters.length = 0); - docFilters.push(key + ':' + value + ':' + modifiers); - container[filterField] = new List<string>(docFilters); + !append && (childFilters.length = 0); + childFilters.push(key + ':' + value + ':' + modifiers); + container[filterField] = new List<string>(childFilters); } }); } export function readDocRangeFilter(doc: Doc, key: string) { - const docRangeFilters = Cast(doc._docRangeFilters, listSpec('string'), []); - for (let i = 0; i < docRangeFilters.length; i += 3) { - if (docRangeFilters[i] === key) { - return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])]; + const childFiltersByRanges = Cast(doc._childFiltersByRanges, listSpec('string'), []); + for (let i = 0; i < childFiltersByRanges.length; i += 3) { + if (childFiltersByRanges[i] === key) { + return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])]; } } } @@ -1469,18 +1497,18 @@ export namespace Doc { export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) { runInAction(() => { if (Doc.NativeWidth(layoutDoc) || Doc.NativeHeight(layoutDoc)) { - layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale; + layoutDoc._freeform_scale = NumCast(layoutDoc._freeform_scale, 1) * contentScale; layoutDoc._nativeWidth = undefined; layoutDoc._nativeHeight = undefined; - layoutDoc._forceReflow = undefined; + layoutDoc._layout_forceReflow = undefined; layoutDoc._nativeHeightUnfrozen = undefined; layoutDoc._nativeDimModifiable = undefined; } else { - layoutDoc._autoHeight = false; + layoutDoc._layout_autoHeight = false; if (!Doc.NativeWidth(layoutDoc)) { layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth); layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight); - layoutDoc._forceReflow = true; + layoutDoc._layout_forceReflow = true; layoutDoc._nativeHeightUnfrozen = true; layoutDoc._nativeDimModifiable = true; } @@ -1508,15 +1536,33 @@ export namespace Doc { 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)) + .map(d => DocCast(d)) + .filter(d => d); + const docs = clone ? (await Promise.all(Doc.MakeClones(list, false))).map(res => res.clone) : list; + if (ptx !== undefined && pty !== undefined && newPoint !== undefined) { + const firstx = list.length ? NumCast(list[0].x) + ptx - newPoint[0] : 0; + const firsty = list.length ? NumCast(list[0].y) + pty - newPoint[1] : 0; + docs.map(doc => { + doc.x = NumCast(doc.x) - firstx; + doc.y = NumCast(doc.y) - firsty; + }); + } + undoable(addDocument, 'Paste Doc')(docs); // embedContainer gets set in addDocument + }); + } + // 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'; @@ -1708,8 +1754,8 @@ export function IdToDoc(id: string) { ScriptingGlobals.add(function idToDoc(id: string): any { return IdToDoc(id); }); -ScriptingGlobals.add(function renameAlias(doc: any) { - return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.aliasNumber})`; +ScriptingGlobals.add(function renameEmbedding(doc: any) { + return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`; }); ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); @@ -1717,8 +1763,8 @@ ScriptingGlobals.add(function getProto(doc: any) { ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); -ScriptingGlobals.add(function getAlias(doc: any) { - return Doc.MakeAlias(doc); +ScriptingGlobals.add(function getEmbedding(doc: any) { + return Doc.MakeEmbedding(doc); }); ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); @@ -1742,6 +1788,16 @@ ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); + +export function ShowUndoStack() { + SelectionManager.DeselectAll(); + var buffer = ''; + UndoManager.undoStack.forEach((batch, i) => { + buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n'; + ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n')); + }); + alert(buffer); +} ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts new file mode 100644 index 000000000..65decc147 --- /dev/null +++ b/src/fields/DocSymbols.ts @@ -0,0 +1,26 @@ +export const Update = Symbol('DocUpdate'); +export const Self = Symbol('DocSelf'); +export const SelfProxy = Symbol('DocSelfProxy'); +export const FieldKeys = Symbol('DocFieldKeys'); +export const FieldTuples = Symbol('DocFieldTuples'); +export const Width = Symbol('DocWidth'); +export const Height = Symbol('DocHeight'); +export const Animation = Symbol('DocAnimation'); +export const Highlight = Symbol('DocHighlight'); +export const DocData = Symbol('DocData'); +export const DocLayout = Symbol('DocLayout'); +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'); +export const AclSelfEdit = Symbol('DocAclSelfEdit'); +export const AclEdit = Symbol('DocAclEdit'); +export const AclAdmin = Symbol('DocAclAdmin'); +export const UpdatingFromServer = Symbol('DocUpdatingFromServer'); +export const Initializing = Symbol('DocInitializing'); +export const ForceServerWrite = Symbol('DocForceServerWrite'); +export const CachedUpdates = Symbol('DocCachedUpdates'); diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts index e50c2856f..c381f14f5 100644 --- a/src/fields/FieldSymbols.ts +++ b/src/fields/FieldSymbols.ts @@ -1,12 +1,9 @@ -export const Update = Symbol('Update'); -export const Self = Symbol('Self'); -export const SelfProxy = Symbol('SelfProxy'); -export const HandleUpdate = Symbol('HandleUpdate'); -export const Id = Symbol('Id'); -export const OnUpdate = Symbol('OnUpdate'); -export const Parent = Symbol('Parent'); -export const Copy = Symbol('Copy'); -export const ToValue = Symbol('ToValue'); -export const ToScriptString = Symbol('ToScriptString'); -export const ToPlainText = Symbol('ToPlainText'); -export const ToString = Symbol('ToString'); +export const HandleUpdate = Symbol('FieldHandleUpdate'); +export const Id = Symbol('FieldId'); +export const OnUpdate = Symbol('FieldOnUpdate'); +export const Parent = Symbol('FieldParent'); +export const Copy = Symbol('FieldCopy'); +export const ToValue = Symbol('FieldToValue'); +export const ToScriptString = Symbol('FieldToScriptString'); +export const ToPlainText = Symbol('FieldToPlainText'); +export const ToString = Symbol('FieldToString'); diff --git a/src/fields/List.ts b/src/fields/List.ts index dfd24cf7a..033fa569b 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -3,8 +3,9 @@ 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, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols'; +import { Copy, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols'; import { ObjectField } from './ObjectField'; import { ProxyField } from './Proxy'; import { RefField } from './RefField'; @@ -21,12 +22,12 @@ const listHandlers: any = { if (value instanceof RefField) { throw new Error('fill with RefFields not supported yet'); } - const res = this[Self].__fields.fill(value, start, end); + const res = this[Self].__fieldTuples.fill(value, start, end); this[Update](); return res; }, pop(): any { - const field = toRealField(this[Self].__fields.pop()); + const field = toRealField(this[Self].__fieldTuples.pop()); this[Update](); return field; }, @@ -34,7 +35,7 @@ const listHandlers: any = { items = items.map(toObjectField); const list = this[Self]; - const length = list.__fields.length; + 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 @@ -43,23 +44,23 @@ const listHandlers: any = { item[OnUpdate] = updateFunction(list, i + length, item, this); } } - const res = list.__fields.push(...items); + const res = list.__fieldTuples.push(...items); this[Update]({ op: '$addToSet', items, length: length + items.length }); return res; }), reverse() { - const res = this[Self].__fields.reverse(); + const res = this[Self].__fieldTuples.reverse(); this[Update](); return res; }, shift() { - const res = toRealField(this[Self].__fields.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].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined); + const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined); this[Update](); return res; }, @@ -67,7 +68,7 @@ const listHandlers: any = { this[Self].__realFields(); // coerce retrieving entire array items = items.map(toObjectField); const list = this[Self]; - const removed = list.__fields.filter((item: any, i: number) => i >= start && i < start + deleteCount); + 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 @@ -77,18 +78,18 @@ const listHandlers: any = { 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.__fields[i], index : i}); + 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.__fields.splice(start, deleteCount, ...items); + 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. + // of elements to delete. this[Update]( items.length === 0 && deleteCount - ? { op: '$remFromSet', items: removed, hint : { start : start, deleteCount : deleteCount }, length: list.__fields.length } - : items.length && !deleteCount && start === list.__fields.length - ? { op: '$addToSet', items, length: list.__fields.length } + ? { 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); @@ -96,7 +97,7 @@ const listHandlers: any = { unshift(...items: any[]) { items = items.map(toObjectField); const list = this[Self]; - const length = list.__fields.length; + 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 @@ -106,32 +107,32 @@ const listHandlers: any = { item[OnUpdate] = updateFunction(list, i, item, this); } } - const res = this[Self].__fields.unshift(...items); + 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].__fields.map(toRealField).concat(...items); + 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].__fields.includes(valueToFind, fromIndex); + 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].__fields.indexOf(valueToFind, fromIndex); + return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex); } }, join(separator: any) { this[Self].__realFields(); - return this[Self].__fields.map(toRealField).join(separator); + return this[Self].__fieldTuples.map(toRealField).join(separator); }, lastElement() { return this[Self].__realFields().lastElement(); @@ -140,12 +141,12 @@ const listHandlers: any = { if (valueToFind instanceof RefField) { return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex); } else { - return this[Self].__fields.lastIndexOf(valueToFind, fromIndex); + return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex); } }, slice(begin: number, end: number) { this[Self].__realFields(); - return this[Self].__fields.slice(begin, end).map(toRealField); + return this[Self].__fieldTuples.slice(begin, end).map(toRealField); }, /// Iteration methods @@ -156,55 +157,55 @@ const listHandlers: 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].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + // 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].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + // 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].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + // 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].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + // 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].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + // 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].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + // 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].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + // 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].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + // 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].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); }, values() { return this[Self].__realFields().values(); @@ -246,7 +247,7 @@ type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>; type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T; -export const ListFieldName="fields"; +export const ListFieldName = 'fields'; @Deserializable('list') class ListImpl<T extends Field> extends ObjectField { constructor(fields?: T[]) { @@ -254,9 +255,9 @@ class ListImpl<T extends Field> extends ObjectField { const list = new Proxy<this>(this, { set: setter, get: listGetter, - ownKeys: target => Object.keys(target.__fields), + ownKeys: target => Object.keys(target.__fieldTuples), getOwnPropertyDescriptor: (target, prop) => { - if (prop in target.__fields) { + if (prop in target[FieldTuples]) { return { configurable: true, //TODO Should configurable be true? enumerable: true, @@ -281,7 +282,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() { - const unrequested = this.__fields.filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>); + 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 if (unrequested.length) { @@ -293,17 +294,16 @@ class ListImpl<T extends Field> extends ObjectField { // will await the batch request and return the requested field value. unrequested.forEach(p => p.setExternalValuePromise(allSetPromise)); } - return this.__fields.map(toRealField); + return this[FieldTuples].map(toRealField); } - public static FieldDataName = 'fields'; @serializable(alias(ListFieldName, list(autoObject(), { afterDeserialize: afterDocDeserialize }))) - private get __fields() { - return this.___fields; + private get __fieldTuples() { + return this[FieldTuples]; } - private set __fields(value) { - this.___fields = value; + private set __fieldTuples(value) { + this[FieldTuples] = value; for (const key in value) { const field = value[key]; if (field instanceof ObjectField) { @@ -314,14 +314,14 @@ class ListImpl<T extends Field> extends ObjectField { } [Copy]() { - const copiedData = this[Self].__fields.map(f => (f instanceof ObjectField ? f[Copy]() : f)); + const copiedData = this[Self].__fieldTuples.map(f => (f instanceof ObjectField ? f[Copy]() : f)); const deepCopy = new ListImpl<T>(copiedData as any); return deepCopy; } // @serializable(alias("fields", list(autoObject()))) @observable - private ___fields: StoredType<T>[] = []; + private [FieldTuples]: StoredType<T>[] = []; private [Update] = (diff: any) => { // console.log(diff); diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index 239b59e83..24cd078f2 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -395,11 +395,11 @@ export namespace RichTextUtils { if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); if (linkDoc instanceof Doc) { - let exported = (await Cast(linkDoc.anchor2, Doc))!; + let exported = (await Cast(linkDoc.link_anchor_2, Doc))!; if (!exported.customLayout) { - exported = Doc.MakeAlias(exported); + exported = Doc.MakeEmbedding(exported); DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); - linkDoc.anchor2 = exported; + linkDoc.link_anchor_2 = exported; } url = Utils.shareUrl(exported[Id]); } diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts index 7ad376a28..f035eeb0d 100644 --- a/src/fields/Schema.ts +++ b/src/fields/Schema.ts @@ -1,13 +1,13 @@ -import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from "./Types"; -import { Doc, Field } from "./Doc"; -import { ObjectField } from "./ObjectField"; -import { RefField } from "./RefField"; -import { SelfProxy } from "./FieldSymbols"; -import { List } from "./List"; +import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from './Types'; +import { Doc, Field } from './Doc'; +import { ObjectField } from './ObjectField'; +import { RefField } from './RefField'; +import { SelfProxy } from './DocSymbols'; +import { List } from './List'; type AllToInterface<T extends Interface[]> = { - 1: ToInterface<Head<T>> & AllToInterface<Tail<T>>, - 0: ToInterface<Head<T>> + 1: ToInterface<Head<T>> & AllToInterface<Tail<T>>; + 0: ToInterface<Head<T>>; }[HasTail<T> extends true ? 1 : 0]; export const emptySchema = createSchema({}); @@ -28,36 +28,40 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu schema[key] = s[key]; } } - const proto = new Proxy({}, { - get(target: any, prop, receiver) { - const field = receiver.doc?.[prop]; - if (prop in schema) { - const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should? - if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec - return Cast(field, desc.type, desc.defaultVal); - } - if (typeof desc === "function" && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) { - const doc = Cast(field, Doc); - if (doc === undefined) { - return undefined; + const proto = new Proxy( + {}, + { + get(target: any, prop, receiver) { + const field = receiver.doc?.[prop]; + if (prop in schema) { + const desc = prop === 'proto' ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should? + if (typeof desc === 'object' && 'defaultVal' in desc && 'type' in desc) { + //defaultSpec + return Cast(field, desc.type, desc.defaultVal); } - if (doc instanceof Doc) { - return desc(doc); + if (typeof desc === 'function' && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) { + const doc = Cast(field, Doc); + if (doc === undefined) { + return undefined; + } + if (doc instanceof Doc) { + return desc(doc); + } + return doc.then(doc => doc && desc(doc)); } - return doc.then(doc => doc && desc(doc)); + return Cast(field, desc); } - return Cast(field, desc); - } - return field; - }, - set(target: any, prop, value, receiver) { - receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in acls - return true; + return field; + }, + set(target: any, prop, value, receiver) { + receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in acls + return true; + }, } - }); + ); // !(doc instanceof Doc) && (throw new Error("Currently wrapping a schema in another schema isn't supported")); const fn = (doc: Doc) => Object.create(proto, { doc: { value: doc[SelfProxy], writable: false } }); - return ((doc?: Doc | Doc[]) => (doc instanceof List ? doc : undefined)?.map?.(fn) ?? fn((doc as Doc) ?? new Doc)) as InterfaceFunc<T>; + return ((doc?: Doc | Doc[]) => (doc instanceof List ? doc : undefined)?.map?.(fn) ?? fn((doc as Doc) ?? new Doc())) as InterfaceFunc<T>; } export type makeStrictInterface<T extends Interface> = Partial<ToInterface<T>>; @@ -75,8 +79,8 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) this.__doc[key] = value; return; } - throw new TypeError("Expected type " + type); - } + throw new TypeError('Expected type ' + type); + }, }); } return function (doc: any) { @@ -95,12 +99,12 @@ export function createSchema<T extends Interface>(schema: T): T & { proto: ToCon } export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> { - return { List: type as any };//TODO Types + return { List: type as any }; //TODO Types } export function defaultSpec<T extends ToConstructor<Field>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> { return { type: type as any, - defaultVal + defaultVal, }; -}
\ No newline at end of file +} diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 4cf286a32..251b1149d 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -3,7 +3,7 @@ import { List } from './List'; import { RefField } from './RefField'; import { DateField } from './DateField'; import { ScriptField } from './ScriptField'; -import { URLField, WebField, ImageField } from './URLField'; +import { URLField, WebField, ImageField, CsvField } from './URLField'; import { TextField } from '@material-ui/core'; import { RichTextField } from './RichTextField'; @@ -104,6 +104,9 @@ export function RTFCast(field: FieldResult) { export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) { return Cast(field, ScriptField, defaultVal); } +export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) { + return Cast(field, CsvField, defaultVal); +} export function WebCast(field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index b7fd06973..757d507be 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -9,15 +9,15 @@ export const documentSchema = createSchema({ type: 'string', // enumerated type of document -- should be template-specific (ie, start with an '_') title: 'string', // document title (can be on either data document or layout) isTemplateForField: 'string', // if specified, it indicates the document is a template that renders the specified field - creationDate: DateField, // when the document was created + author_date: DateField, // when the document was created links: listSpec(Doc), // computed (readonly) list of links associated with this document // "Location" properties in a very general sense - _curPage: 'number', // current page of a page based document + _layout_curPage: 'number', // current page of a page based document _currentFrame: 'number', // current frame of a frame based collection (e.g., a progressive slide) lastFrame: 'number', // last frame of a frame based collection (e.g., a progressive slide) activeFrame: 'number', // the active frame of a frame based animated document - _currentTimecode: 'number', // current play back time of a temporal document (video / audio) + _layout_currentTimecode: 'number', // current play back time of a temporal document (video / audio) _timecodeToShow: 'number', // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) _timecodeToHIde: 'number', // the time that a document should be hidden markers: listSpec(Doc), // list of markers for audio / video @@ -25,52 +25,52 @@ export const documentSchema = createSchema({ y: 'number', // y coordinate when in a freeform view z: 'number', // z "coordinate" - non-zero specifies the overlay layer of a freeformview zIndex: 'number', // zIndex of a document in a freeform view - _scrollTop: 'number', // scroll position of a scrollable document (pdf, text, web) + _layout_scrollTop: 'number', // scroll position of a scrollable document (pdf, text, web) lat: 'number', lng: 'number', // appearance properties on the layout '_backgroundGrid-spacing': 'number', // the size of the grid for collection views - _autoHeight: 'boolean', // whether the height of the document should be computed automatically based on its contents + _layout_autoHeight: 'boolean', // whether the height of the document should be computed automatically based on its contents _nativeWidth: 'number', // native width of document which determines how much document contents are scaled when the document's width is set _nativeHeight: 'number', // " _width: 'number', // width of document in its container's coordinate system _height: 'number', // " - _xPadding: 'number', // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set - _yPadding: 'number', // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set + _xPadding: 'number', // pixels of padding on left/right of collectionfreeformview contents when freeform_fitContentsToBox is set + _yPadding: 'number', // pixels of padding on top/bottom of collectionfreeformview contents when freeform_fitContentsToBox is set _xMargin: 'number', // margin added on left/right of most documents to add separation from their container _yMargin: 'number', // margin added on top/bottom of most documents to add separation from their container _overflow: 'string', // sets overflow behvavior for CollectionFreeForm views - _showCaption: 'string', // whether editable caption text is overlayed at the bottom of the document - _showTitle: 'string', // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover + _layout_showCaption: 'string', // whether editable caption text is overlayed at the bottom of the document + _layout_showTitle: 'string', // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover _pivotField: 'string', // specifies which field key should be used as the timeline/pivot axis _columnsFill: 'boolean', // whether documents in a stacking view column should be sized to fill the column _columnsSort: 'string', // how a document should be sorted "ascending", "descending", undefined (none) _columnsHideIfEmpty: 'boolean', // whether empty stacking view column headings should be hidden - _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry - _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views - _fontSize: 'string', - _fontFamily: 'string', - _sidebarWidthPercent: 'string', // percent of text window width taken up by sidebar + // _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry + // _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views + _text_fontSize: 'string', + _text_fontFamily: 'string', + _layout_sidebarWidthPercent: 'string', // percent of text window width taken up by sidebar // appearance properties on the data document backgroundColor: 'string', // background color of document - borderRounding: 'string', // border radius rounding of document - boxShadow: 'string', // the amount of shadow around the perimeter of a document + layout_borderRounding: 'string', // border radius rounding of document + layout_boxShadow: 'string', // the amount of shadow around the perimeter of a document color: 'string', // foreground color of document - fitContentsToBox: 'boolean', // whether freeform view contents should be zoomed/panned to fill the area of the document view box + freeform_fitContentsToBox: 'boolean', // whether freeform view contents should be zoomed/panned to fill the area of the document view box fontSize: 'string', hidden: 'boolean', // whether a document should not be displayed - isInkMask: 'boolean', // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through) - layout: 'string', // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below - layoutKey: 'string', // holds the field key for the field that actually holds the current lyoat + stroke_isInkMask: 'boolean', // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through) + layout: 'string', // this is the native layout string for the document. templates can be added using other fields and setting layout_fieldKey below + layout_fieldKey: 'string', // holds the field key for the field that actually holds the current lyoat letterSpacing: 'string', opacity: 'number', // opacity of document - strokeWidth: 'number', - strokeBezier: 'number', - strokeStartMarker: 'string', - strokeEndMarker: 'string', - strokeDash: 'string', + stroke_width: 'number', + stroke_bezier: 'number', + stroke_startMarker: 'string', + 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 @@ -86,21 +86,21 @@ export const documentSchema = createSchema({ onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, lightbox, default, ) hideLinkButton: 'boolean', // whether the blue link counter button should be hidden - hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden - linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints. + layout_hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden + link_displayLine: 'boolean', // whether a link connection should be shown between link anchor endpoints. isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations layers: listSpec('string'), // which layers the document is part of _lockedPosition: 'boolean', // whether the document can be moved (dragged) _lockedTransform: 'boolean', // whether a freeformview can pan/zoom - linkDisplayArrow: 'boolean', // toggles directed arrows + link_displayArrow: 'boolean', // toggles directed arrows // drag drop properties _stayInCollection: '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 "alias", "copy", "move") - targetDropAction: 'string', // allows the target of a drop event to specify the dropAction ("alias", "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 "alias" or "copy") - removeDropProperties: listSpec('string'), // properties that should be removed from the alias/copy/etc of this document when it is dropped + 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 }); export const collectionSchema = createSchema({ diff --git a/src/fields/util.ts b/src/fields/util.ts index 5e925cd7e..6058a5a5f 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -9,11 +9,10 @@ import { returnZero } from '../Utils'; import CursorField from './CursorField'; import { AclAdmin, - AclAugment, AclEdit, aclLevel, AclPrivate, - AclReadonly, + AclSelfEdit, AclSym, DataSym, Doc, @@ -35,7 +34,7 @@ import { ObjectField } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; import { RefField } from './RefField'; import { SchemaHeaderField } from './SchemaHeaderField'; -import { ComputedField } from './ScriptField'; +import { ComputedField, ScriptField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; import { convertCompilerOptionsFromJson } from 'typescript'; @@ -57,7 +56,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (value !== undefined) { value = value[SelfProxy] || value; } - const curValue = target.__fields[prop]; + 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 // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way @@ -66,6 +65,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (value instanceof RefField) { value = new ProxyField(value); } + if (value instanceof ObjectField) { if (value[Parent] && value[Parent] !== receiver && !(value instanceof PrefetchProxy)) { throw new Error("Can't put the same object in multiple documents at the same time"); @@ -88,11 +88,13 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (writeToDoc) { if (value === undefined) { - target.__fieldKeys && delete target.__fieldKeys[prop]; - delete target.__fields[prop]; + target[FieldKeys] && delete target[FieldKeys][prop]; // Lists don't have a FieldKeys field + delete target.__fieldTuples[prop]; } else { - target.__fieldKeys && (target.__fieldKeys[prop] = true); - target.__fields[prop] = value; + // bcz: uncomment to see if server is being updated + // console.log(prop + ' = ' + value + '(' + curValue + ')'); + target[FieldKeys] && (target[FieldKeys][prop] = true); // Lists don't have a FieldKeys field + target.__fieldTuples[prop] = value; } if (writeToServer) { @@ -103,20 +105,24 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } !receiver[Initializing] && + !receiver.dontUndo && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && - UndoManager.AddEvent({ - redo: () => (receiver[prop] = value), - undo: () => { - const wasUpdate = receiver[UpdatingFromServer]; - const wasForce = receiver[ForceServerWrite]; - receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet - receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. - receiver[prop] = curValue; - receiver[ForceServerWrite] = wasForce; - receiver[UpdatingFromServer] = wasUpdate; + UndoManager.AddEvent( + { + redo: () => (receiver[prop] = value), + undo: () => { + const wasUpdate = receiver[UpdatingFromServer]; + const wasForce = receiver[ForceServerWrite]; + receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet + receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. + receiver[prop] = curValue; + receiver[ForceServerWrite] = wasForce; + receiver[UpdatingFromServer] = wasUpdate; + }, + prop: prop?.toString(), }, - prop: prop?.toString(), - }); + value + ); return true; } return false; @@ -143,13 +149,13 @@ 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); } - return; } /** @@ -209,24 +215,8 @@ export function SetCachedGroups(groups: string[]) { } function getEffectiveAcl(target: any, user?: string): symbol { const targetAcls = target[AclSym]; - if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName(SharingPermissions.Admin)) return AclAdmin; - if (target['acl-' + normalizeEmail(Doc.CurrentUserEmail)]) { - if (target['acl-' + normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.Admin) { - return AclAdmin; - } - if (target['acl-' + normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.Edit) { - return AclEdit; - } - if (target['acl-' + normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.Augment) { - return AclAugment; - } - if (target['acl-' + normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.View) { - return AclReadonly; - } - if (target['acl-' + normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.None) { - return AclPrivate; - } - } + 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 if (targetAcls && Object.keys(targetAcls).length) { let effectiveAcl = AclPrivate; @@ -248,7 +238,7 @@ function getEffectiveAcl(target: any, user?: string): symbol { return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl; } // authored documents are private until an ACL is set. - const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author + const targetAuthor = target.__fieldTuples?.author || target.author; // target may be a Doc of Proxy, so check __fieldTuples.author and .author if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; return AclAdmin; let acl = AclPrivate; @@ -272,7 +262,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc if (!visited) visited = [] as Doc[]; if (!target || visited.includes(target)) return; - if ((target._viewType === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { + if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { target[key] = acl; Doc.GetProto(target)[key] = acl; if (target !== Doc.GetProto(target)) { @@ -296,7 +286,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc } let dataDocChanged = false; - const dataDoc = target[DataSym]; + const dataDoc = target[DocData]; if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { if (GetEffectiveAcl(dataDoc) === AclAdmin) { dataDoc[key] = acl; @@ -309,13 +299,13 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc // maps over the children of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { distributeAcls(key, acl, d, inheritingFromCollection, visited); - distributeAcls(key, acl, d[DataSym], inheritingFromCollection, visited); + distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); }); // maps over the annotations of the document - DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '-annotations']).forEach(d => { + DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { distributeAcls(key, acl, d, inheritingFromCollection, visited); - distributeAcls(key, acl, d[DataSym], inheritingFromCollection, visited); + distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); }); } @@ -330,16 +320,16 @@ export function setter(target: any, in_prop: string | symbol | number, value: an // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; - if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) { + if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_')) { if (!prop.startsWith('__')) prop = prop.substring(1); if (target.__LAYOUT__) { target.__LAYOUT__[prop] = value; return true; } } - if (target.__fields[prop] instanceof ComputedField) { - if (target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) { - return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false; + 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 _setter(target, prop, value, receiver); @@ -349,14 +339,13 @@ export function getter(target: any, prop: string | symbol, proxy: any): any { // prettier-ignore switch (prop) { case 'then' : return undefined; - case '__fields' : case '__id': - case 'constructor': case 'toString': case 'valueOf': - case 'factory': case 'serializeInfo': + case 'constructor': case 'toString': case 'valueOf': + case 'serializeInfo': case 'factory': return target[prop]; - case AclSym : return target[AclSym]; - case $mobx: return target.__fields[prop]; - case LayoutSym: return target.__LAYOUT__; - case HeightSym: case WidthSym: if (GetEffectiveAcl(target) === AclPrivate) return returnZero; + case DocAcl : return target[DocAcl]; + case $mobx: return target.__fieldTuples[prop]; + case DocLayout: return target.__LAYOUT__; + case Height: case Width: if (GetEffectiveAcl(target) === AclPrivate) return returnZero; default : if (typeof prop === 'symbol') return target[prop]; if (prop.startsWith('isMobX')) return target[prop]; @@ -370,7 +359,7 @@ export function getter(target: any, prop: string | symbol, proxy: any): any { } function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any { - const field = target.__fields[prop]; + 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') { @@ -414,6 +403,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any 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]); }, @@ -430,11 +420,12 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any }); lastValue = ObjectField.MakeCopy(receiver[prop]); }), - 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); @@ -454,10 +445,11 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any }); lastValue = ObjectField.MakeCopy(receiver[prop]); }, - 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]); }, @@ -466,7 +458,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>); lastValue = ObjectField.MakeCopy(receiver[prop]); }, - prop: '', + prop: 'assign list', } ); } |
