diff options
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r-- | src/fields/Doc.ts | 138 |
1 files changed, 90 insertions, 48 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index fc43325fe..df49c32f0 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,6 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { saveAs } from 'file-saver'; -import { action, computed, observable, ObservableMap, runInAction } from 'mobx'; +import { action, computed, observable, ObservableMap, ObservableSet, runInAction } from 'mobx'; import { computedFn } from 'mobx-utils'; import { alias, map, serializable } from 'serializr'; import { DocServer } from '../client/DocServer'; @@ -94,6 +94,8 @@ export function DocListCastOrNull(field: FieldResult) { export const WidthSym = Symbol('Width'); export const HeightSym = Symbol('Height'); +export const AnimationSym = Symbol('Animation'); +export const HighlightSym = Symbol('Highlight'); export const DataSym = Symbol('Data'); export const LayoutSym = Symbol('Layout'); export const FieldsSym = Symbol('Fields'); @@ -111,28 +113,37 @@ export const Initializing = Symbol('Initializing'); export const ForceServerWrite = Symbol('ForceServerWrite'); export const CachedUpdates = Symbol('Cached updates'); -const AclMap = new Map<string, symbol>([ - ['None', AclUnset], - [SharingPermissions.None, AclPrivate], - [SharingPermissions.View, AclReadonly], - [SharingPermissions.Augment, AclAugment], - [SharingPermissions.SelfEdit, AclSelfEdit], - [SharingPermissions.Edit, AclEdit], - [SharingPermissions.Admin, AclAdmin], +export enum aclLevel { + unset = -1, + 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 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] }])); // caches the document access permissions for the current user. // this recursively updates all protos as well. export function updateCachedAcls(doc: Doc) { if (!doc) return; - const permissions: { [key: string]: symbol } = {}; - - doc[UpdatingFromServer] = true; - Object.keys(doc).filter(key => key.startsWith('acl') && (permissions[key] = AclMap.get(StrCast(doc[key]))!)); - doc[UpdatingFromServer] = false; - if (Object.keys(permissions).length) { - doc[AclSym] = permissions; + 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 (doc.proto instanceof Promise) { @@ -228,6 +239,19 @@ export class Doc extends RefField { public static get MyFileOrphans() { return DocCast(Doc.UserDoc().myFileOrphans); } + public static AddFileOrphan(doc: Doc) { + if ( + doc && + Doc.MyFileOrphans instanceof Doc && + Doc.IsPrototype(doc) && + !Doc.IsSystem(doc) && + ![DocumentType.MARKER, 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); } @@ -265,7 +289,7 @@ export class Doc extends RefField { } constructor(id?: FieldId, forceSave?: boolean) { super(id); - const doc = new Proxy<this>(this, { + const docProxy = new Proxy<this>(this, { set: setter, get: getter, // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter @@ -294,11 +318,11 @@ export class Doc extends RefField { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, }); - this[SelfProxy] = doc; + this[SelfProxy] = docProxy; if (!id || forceSave) { - DocServer.CreateField(doc); + DocServer.CreateField(docProxy); } - return doc; + return docProxy; } proto: Opt<Doc>; @@ -327,8 +351,15 @@ export class Doc extends RefField { @observable private ___fields: 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 [DirectLinksSym]: Set<Doc> = new Set(); + @observable public [AnimationSym]: Opt<Doc>; + @observable public [HighlightSym]: boolean = false; + static __Anim(Doc: Doc) { + // for debugging to print AnimationSym field easily. + return Doc[AnimationSym]; + } private [UpdatingFromServer]: boolean = false; private [ForceServerWrite]: boolean = false; @@ -756,12 +787,13 @@ export namespace Doc { } cloneMap.set(doc[Id], copy); } + Doc.AddFileOrphan(copy); return copy; } export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) { const linkMap = new Map<Doc, Doc>(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf', 'context'], dontCreate, asBranch); Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { @@ -934,11 +966,12 @@ 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 = ProxyField.WithoutProxy(() => 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); @@ -962,7 +995,6 @@ export namespace Doc { } } }); - copy.author = Doc.CurrentUserEmail; if (copyProto) { Doc.GetProto(copy).context = undefined; Doc.GetProto(copy).aliases = new List<Doc>([copy]); @@ -974,6 +1006,7 @@ export namespace Doc { if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } + Doc.AddFileOrphan(copy); return copy; } @@ -983,6 +1016,7 @@ export namespace Doc { if (doc) { const delegate = new Doc(id, true); delegate[Initializing] = true; + updateCachedAcls(delegate); delegate.proto = doc; delegate.author = Doc.CurrentUserEmail; Object.keys(doc) @@ -991,6 +1025,7 @@ export namespace Doc { if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], 'aliases', delegate); title && (delegate.title = title); delegate[Initializing] = false; + Doc.AddFileOrphan(delegate); return delegate; } return undefined; @@ -1113,7 +1148,7 @@ export namespace Doc { BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap(); SearchMatchDoc: ObservableMap<Doc, { searchMatch: number }> = new ObservableMap(); } - const brushManager = new DocBrush(); + export const brushManager = new DocBrush(); export class DocData { @observable _user_doc: Doc = undefined!; @@ -1259,48 +1294,55 @@ export namespace Doc { } export function linkFollowUnhighlight() { - Doc.UnhighlightAll(); + UnhighlightWatchers.forEach(watcher => watcher()); + UnhighlightWatchers.length = 0; + highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc)); document.removeEventListener('pointerdown', linkFollowUnhighlight); } - let _lastDate = 0; - export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true) { + let UnhighlightWatchers: (() => void)[] = []; + export let UnhighlightTimer: any; + export function AddUnHighlightWatcher(watcher: () => void) { + if (UnhighlightTimer) { + UnhighlightWatchers.push(watcher); + } else watcher(); + } + export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) { linkFollowUnhighlight(); - (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs)); + (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect)); document.removeEventListener('pointerdown', linkFollowUnhighlight); document.addEventListener('pointerdown', linkFollowUnhighlight); - const lastDate = (_lastDate = Date.now()); - window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000); + if (UnhighlightTimer) clearTimeout(UnhighlightTimer); + UnhighlightTimer = window.setTimeout(() => { + linkFollowUnhighlight(); + UnhighlightTimer = 0; + }, 5000); } - export class HighlightBrush { - @observable HighlightedDoc: Map<Doc, boolean> = new Map(); - } - const highlightManager = new HighlightBrush(); + 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 highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); + return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym]; } - export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { + export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) { runInAction(() => { - highlightManager.HighlightedDoc.set(doc, true); - dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true); + highlightedDocs.add(doc); + doc[HighlightSym] = true; + doc[AnimationSym] = presEffect; + if (dataAndDisplayDocs) { + highlightedDocs.add(Doc.GetProto(doc)); + Doc.GetProto(doc)[HighlightSym] = true; + } }); } export function UnHighlightDoc(doc: Doc) { runInAction(() => { - highlightManager.HighlightedDoc.set(doc, false); - highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false); + highlightedDocs.delete(doc); + highlightedDocs.delete(Doc.GetProto(doc)); + doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false; + doc[AnimationSym] = undefined; }); } - export function UnhighlightAll() { - const mapEntries = highlightManager.HighlightedDoc.keys(); - let docEntry: IteratorResult<Doc>; - while (!(docEntry = mapEntries.next()).done) { - const targetDoc = docEntry.value; - targetDoc && Doc.UnHighlightDoc(targetDoc); - } - } export function UnBrushAllDocs() { brushManager.BrushedDoc.clear(); } |