diff options
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r-- | src/fields/Doc.ts | 75 |
1 files changed, 42 insertions, 33 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index de94ed5db..168e29dd5 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -10,6 +10,7 @@ import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGloba import { SelectionManager } from '../client/util/SelectionManager'; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; import { UndoManager } from '../client/util/UndoManager'; +import { decycle } from '../decycler/decycler'; import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; import { DateField } from './DateField'; import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols'; @@ -25,7 +26,6 @@ import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Ty import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from './URLField'; import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util'; import JSZip = require('jszip'); - export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key); @@ -701,22 +701,12 @@ export namespace Doc { return bestAlias ?? Doc.MakeAlias(doc); } - export async function makeClone( - doc: Doc, - cloneMap: Map<string, Doc>, - linkMap: Map<Doc, Doc>, - rtfs: { copy: Doc; key: string; field: RichTextField }[], - exclusions: string[], - topLevelExclusions: string[], - dontCreate: boolean, - asBranch: boolean - ): Promise<Doc> { + export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; - const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true); + const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) ?? doc : doc) : new Doc(undefined, true); cloneMap.set(doc[Id], copy); - const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions; - const filter = [...fieldExclusions, ...topLevelExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; + const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; await Promise.all( Object.keys(doc).map(async key => { if (filter.includes(key)) return; @@ -727,10 +717,10 @@ export namespace Doc { const list = await Cast(doc[key], listSpec(Doc)); const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); if (docs !== undefined && docs.length) { - const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch))); + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch))); !dontCreate && assignKey(new List<Doc>(clones)); } else if (doc[key] instanceof Doc) { - assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields + assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields } else { !dontCreate && assignKey(ObjectField.MakeCopy(field)); if (field instanceof RichTextField) { @@ -740,13 +730,12 @@ export namespace Doc { } } }; - if (key === 'proto') { - if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)); - } - } else if (key === 'anchor1' || key === 'anchor2') { - if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], true, asBranch)); + const docAtKey = doc[key]; + if (docAtKey instanceof Doc) { + if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || (key === 'proto' && cloneMap.has(doc[Id])) || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { + assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); + } else { + assignKey(docAtKey); } } else { if (field instanceof RefField) { @@ -765,8 +754,8 @@ export namespace Doc { }) ); for (const link of Array.from(doc[DirectLinksSym])) { - const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch); - linkMap.set(link, linkClone); + const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); + linkMap.set(link[Id], linkClone); } if (!dontCreate) { Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); @@ -779,11 +768,29 @@ export namespace Doc { Doc.AddFileOrphan(copy); return copy; } + export function repairClone(doc: Doc, cloned: Doc[], visited: Set<Doc>) { + if (visited.has(doc)) return; + visited.add(doc); + Object.keys(doc).map(key => { + const docAtKey = DocCast(doc[key]); + if (docAtKey && !Doc.IsSystem(docAtKey)) { + if (!cloned.includes(docAtKey)) { + doc[key] = undefined; + } else { + repairClone(docAtKey, cloned, visited); + } + } + }); + } export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) { - const linkMap = new Map<Doc, Doc>(); + const linkMap = new Map<string, Doc>(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - 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)); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch); + const repaired = new Set<Doc>(); + const linkedDocs = Array.from(linkMap.values()); + const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; + clonedDocs.map(clone => Doc.repairClone(clone, Array.from(cloneMap.values()), repaired)); + linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); @@ -797,7 +804,7 @@ export namespace Doc { const re = new RegExp(regex, 'g'); copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); }); - return { clone: copy, map: cloneMap }; + return { clone: copy, map: cloneMap, linkMap }; } export async function Zip(doc: Doc) { @@ -806,9 +813,10 @@ export namespace Doc { // a.href = url; // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); - const { clone, map } = await Doc.MakeClone(doc, true); + const { clone, map, linkMap } = await Doc.MakeClone(doc, true); + clone.LINKS = new List<Doc>(Array.from(linkMap.values())); function replacer(key: any, value: any) { - if (['branchOf', 'cloneOf', 'context', 'cursors'].includes(key)) return undefined; + if (['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; else if (value instanceof Doc) { if (key !== 'field' && Number.isNaN(Number(key))) { const __fields = value[FieldsSym](); @@ -833,7 +841,7 @@ export namespace Doc { const docs: { [id: string]: any } = {}; Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); - const docString = JSON.stringify({ id: doc[Id], docs }, replacer); + const docString = JSON.stringify({ id: doc[Id], docs }, decycle(replacer)); const zip = new JSZip(); @@ -1520,7 +1528,8 @@ export namespace Doc { const response = await fetch(upload, { method: 'POST', body: formData }); const json = await response.json(); if (json !== 'error') { - const doc = await DocServer.GetRefField(json); + const doc = DocCast(await DocServer.GetRefField(json)); + (await DocListCastAsync(doc?.LINKS))?.forEach(link => LinkManager.Instance.addLink(link)); return doc; } } |