diff options
author | eperelm2 <emily_perelman@brown.edu> | 2023-07-18 11:40:12 -0400 |
---|---|---|
committer | eperelm2 <emily_perelman@brown.edu> | 2023-07-18 11:40:12 -0400 |
commit | 5100a643fb0d98b6dd738e7024f4fe15f56ba1a8 (patch) | |
tree | 92fa39d2d5cc8f584e3346c8fe0efaa5b184a9e5 /src/fields/util.ts | |
parent | c9779f31d9ce2363e61c3c9fa7e3446203622dde (diff) | |
parent | 16a1b7de3ec26187b3a426eb037a5e4f4b9fcc55 (diff) |
Merge branch 'master' into secondpropertiesmenu-emily
Diffstat (limited to 'src/fields/util.ts')
-rw-r--r-- | src/fields/util.ts | 321 |
1 files changed, 169 insertions, 152 deletions
diff --git a/src/fields/util.ts b/src/fields/util.ts index 0f164a709..28db77c65 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,23 +1,21 @@ import { $mobx, action, observable, runInAction, trace } from 'mobx'; import { computedFn } from 'mobx-utils'; import { DocServer } from '../client/DocServer'; -import { CollectionViewType } from '../client/documents/DocumentTypes'; import { LinkManager } from '../client/util/LinkManager'; import { SerializationHelper } from '../client/util/SerializationHelper'; import { UndoManager } from '../client/util/UndoManager'; import { returnZero } from '../Utils'; -import CursorField from './CursorField'; -import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc'; -import { AclAdmin, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols'; -import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols'; +import { aclLevel, Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc'; +import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols'; +import { FieldChanged, Id, Parent, ToValue } from './FieldSymbols'; import { List } from './List'; import { ObjectField } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; import { RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { SchemaHeaderField } from './SchemaHeaderField'; -import { ComputedField, ScriptField } from './ScriptField'; -import { ScriptCast, StrCast } from './Types'; +import { ComputedField } from './ScriptField'; +import { DocCast, ScriptCast, StrCast } from './Types'; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -51,11 +49,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number throw new Error("Can't put the same object in multiple documents at the same time"); } value[Parent] = receiver; - value[OnUpdate] = updateFunction(target, prop, value, receiver); + value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value); } if (curValue instanceof ObjectField) { delete curValue[Parent]; - delete curValue[OnUpdate]; + delete curValue[FieldChanged]; } const effectiveAcl = GetEffectiveAcl(target); @@ -63,8 +61,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number const writeMode = DocServer.getFieldWriteMode(prop as string); const fromServer = target[UpdatingFromServer]; const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail; - const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly; - const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly(); + const writeToDoc = + sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField); + const writeToServer = + !DocServer.Control.isReadOnly() && // + (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && value instanceof RichTextField)); if (writeToDoc) { if (value === undefined) { @@ -78,8 +79,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } if (writeToServer) { - if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } }); - else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } }); + // prettier-ignore + if (value === undefined) + (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $unset: { ['fields.' + prop]: '' } }); + else (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) :value}}); if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target); } else { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); @@ -105,7 +108,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number ); return true; } - return false; + return true; }); let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl; @@ -128,14 +131,18 @@ export function denormalizeEmail(email: string) { /** * Copies parent's acl fields to the child */ -export function inheritParentAcls(parent: Doc, child: Doc) { - return; - // const dataDoc = parent[DataSym]; - // for (const key of Object.keys(dataDoc)) { - // // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - // const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key]; - // key.startsWith('acl') && distributeAcls(key, permission, child); - // } +export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) { + [...Object.keys(parent), ...(Doc.CurrentUserEmail !== parent.author ? ['acl-Owner'] : [])] + .filter(key => key.startsWith('acl')) + .forEach(key => { + // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private. + // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; + const parAcl = ReverseHierarchyMap.get(StrCast(key === 'acl-Owner' ? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Edit) : parent[key]))?.acl; + if (parAcl) { + const sharePermission = HierarchyMapping.get(parAcl)?.name; + sharePermission && distributeAcls(key === 'acl-Owner' ? `acl-${normalizeEmail(StrCast(parent.author))}` : key, sharePermission, child, undefined, false, layoutOnly); + } + }); } /** @@ -154,13 +161,11 @@ export function inheritParentAcls(parent: Doc, child: Doc) { * Unset: Remove a sharing permission (eg., used ) */ export enum SharingPermissions { - Unset = 'None', Admin = 'Admin', Edit = 'Edit', - SelfEdit = 'Self Edit', Augment = 'Augment', View = 'View', - None = 'Not Shared', + None = 'Not-Shared', } // return acl from cache or cache the acl and return. @@ -173,11 +178,11 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { */ export function GetEffectiveAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; - if (target[UpdatingFromServer]) return AclAdmin; + if (target[UpdatingFromServer] || Doc.CurrentUserEmail === 'guest') return AclAdmin; return getEffectiveAclCache(target, user); // all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } -function getPropAcl(target: any, prop: string | symbol | number) { +export function GetPropAcl(target: any, prop: string | symbol | number) { if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable return GetEffectiveAcl(target); @@ -204,18 +209,13 @@ function getEffectiveAcl(target: any, user?: string): symbol { // there are issues with storing fields with . in the name, so they are replaced with _ during creation // as a result we need to restore them again during this comparison. const entity = denormalizeEmail(key.substring(4)); // an individual or a group - if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) { - if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') { + if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') { + if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) { effectiveAcl = value as symbol; } } } - // if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document - //const override = targetAcls['acl-Override']; - // if (override !== AclUnset && override !== undefined) effectiveAcl = override; - - // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl) return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl; } // authored documents are private until an ACL is set. @@ -223,73 +223,72 @@ function getEffectiveAcl(target: any, user?: string): symbol { if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; return AclAdmin; } + /** * Recursively distributes the access right for a user across the children of a document and its annotations. * @param key the key storing the access right (e.g. acl-groupname) * @param acl the access right being stored (e.g. "Can Edit") * @param target the document on which this access right is being set - * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection) - * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change + * @param visited list of Doc's already distributed to. + * @param allowUpgrade whether permissions can be made less restrictive + * @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well) */ -export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) { +export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) { + const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`; if (!visited) visited = [] as Doc[]; - if (!target || visited.includes(target)) return; - - if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { - target[key] = acl; - if (target !== Doc.GetProto(target)) { - //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???) - updateCachedAcls(target); - } - return; - } + if (!target || visited.includes(target) || key === selfKey) return; visited.push(target); - let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) - // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive - if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { - target[key] = acl; - layoutDocChanged = true; - - if (isDashboard) { - DocListCastAsync(target[Doc.LayoutFieldKey(target)]).then(docs => { - docs?.forEach(d => distributeAcls(key, acl, d, inheritingFromCollection, visited)); - }); - } - } - let dataDocChanged = false; const dataDoc = target[DocData]; - if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { - if (GetEffectiveAcl(dataDoc) === AclAdmin) { - dataDoc[key] = acl; - dataDocChanged = true; - } + const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0; + const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0; + if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) { + // propagate ACLs to links, children, and annotations - // maps over the links of the document - LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited)); + LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false)); - // maps over the children of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { - distributeAcls(key, acl, d, inheritingFromCollection, visited); - distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); - // maps over the annotations of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { - distributeAcls(key, acl, d, inheritingFromCollection, visited); - distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); + + Object.keys(target) // share expanded layout templates (eg, for presElementBox'es ) + .filter(lkey => lkey.includes('layout[') && DocCast(target[lkey])) + .map(lkey => { + distributeAcls(key, acl, DocCast(target[lkey]), visited, allowUpgrade ? true : false); + }); + + if (GetEffectiveAcl(dataDoc) === AclAdmin) { + dataDoc[key] = acl; + dataDocChanged = true; + } + } + + let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) + // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive + if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !Doc.GetT(target, key, 'boolean', true) || ReverseHierarchyMap.get(StrCast(target[key]))!.level > aclVal)) { + target[key] = acl; + layoutDocChanged = true; + if (dataDoc[key] === undefined) dataDoc[key] = acl; } layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made dataDocChanged && updateCachedAcls(dataDoc); } +// +// target should be either a Doc or ListImpl. receiver should be a Proxy<Doc> Or List. +// export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : getPropAcl(target, prop); - if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true; + const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop); + if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; @@ -356,86 +355,104 @@ export function deleteProperty(target: any, prop: string | number | symbol) { return true; } -export function updateFunction(target: any, prop: any, value: any, receiver: any) { - let lastValue = ObjectField.MakeCopy(value); - return (diff?: any) => { - const op = - diff?.op === '$addToSet' - ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } - : diff?.op === '$remFromSet' - ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)), hint: diff.hint } } - : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } }; - !op.$set && ((op as any).length = diff.length); - const prevValue = ObjectField.MakeCopy(lastValue as List<any>); - lastValue = ObjectField.MakeCopy(value); - const newValue = ObjectField.MakeCopy(value); - - if (!(value instanceof CursorField) && !value?.some?.((v: any) => v instanceof CursorField)) { - !receiver[UpdatingFromServer] && +// this function creates a function that can be used to setup Undo for whenever an ObjectField changes. +// the idea is that the Doc field setter can only setup undo at the granularity of an entire field and won't even be called if +// just a part of a field (eg. field within an ObjectField) changes. This function returns a function that can be called +// whenever an internal ObjectField field changes. It should be passed a 'diff' specification describing the change. Currently, +// List's are the only true ObjectFields that can be partially modified (ignoring SchemaHeaderFields which should go away). +// The 'diff' specification that a list can send is limited to indicating that something was added, removed, or that the list contents +// were replaced. Based on this specification, an Undo event is setup that will save enough information about the ObjectField to be +// able to undo and redo the partial change. +// +export function containedFieldChangedHandler(container: List<Field> | Doc, prop: string | number, liveContainedField: ObjectField) { + let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField; + return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => { + const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: Field) => SerializationHelper.Serialize(item)) }); + // prettier-ignore + const serverOp = diff?.op === '$addToSet' + ? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length } + : diff?.op === '$remFromSet' + ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint}, length: diff.length } + : { $set: { ['fields.' + prop]: liveContainedField ? SerializationHelper.Serialize(liveContainedField) : undefined } }; + + if (!(container instanceof Doc) || !container[UpdatingFromServer]) { + const prevValue = ObjectField.MakeCopy(lastValue as List<any>); + lastValue = ObjectField.MakeCopy(liveContainedField); + const newValue = ObjectField.MakeCopy(liveContainedField); + if (diff?.op === '$addToSet') { UndoManager.AddEvent( - diff?.op === '$addToSet' - ? { - redo: () => { - console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo - receiver[prop].push(...diff.items.map((item: any) => item.value ?? item)); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - undo: action(() => { - // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo - diff.items.forEach((item: any) => { - if (item instanceof SchemaHeaderField) { - const ind = receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); - ind !== -1 && receiver[prop].splice(ind, 1); - } else { - const ind = receiver[prop].indexOf(item.value ?? item); - ind !== -1 && receiver[prop].splice(ind, 1); - } - }); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }), - prop: 'add ' + diff.items.length + ' items to list', - } - : diff?.op === '$remFromSet' - ? { - redo: action(() => { - console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo - diff.items.forEach((item: any) => { - const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item); - ind !== -1 && receiver[prop].splice(ind, 1); - }); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }), - undo: () => { - // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo - diff.items.forEach((item: any) => { - if (item instanceof SchemaHeaderField) { - const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); - ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item); - } else { - const ind = (prevValue as List<any>).indexOf(item.value ?? item); - ind !== -1 && receiver[prop].indexOf(item.value ?? item) === -1 && receiver[prop].splice(ind, 0, item); - } - }); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - prop: 'remove ' + diff.items.length + ' items from list', - } - : { - redo: () => { - console.log('redo list: ' + prop, receiver[prop]); // bcz: uncomment to log undo - receiver[prop] = ObjectField.MakeCopy(newValue as List<any>); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - undo: () => { - // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo - receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - prop: 'assign list', - }, + { + redo: () => { + //console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo + (container as any)[prop as any]?.push(...(diff.items || [])?.map((item: any) => item.value ?? item)); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + undo: action(() => { + // console.log('undo $add: ' + prop, diff.items); // bcz: uncomment to log undo + diff.items?.forEach((item: any) => { + const ind = + item instanceof SchemaHeaderField // + ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) + : (container as any)[prop as any]?.indexOf(item.value ?? item); + ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1); + }); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }), + prop: 'add ' + diff.items?.length + ' items to list', + }, + diff?.items + ); + } else if (diff?.op === '$remFromSet') { + UndoManager.AddEvent( + { + redo: action(() => { + // console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo + diff.items?.forEach((item: any) => { + const ind = + item instanceof SchemaHeaderField // + ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) + : (container as any)[prop as any]?.indexOf(item.value ?? item); + ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1); + }); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }), + undo: () => { + // console.log('undo $rem: ' + prop, diff.items); // bcz: uncomment to log undo + diff.items?.forEach((item: any) => { + if (item instanceof SchemaHeaderField) { + const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); + ind !== -1 && (container as any)[prop as any].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && (container as any)[prop as any].splice(ind, 0, item); + } else { + const ind = (prevValue as List<any>).indexOf(item.value ?? item); + ind !== -1 && (container as any)[prop as any].indexOf(item.value ?? item) === -1 && (container as any)[prop as any].splice(ind, 0, item); + } + }); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + prop: 'remove ' + diff.items?.length + ' items from list', + }, diff?.items ); + } else { + const setFieldVal = (val: Field | undefined) => (container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as Field)); + UndoManager.AddEvent( + { + redo: () => { + // console.log('redo list: ' + prop, fieldVal()); // bcz: uncomment to log undo + setFieldVal(newValue instanceof ObjectField ? ObjectField.MakeCopy(newValue) : undefined); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + undo: () => { + // console.log('undo list: ' + prop, fieldVal()); // bcz: uncomment to log undo + setFieldVal(prevValue instanceof ObjectField ? ObjectField.MakeCopy(prevValue) : undefined); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + prop: 'set list field', + }, + diff?.items + ); + } } - target[Update](op); + container[FieldChanged]?.(undefined, serverOp); }; } |