aboutsummaryrefslogtreecommitdiff
path: root/src/fields/Doc.ts
diff options
context:
space:
mode:
authorgeireann <geireann.lindfield@gmail.com>2021-08-27 14:19:25 -0400
committergeireann <geireann.lindfield@gmail.com>2021-08-27 14:19:25 -0400
commitbe4fd2492ad706f30af28f33133a4df0e8049e12 (patch)
treee33b32f54be50122ed16c07d2b6f6b2e79239cb4 /src/fields/Doc.ts
parentc5e96c72fcf149b9bcfe5f7f7a9c714de1d5fd9a (diff)
parent7c83bc30b3a6ed6061ef68bcef6a0e8941668b3c (diff)
Merge branch 'master' into schema-view-En-Hua
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r--src/fields/Doc.ts165
1 files changed, 114 insertions, 51 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e51eb44db..d24175ecd 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -21,8 +21,11 @@ import { listSpec } from "./Schema";
import { ComputedField, ScriptField } from "./ScriptField";
import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
import { AudioField, ImageField, PdfField, VideoField, WebField } from "./URLField";
-import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
+import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
import JSZip = require("jszip");
+import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import Color = require("color");
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -53,6 +56,9 @@ export namespace Field {
|| (field instanceof RefField)
|| (includeUndefined && field === undefined);
}
+ export function Copy(field: any) {
+ return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
+ }
}
export type Field = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
@@ -60,10 +66,10 @@ export type FieldWaiting<T extends RefField = RefField> = T extends undefined ?
export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
/**
- * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
- * If a default value is given, that will be returned instead of undefined.
- * If a default value is given, the returned value should not be modified as it might be a temporary value.
- * If no default value is given, and the returned value is not undefined, it can be safely modified.
+ * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
+ * If a default value is given, that will be returned instead of undefined.
+ * If a default value is given, the returned value should not be modified as it might be a temporary value.
+ * If no default value is given, and the returned value is not undefined, it can be safely modified.
*/
export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
@@ -88,7 +94,8 @@ export const DirectLinksSym = Symbol("DirectLinks");
export const AclUnset = Symbol("AclUnset");
export const AclPrivate = Symbol("AclOwnerOnly");
export const AclReadonly = Symbol("AclReadOnly");
-export const AclAddonly = Symbol("AclAddonly");
+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");
@@ -100,7 +107,8 @@ const AclMap = new Map<string, symbol>([
["None", AclUnset],
[SharingPermissions.None, AclPrivate],
[SharingPermissions.View, AclReadonly],
- [SharingPermissions.Add, AclAddonly],
+ [SharingPermissions.Augment, AclAugment],
+ [SharingPermissions.SelfEdit, AclSelfEdit],
[SharingPermissions.Edit, AclEdit],
[SharingPermissions.Admin, AclAdmin]
]);
@@ -250,7 +258,8 @@ export class Doc extends RefField {
DocServer.GetRefField(this[Id], true);
}
};
- if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+ const writeMode = DocServer.getFieldWriteMode(fKey);
+ if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
await fn();
} else {
@@ -364,13 +373,13 @@ export namespace Doc {
/**
* This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
* the values of the properties of a source object into the target.
- *
+ *
* This is just a specific, Dash-authored version that serves the same role for our
* Doc class.
- *
- * @param doc the target document into which you'd like to insert the new fields
+ *
+ * @param doc the target document into which you'd like to insert the new fields
* @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
- * to a potentially undefined field, where each entry in this mapping is optional.
+ * to a potentially undefined field, where each entry in this mapping is optional.
*/
export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
isInitializing && (doc[Initializing] = true);
@@ -397,7 +406,7 @@ 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
+ // 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').
export function GetProto(doc: Doc): Doc {
if (doc instanceof Promise) {
@@ -423,6 +432,9 @@ export namespace Doc {
return Array.from(results);
}
+ /**
+ * @returns the index of doc toFind in list of docs, -1 otherwise
+ */
export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
@@ -516,30 +528,30 @@ export namespace Doc {
return alias;
}
- export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+ export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, 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);
cloneMap.set(doc[Id], copy);
- if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
- const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
- await Promise.all([...Object.keys(doc), "links"].map(async key => {
+ const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
+ const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
+ await Promise.all(Object.keys(doc).map(async key => {
if (filter.includes(key)) return;
const assignKey = (val: any) => !dontCreate && (copy[key] = val);
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === "links" && Doc.IsPrototype(doc) ? doc[key] : ProxyField.WithoutProxy(() => doc[key]);
+ const field = ProxyField.WithoutProxy(() => doc[key]);
const copyObjectField = async (field: ObjectField) => {
- const list = Cast(doc[key], listSpec(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, 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, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate 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) {
- if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
+ if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
rtfs.push({ copy, key, field });
}
}
@@ -547,23 +559,30 @@ export namespace Doc {
};
if (key === "proto") {
if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate, asBranch));
+ 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));
}
} else {
if (field instanceof RefField) {
assignKey(field);
} else if (cfield instanceof ComputedField) {
!dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
- (key === "links" && field instanceof ObjectField) && await copyObjectField(field);
} else if (field instanceof ObjectField) {
await copyObjectField(field);
} else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
+ debugger; //This shouldn't happen...
} else {
assignKey(field);
}
}
}));
+ for (const link of Array.from(doc[DirectLinksSym])) {
+ const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
+ linkMap.set(link, linkClone);
+ }
if (!dontCreate) {
Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
@@ -574,10 +593,11 @@ export namespace Doc {
}
return copy;
}
- export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false) {
- const cloneMap = new Map<string, Doc>();
+ 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, rtfMap, ["context", "annotationOn", "cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], 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) => {
const mapped = cloneMap.get(id);
@@ -587,9 +607,9 @@ export namespace Doc {
const mapped = cloneMap.get(id);
return href + (mapped ? mapped[Id] : id);
};
- const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
+ const regex = `(${Doc.localServerPath()})([^"]*)`;
const re = new RegExp(regex, "g");
- copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
+ copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
});
return { clone: copy, map: cloneMap };
}
@@ -657,14 +677,14 @@ export namespace Doc {
const _pendingMap: Map<string, boolean> = new Map();
//
// Returns an expanded template layout for a target data document if there is a template relationship
- // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
+ // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
// of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
// templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key.
// NOTE: the template will have references to "@params" -- the template arguments will be assigned to the '@params' field
// so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and
// the derefence will then occur on the rootDocument (the original document).
// in the future, field references could be written as @<someparam> and then arguments would be passed in the layout key as:
- // layout_mytemplate(somparam=somearg).
+ // layout_mytemplate(somparam=somearg).
// then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS);
@@ -765,7 +785,7 @@ export namespace Doc {
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
+ 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...
@@ -803,6 +823,27 @@ export namespace Doc {
return undefined;
}
+ // Makes a delegate of a document by first creating a delegate where data should be stored
+ // (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
+ // This is appropriate if you're trying to create a document that behaves like all
+ // regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
+ export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string): Doc {
+ const delegateProto = new Doc();
+ delegateProto[Initializing] = true;
+ delegateProto.proto = doc;
+ delegateProto.author = Doc.CurrentUserEmail;
+ delegateProto.isPrototype = 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);
+ delegate[Initializing] = false;
+ delegateProto[Initializing] = false;
+ return delegate;
+ }
+
let _applyCount: number = 0;
export function ApplyTemplate(templateDoc: Doc) {
if (templateDoc) {
@@ -866,6 +907,16 @@ export namespace Doc {
return true;
}
+
+ // converts a document id to a url path on the server
+ export function globalServerPath(doc: Doc | string = ""): string {
+ return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc));
+ }
+ // converts a document id to a url path on the server
+ export function localServerPath(doc?: Doc): string {
+ return "/doc/" + (doc ? doc[Id] : "");
+ }
+
export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
const doc2Layout = Doc.Layout(doc2);
const doc1Layout = Doc.Layout(doc1);
@@ -897,7 +948,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.
+ // 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;
@@ -1037,6 +1088,13 @@ export namespace Doc {
}
export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
+ if (Utils.HasTransparencyFilter(value)) {
+ const isTransparent = (color: string) => color !== "" && (Color(color).alpha() !== 1);
+ return isTransparent(StrCast(doc[key]));
+ }
+ if (typeof value === "string") {
+ value = value.replace(`,${Utils.noRecursionHack}`, "");
+ }
const fieldVal = doc[key];
if (Cast(fieldVal, listSpec("string"), []).length) {
const vals = Cast(fieldVal, listSpec("string"), []);
@@ -1076,7 +1134,7 @@ export namespace Doc {
}
// filters document in a container collection:
- // all documents with the specified value for the specified key are included/excluded
+ // all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
if (!container) return;
@@ -1147,11 +1205,13 @@ export namespace Doc {
dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
}
+
+ if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
+
return ndoc;
}
export function delegateDragFactory(dragFactory: Doc) {
- const ndoc = Doc.MakeDelegate(dragFactory);
- ndoc.isPrototype = true;
+ const ndoc = Doc.MakeDelegateWithProto(dragFactory);
if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
@@ -1164,7 +1224,10 @@ export namespace Doc {
case DocumentType.IMG: return "image";
case DocumentType.COMPARISON: return "columns";
case DocumentType.RTF: return "sticky-note";
- case DocumentType.COL: return !doc?.isFolder ? "folder" + (isOpen ? "-open" : "") : "chevron-" + (isOpen ? "down" : "right");
+ case DocumentType.COL:
+ const folder: IconProp = isOpen ? "folder-open" : "folder";
+ const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right";
+ return !doc?.isFolder ? folder : chevron;
case DocumentType.WEB: return "globe-asia";
case DocumentType.SCREENSHOT: return "photo-video";
case DocumentType.WEBCAM: return "video";
@@ -1198,39 +1261,39 @@ export namespace Doc {
/**
* This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
* deep levels of nesting, converts the data and structure into nested documents with the appropriate fields.
- *
+ *
* After building a hierarchy within / below a top-level document, it then returns that top-level parent.
- *
+ *
* If we've received a string, treat it like valid JSON and try to parse it into an object. If this fails, the
* string is invalid JSON, so we should assume that the input is the result of a JSON.parse()
* call that returned a regular string value to be stored as a Field.
- *
+ *
* If we've received something other than a string, since the caller might also pass in the results of a
* JSON.parse() call, valid input might be an object, an array (still typeof object), a boolean or a number.
* Anything else (like a function, etc. passed in naively as any) is meaningless for this operation.
- *
+ *
* All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else,
* lacking the key value structure, gets stored as a field in a wrapper document.
- *
+ *
* @param data for convenience and flexibility, either a valid JSON string to be parsed,
* or the result of any JSON.parse() call.
* @param title an optional title to give to the highest parent document in the hierarchy.
* If whether this function creates a new document or appendToExisting is specified and that document already has a title,
* because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title.
* @param appendToExisting **if specified**, there are two cases, both of which return the target document:
- *
+ *
* 1) the json to be converted can be represented as a document, in which case the target document will act as the root
* of the tree and receive all the conversion results as new fields on itself
* 2) the json can't be represented as a document, in which case the function will assign the field-level conversion
* results to either the specified key on the target document, or to its "json" key by default.
- *
+ *
* If not specified, the function creates and returns a new entirely generic document (different from the Doc.Create calls)
* to act as the root of the tree.
- *
+ *
* One might choose to specify this field if you want to write to a document returned from a Document.Create function call,
* say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created
* from a default call to new Doc.
- *
+ *
* @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even
* if they contain no data. By default, empty objects and arrays are ignored.
*/
@@ -1267,7 +1330,7 @@ export namespace Doc {
* For each value of the object, recursively convert it to its appropriate field value
* and store the field at the appropriate key in the document if it is not undefined
* @param object the object to convert
- * @returns the object mapped from JSON to field values, where each mapping
+ * @returns the object mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertObject)
*/
const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
@@ -1291,10 +1354,10 @@ export namespace Doc {
};
/**
- * For each element in the list, recursively convert it to a document or other field
+ * For each element in the list, recursively convert it to a document or other field
* and push the field to the list if it is not undefined
* @param list the list to convert
- * @returns the list mapped from JSON to field values, where each mapping
+ * @returns the list mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertList)
*/
const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
@@ -1333,7 +1396,7 @@ Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
Scripting.addGlobal(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); });
Scripting.addGlobal(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); });
-Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
+Scripting.addGlobal(function copyField(field: any) { return Field.Copy(field); });
Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });