diff options
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r-- | src/fields/Doc.ts | 173 |
1 files changed, 108 insertions, 65 deletions
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'; |