aboutsummaryrefslogtreecommitdiff
path: root/src/fields/Doc.ts
diff options
context:
space:
mode:
authorJoanne <zehan_ding@brown.edu>2025-05-12 20:58:01 -0400
committerJoanne <zehan_ding@brown.edu>2025-05-12 20:58:01 -0400
commitcd93c88b8fee83a99342eac4dc60f7b4373fa843 (patch)
treeb00d1f46c802752c90e54bb21be785a05e05195e /src/fields/Doc.ts
parent4997c3de20a381eac30224a7a550afa66174f07d (diff)
parent3a733aa0fd24517e83649824dec0fc8bcc0bde43 (diff)
added tutorial tool, still need to integrate with metadatatool
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r--src/fields/Doc.ts593
1 files changed, 308 insertions, 285 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index dc4a5a011..ba94f0504 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -17,7 +17,7 @@ import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScr
import { InkEraserTool, InkInkTool, InkTool } from './InkField';
import { List } from './List';
import { ObjectField, serverOpType } from './ObjectField';
-import { PrefetchProxy, ProxyField } from './Proxy';
+import { PrefetchProxy } from './Proxy';
import { FieldId, RefField } from './RefField';
import { RichTextField } from './RichTextField';
import { listSpec } from './Schema';
@@ -43,11 +43,11 @@ export namespace Field {
* @param doc doc containing key
* @param key field key to display
* @param showComputedValue whether copmuted function should display its value instead of its function
+ * @param schemaCell
* @returns string representation of the field
*/
export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean, schemaCell?: boolean): string {
- const isOnDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
const valFunc = (field: FieldType): string => {
const res =
field instanceof ComputedField && showComputedValue
@@ -64,7 +64,9 @@ export namespace Field {
.trim()
.replace(/^new List\((.*)\)$/, '$1');
};
- return !Field.IsField(cfield) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(cfield);
+ const notOnTemplate = !key.startsWith('_') || doc[DocLayout] === doc;
+ const isOnDelegate = notOnTemplate && !Doc.IsDataProto(doc) && ((key.startsWith('_') && !Field.IsField(cfield)) || Object.keys(doc).includes(key.replace(/^_/, '')));
+ return (isOnDelegate ? '=' : '') + (!Field.IsField(cfield) ? '' : valFunc(cfield));
}
export function toScriptString(field: FieldType, schemaCell?: boolean) {
switch (typeof field) {
@@ -131,13 +133,13 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
}
export function NumListCast(field: FieldResult, defaultVal: number[] = []) {
- return Cast(field, listSpec('number'), defaultVal);
+ return Cast(field, listSpec('number'), defaultVal)!;
}
export function StrListCast(field: FieldResult, defaultVal: string[] = []) {
- return Cast(field, listSpec('string'), defaultVal);
+ return Cast(field, listSpec('string'), defaultVal)!;
}
export function DocListCast(field: FieldResult, defaultVal: Doc[] = []) {
- return Cast(field, listSpec(Doc), defaultVal).filter(d => d instanceof Doc) as Doc[];
+ return Cast(field, listSpec(Doc), defaultVal)!.filter(d => d instanceof Doc);
}
export enum aclLevel {
@@ -174,13 +176,27 @@ export function updateCachedAcls(doc: Doc) {
}
if (doc.proto instanceof Promise) {
- doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
+ doc.proto.then(proto => DocCast(proto) && updateCachedAcls(DocCast(proto)!));
return doc.proto;
}
}
return undefined;
}
+/**
+ * computes a field name for where to store and expanded template Doc
+ * The format is layout_[ROOT_TEMPLATE_NMAE]_[ROOT_TEMPLATE_CHILD_NAME]_...
+ * @param template the template (either a root or a root child Doc)
+ * @param layoutFieldKey the fieldKey of the container of the template
+ * @returns field key to store expanded template Doc
+ */
+export function expandedFieldName(template: Doc, layoutFieldKey?: string) {
+ const layout_key = !layoutFieldKey?.endsWith(']')
+ ? 'layout' // layout_SOMETHING = SOMETHING => layout_[SOMETHING] = [SOMETHING]
+ : layoutFieldKey; // prettier-ignore
+ const tempTitle = '[' + StrCast(template.title).replace(/^\[(.*)\]$/, '$1') + ']';
+ return `${layout_key}_${tempTitle}`; // prettier-ignore
+}
@scriptingGlobal
@Deserializable('Doc', (obj: unknown) => updateCachedAcls(obj as Doc), ['id'])
export class Doc extends RefField {
@@ -215,7 +231,7 @@ export class Doc extends RefField {
public static AddLink: (link: Doc, checkExists?: boolean) => void;
public static DeleteLink: (link: Doc) => void;
public static Links: (link: Doc | undefined) => Doc[];
- public static getOppositeAnchor: (linkDoc: Doc, anchor: Doc) => Doc | undefined;
+ public static getOppositeAnchor: (linkDoc: Doc | undefined, anchor: Doc | undefined) => Doc | undefined;
// KeyValueBox SetField (defined there)
public static SetField: (doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => boolean;
// UserDoc "API"
@@ -237,7 +253,7 @@ export class Doc extends RefField {
public static get MyPublishedDocs() { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs).concat(DocListCast(DocCast(Doc.UserDoc().myPublishedDocs)?.data)); } // prettier-ignore
public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore
public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore
- public static get MyStickers() { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore
+ public static get MyStickers() { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore
public static get MyLightboxDrawings() { return DocCast(Doc.UserDoc().myLightboxDrawings); } // prettier-ignore
public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore
public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore
@@ -264,22 +280,28 @@ export class Doc extends RefField {
public static set ActiveDashboard(val: Opt<Doc>) { Doc.UserDoc().activeDashboard = val; } // prettier-ignore
public static get MyFilterHotKeys() { return DocListCast(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter)?.data).filter(key => key.toolType !== "-opts-"); } // prettier-ignore
public static RemFromFilterHotKeys(doc: Doc) {
- return Doc.RemoveDocFromList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+ return (filters => filters && Doc.RemoveDocFromList(filters, 'data', doc))(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter));
}
public static AddToFilterHotKeys(doc: Doc) {
- return Doc.AddDocToList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+ return (btns => btns && Doc.AddDocToList(btns, 'data', doc))(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter));
}
public static IsInMyOverlay(doc: Doc) { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
- public static AddToMyOverlay(doc: Doc) { return Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
- public static RemFromMyOverlay(doc: Doc) { return Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
- public static AddToMyPublished(doc: Doc) {
- doc[DocData].title_custom = true;
- doc[DocData].layout_showTitle = 'title';
- Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
- public static RemFromMyPublished(doc: Doc){
- doc[DocData].title_custom = false;
- doc[DocData].layout_showTitle = undefined;
- Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+ public static AddToMyOverlay(doc: Doc) {
+ return Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc);
+ } // : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+ public static RemFromMyOverlay(doc: Doc) {
+ return Doc.ActiveDashboard && Doc.RemoveDocFromList(Doc.ActiveDashboard, 'myOverlayDocs', doc);
+ } // : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+ public static AddToMyPublished(doc: Doc) {
+ doc.$title_custom = true;
+ doc.$layout_showTitle = 'title';
+ Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc);
+ } // : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+ public static RemFromMyPublished(doc: Doc) {
+ doc.$title_custom = false;
+ doc.$layout_showTitle = undefined;
+ Doc.ActiveDashboard && Doc.RemoveDocFromList(Doc.ActiveDashboard, 'myPublishedDocs', doc);
+ } // : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
public static IsComicStyle(doc?: Doc) { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
constructor(id?: FieldId, forceSave?: boolean) {
@@ -320,10 +342,11 @@ export class Doc extends RefField {
UpdatingFromServer,
Width,
'__LAYOUT__',
+ '__DATA__',
];
},
getOwnPropertyDescriptor: (target, prop) => {
- if (prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) {
+ if (prop.toString() === '__DATA__' || prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
return {
@@ -400,23 +423,23 @@ export class Doc extends RefField {
public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore
public get [DocData](): Doc {
+ return this[SelfProxy].__DATA__;
+ }
+ @computed get __DATA__(): Doc {
const self = this[SelfProxy];
- return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+ return self.rootDocument && !self.isTemplateForField ? self : Doc.GetProto(DocCast(self[DocLayout].rootDocument, self)!);
}
- @computed get __LAYOUT__(): Doc | undefined {
+ @computed get __LAYOUT__(): Doc {
const self = this[SelfProxy];
const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null);
if (templateLayoutDoc) {
- let renderFieldKey: string = '';
const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
- if (typeof layoutField === 'string') {
- [renderFieldKey] = layoutField.split("fieldKey={'")[1].split("'"); // layoutField.split("'")[1];
- } else {
- return Cast(layoutField, Doc, null);
+ if (typeof layoutField !== 'string') {
+ return DocCast(layoutField, self)!;
}
- return Cast(self[renderFieldKey + '_layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
+ return DocCast(self[expandedFieldName(templateLayoutDoc)], templateLayoutDoc)!;
}
- return undefined;
+ return self;
}
public async [HandleUpdate](diff: { $set: { [key: string]: FieldType } } | { $unset?: unknown }) {
@@ -591,7 +614,7 @@ export namespace Doc {
// 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 data doc ('isDataDoc').
export function GetProto(doc: Doc): Doc {
- const proto = doc && (Doc.GetT(doc, 'isDataDoc', 'boolean', true) ? doc : DocCast(doc.proto, doc));
+ const proto = doc && (Doc.GetT(doc, 'isDataDoc', 'boolean', true) ? doc : DocCast(doc.proto, doc)!);
return proto === doc ? proto : Doc.GetProto(proto);
}
export function GetDataDoc(doc: Doc): Doc {
@@ -624,8 +647,8 @@ export namespace Doc {
* @returns true if successful, false otherwise.
*/
export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
- const key = fieldKey || Doc.LayoutFieldKey(listDoc);
- const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
+ const key = fieldKey || Doc.LayoutDataKey(listDoc);
+ const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
if (list) {
const ind = list.indexOf(doc);
if (ind !== -1) {
@@ -641,8 +664,8 @@ export namespace Doc {
* @returns true if successful, false otherwise.
*/
export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean, ignoreProto?: boolean) {
- const key = fieldKey || Doc.LayoutFieldKey(listDoc);
- const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
+ const key = fieldKey || Doc.LayoutDataKey(listDoc);
+ const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
if (list) {
if (!allowDuplicates) {
const pind = list.findIndex(d => d instanceof Doc && d[Id] === doc[Id]);
@@ -680,41 +703,36 @@ export namespace Doc {
return DocListCast(Doc.Get(doc[DocData], 'proto_embeddings', true));
}
+ /**
+ * Makes an embedding of a Doc. This Doc shares the data portion of the origiginal Doc.
+ * If the copied Doc has no prototype, then instead of copying the Doc, this just creates
+ * a new Doc that is a delegate of the original Doc.
+ * @param doc Doc to embed
+ * @param id id to use for embedded Doc
+ * @returns a new Doc that is an embedding of the original Doc
+ */
export function MakeEmbedding(doc: Doc, id?: string) {
- const embedding = (!GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
- const layout = Doc.LayoutField(embedding);
- if (layout instanceof Doc && layout !== embedding && layout === Doc.Layout(embedding)) {
- Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
- }
+ const embedding = (!Doc.IsDataProto(doc) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
embedding.createdFrom = doc;
- embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
- !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
embedding.author = ClientUtils.CurrentUserEmail();
+ embedding.proto_embeddingId = doc.$proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
+ embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`);
return embedding;
}
export function BestEmbedding(doc: Doc) {
- const dataDoc = doc[DocData];
- const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
- const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
+ const availableEmbeddings = Doc.GetEmbeddings(doc);
+ const bestEmbedding = [...(doc[DocData] !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
bestEmbedding && Doc.AddEmbedding(doc, doc);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
// this lists out all the tag ids that can be in a RichTextField that might contain document ids.
// if a document is cloned, we need to make sure to clone all of these referenced documents as well;
- export const DocsInTextFieldIds = ['audioId', 'textId', 'anchorId', 'docId'];
- export async function makeClone(
- doc: Doc,
- cloneMap: Map<string, Doc>,
- linkMap: Map<string, Doc>,
- rtfs: { copy: Doc; key: string; field: RichTextField }[],
- exclusions: string[],
- pruneDocs: Doc[],
- cloneLinks: boolean,
- cloneTemplates: boolean
- ): Promise<Doc> {
+ const FindDocsInRTF = new RegExp(/(audioId|textId|anchorId|docId)"\s*:\s*"(.*?)"/g);
+
+ export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], pruneDocs: Doc[], cloneLinks: boolean, cloneTemplates: boolean): Doc {
if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
return doc;
}
@@ -722,81 +740,59 @@ export namespace Doc {
const copy = new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
const filter = [...exclusions, ...StrListCast(doc.cloneFieldFilter)];
- await Promise.all(
- Object.keys(doc).map(async key => {
- if (filter.includes(key)) return;
- const assignKey = (val: Opt<FieldType>) => {
- copy[key] = val;
- };
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = async (objField: ObjectField) => {
- 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, pruneDocs, cloneLinks, cloneTemplates)));
- assignKey(new List<Doc>(clones));
- } else {
- assignKey(ObjectField.MakeCopy(objField));
- if (objField instanceof RichTextField) {
- if (DocsInTextFieldIds.some(id => objField.Data.includes(`"${id}":`))) {
- const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => '(' + exp + ')').join('|') + ')":"([a-z-A-Z0-9_]*)"', 'g');
- const rawdocids = objField.Data.match(docidsearch);
- const docids = rawdocids?.map((str: string) =>
- DocsInTextFieldIds.reduce((output, exp) => output.replace(new RegExp(`${exp}":`, 'g'), ''), str)
- .replace(/"/g, '')
- .trim()
- );
- const results = docids && (await DocServer.GetRefFields(docids));
- const rdocs = results && Array.from(Object.keys(results)).map(rkey => DocCast(results.get(rkey)));
- rdocs?.map(d => d && Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
- rtfs.push({ copy, key, field: objField });
- }
- }
- }
- };
- const docAtKey = doc[key];
+ Object.keys(doc)
+ .filter(key => !filter.includes(key))
+ .map(key => {
+ const assignKey = (val: Opt<FieldType>) => (copy[key] = val);
+
if (key === 'author') {
- assignKey(ClientUtils.CurrentUserEmail());
- } else if (docAtKey instanceof Doc) {
- if (pruneDocs.includes(docAtKey)) {
- // prune doc and do nothing
- } else if (
- !Doc.IsSystem(docAtKey) &&
- (key.includes('layout[') ||
- key.startsWith('layout') || //
- ['embedContainer', 'annotationOn', 'proto'].includes(key) ||
- (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()))
- ) {
- assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
- } else {
- assignKey(docAtKey);
+ return assignKey(ClientUtils.CurrentUserEmail());
+ }
+ const cfield = ComputedField.DisableCompute(() => doc[key]);
+ if (cfield instanceof ComputedField) {
+ return assignKey(cfield[Copy]());
+ }
+ const field = doc[key];
+ if (field instanceof Doc) {
+ const doCopy = () => Doc.IsSystem(field) ||
+ !( key.startsWith('layout') ||
+ ['embedContainer', 'annotationOn', 'proto'].includes(key) || //
+ (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()) ); // prettier-ignore
+ return !pruneDocs.includes(field) &&
+ assignKey(doCopy()
+ ? field //
+ : Doc.makeClone(field, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)); // prettier-ignore
+ }
+ if (field instanceof RichTextField) {
+ rtfs.push({ copy, key, field });
+ let docId: string | undefined;
+ while ((docId = (FindDocsInRTF.exec(field.Data) ?? [undefined, undefined, undefined])[2])) {
+ const docCopy = DocServer.GetCachedRefField(docId);
+ docCopy && Doc.makeClone(docCopy, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates);
}
- } else if (field instanceof RefField) {
- assignKey(field);
- } else if (cfield instanceof ComputedField) {
- assignKey(cfield[Copy]());
- } else if (field instanceof ObjectField) {
- await copyObjectField(field);
- } else if (field instanceof Promise) {
- // eslint-disable-next-line no-debugger
- debugger; // This shouldn't happen...
- } else {
- assignKey(field);
+ return assignKey(ObjectField.MakeCopy(field));
}
- })
- );
- Array.from(doc[DirectLinks]).forEach(async link => {
+ if (field instanceof ObjectField) {
+ if (DocListCast(field).length) {
+ return assignKey(new List<Doc>(DocListCast(field).map(d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates))));
+ }
+ return assignKey(ObjectField.MakeCopy(field)); // otherwise just copy the field
+ }
+ if (!(field instanceof Promise)) return assignKey(field);
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happen...
+ });
+ Array.from(doc[DirectLinks]).forEach(link => {
if (
cloneLinks ||
- ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id])) &&
- (cloneMap.has(DocCast(link.link_anchor_2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_2)?.annotationOn)?.[Id])))
+ ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id] ?? '') || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id] ?? '')) &&
+ (cloneMap.has(DocCast(link.link_anchor_2)?.[Id] ?? '') || cloneMap.has(DocCast(DocCast(link.link_anchor_2)?.annotationOn)?.[Id] ?? '')))
) {
- linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
+ linkMap.set(link[Id], Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
}
});
copy.cloneOf = doc;
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc.title));
+ const cfield = ComputedField.DisableCompute(() => FieldValue(doc.title));
if (Doc.Get(copy, 'title', true) && !(cfield instanceof ComputedField)) copy.title = '>:' + doc.title;
cloneMap.set(doc[Id], copy);
@@ -823,25 +819,27 @@ export namespace Doc {
return docs.map(doc => Doc.MakeClone(doc, cloneLinks, cloneTemplates, cloneMap));
}
- export async function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
+ /**
+ * Copies a Doc and all of the Docs that it references. This is a deep copy of the Doc.
+ * However, the additional flags allow you to control whether to copy links and templates.
+ * @param doc Doc to clone
+ * @param cloneLinks whether to clone links to this Doc
+ * @param cloneTemplates whether to clone the templates used by this Doc
+ * @param cloneMap a map from the Doc ids of the original Doc to the new Docs
+ * @returns a clone of the original Doc
+ */
+ export function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<string, Doc>();
const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
- const clone = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
+ const clone = Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], DocCast(doc.embedContainer) ? [DocCast(doc.embedContainer)!] : [], cloneLinks, cloneTemplates);
const repaired = new Set<Doc>();
const linkedDocs = Array.from(linkMap.values());
linkedDocs.forEach(link => Doc.AddLink?.(link, true));
rtfMap.forEach(({ copy, key, field }) => {
- const replacer = (match: string, attr: string, id: string /* , offset: any, string: any */) => {
- const mapped = cloneMap.get(id);
- return attr + '"' + (mapped ? mapped[Id] : id) + '"';
- };
- const replacer2 = (match: string, href: string, id: string /* , offset: any, string: any */) => {
- const mapped = cloneMap.get(id);
- return href + (mapped ? mapped[Id] : id);
- };
+ const replacer = (match: string, attr: string, id: string) => attr + '":"' + (cloneMap.get(id)?.[Id] ?? id) + '"';
+ const replacer2 = (match: string, href: string, id: string) => href + (cloneMap.get(id)?.[Id] ?? id);
const re = new RegExp(`(${Doc.localServerPath()})([^"]*)`, 'g');
- const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => `"${exp}":`).join('|') + ')"([^"]+)"', 'g');
- copy[key] = new RichTextField(field.Data.replace(docidsearch, replacer).replace(re, replacer2), field.Text);
+ copy[key] = new RichTextField(field.Data.replace(FindDocsInRTF, replacer).replace(re, replacer2), field.Text);
});
const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs];
clonedDocs.forEach(cloneDoc => Doc.repairClone(cloneDoc, cloneMap, cloneTemplates, repaired));
@@ -849,34 +847,40 @@ export namespace Doc {
}
const _pendingMap = new Set<string>();
- //
- // 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
- // of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
- export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) {
+ /**
+ * 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
+ * of the original layout while allowing for individual layout properties to be overridden in the expanded layout
+ * @param templateLayoutDoc a rendering template Doc
+ * @param targetDoc the Doc that the render template will be applied to
+ * @param layoutFieldKey the accumulated layoutFieldKey for the container of this expanded template
+ * @returns a Doc to use to render the targetDoc in the style of the template layout
+ */
+ export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, layoutFieldKey?: string) {
// nothing to do if the layout isn't a template or we don't have a target that's different than the template
if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) {
return templateLayoutDoc;
}
- const templateField = StrCast(templateLayoutDoc.isTemplateForField, Doc.LayoutFieldKey(templateLayoutDoc)); // the field that the template renders
+ const templateField = StrCast(templateLayoutDoc.isTemplateForField, Doc.LayoutDataKey(templateLayoutDoc)); // the field that the template renders
+
// First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
// using the template layout doc's id as the field key.
// 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 = expandedFieldName(templateLayoutDoc, layoutFieldKey);
let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
- if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
+ if (templateLayoutDoc.rootDocument instanceof Promise) {
expandedTemplateLayout = undefined;
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
} else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) {
- if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
+ if (DocCast(templateLayoutDoc.rootDocument)?.[DocData] === targetDoc[DocData]) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
// eslint-disable-next-line no-param-reassign
- templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
+ templateLayoutDoc.rootDocument && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)!); // if the template has already been applied (ie, a nested template), then use the template's prototype
if (!targetDoc[expandedLayoutFieldKey]) {
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
setTimeout(
@@ -885,7 +889,7 @@ export namespace Doc {
const dataDoc = Doc.GetProto(targetDoc);
newLayoutDoc.rootDocument = targetDoc;
newLayoutDoc.embedContainer = targetDoc;
- newLayoutDoc.resolvedDataDoc = dataDoc;
+ newLayoutDoc.cloneOnCopy = true;
newLayoutDoc.acl_Guest = SharingPermissions.Edit;
if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as List<Doc>)?.length) {
dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
@@ -902,87 +906,78 @@ export namespace Doc {
return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending.
}
- // if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
- // otherwise, it just returns the childDoc
- export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) {
+ /**
+ * Returns a layout and data Doc pair to use to render the specified childDoc of the container.
+ * if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
+ * otherwise, only a layout is returned since that will contain the data as its prototype.
+ * @param containerDoc the template container (that may be nested within a template, the root of a template, or not a template)
+ * @param containerDataDoc the template container's data doc (if the container is nested within the template)
+ * @param childDoc the doc to render
+ * @param layoutFieldKey the accumulated layoutFieldKey for the container of the doc being rendered
+ * @returns a layout Doc to render and an optional data Doc if the layout is a template
+ */
+ export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc, layoutFieldKey?: string) {
if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) {
console.log('Warning: GetLayoutDataDocPair childDoc not defined');
return { layout: childDoc, data: childDoc };
}
- const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
+ const data = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
const templateRoot = DocCast(containerDoc?.rootDocument);
- return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data: resolvedDataDoc };
+ return { layout: Doc.expandTemplateLayout(childDoc, templateRoot, layoutFieldKey), data };
}
- export function FindReferences(infield: Doc | List<Doc>, references: Set<Doc>, system: boolean | undefined) {
- if (infield instanceof Promise) return;
- if (!(infield instanceof Doc)) {
- infield?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
- return;
+ /**
+ * Recursively travels through all the metadata reachable from the Doc to find all referenced Docs
+ * @param doc Doc to search
+ * @param references all Docs reachable from the Doc
+ * @param system whether to include system Docs
+ * @returns all Docs reachable from the Doc
+ */
+ export function FindReferences(doc: Doc | List<Doc> | undefined, references: Set<Doc>, system: boolean | undefined) {
+ if (!doc || doc instanceof Promise) {
+ return references;
+ }
+ if (!(doc instanceof Doc)) {
+ doc?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+ return references;
}
- const doc = infield as Doc;
if (references.has(doc)) {
- references.add(doc);
- return;
+ return references;
+ }
+ if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) {
+ return references;
}
- const excludeLists = [Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc);
- 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);
+ Object.keys(doc)
+ .filter(key => key !== 'author' && !(ComputedField.DisableCompute(() => FieldValue(doc[key])) instanceof ComputedField))
+ .map(key => doc[key])
+ .forEach(field => {
+ if (field instanceof List) {
+ return ![Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc) && Doc.FindReferences(field, references, system);
}
- } else {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? ClientUtils.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) {
- /* empty */
- } 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 RichTextField) {
- const re = /"docId"\s*:\s*"(.*?)"/g;
- let match: string[] | null;
- while ((match = re.exec(field.Data)) !== null) {
- const urlString = match[1];
- if (urlString) {
- const rdoc = DocServer.GetCachedRefField(urlString);
- if (rdoc) {
- references.add(rdoc);
- Doc.FindReferences(rdoc, references, system);
- }
- }
- }
+ if (field instanceof Doc) {
+ return Doc.FindReferences(field, references, system);
+ }
+ if (field instanceof RichTextField) {
+ let docId: string | undefined;
+ while ((docId = (FindDocsInRTF.exec(field.Data) ?? [undefined, undefined, undefined])[2])) {
+ Doc.FindReferences(DocServer.GetCachedRefField(docId), references, system);
}
- } else if (field instanceof Promise) {
- // eslint-disable-next-line no-debugger
- debugger; // This shouldn't happend...
}
- }
- });
+ });
+ return references;
}
+ /**
+ * Copies a Doc by copying its embedding (and optionally its prototype). Values within the Doc are copied except for Docs which are
+ * sipmly referenced (except if they're marked to be copied - eg., template layout Docs)
+ * @param doc Doc to copy
+ * @param copyProto whether to copy the Docs proto
+ * @param copyProtoId the id to use for the proto if copied
+ * @param retitle whether to retitle the copy by adding a copy number to the title
+ * @returns the copied Doc
+ */
export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
const copy = runInAction(() => new Doc(copyProtoId, true));
updateCachedAcls(copy);
@@ -995,24 +990,18 @@ export namespace Doc {
copy[key] = Doc.MakeCopy(doc.proto, false);
}
} else {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
- if (field instanceof RefField) {
- copy[key] = field;
- } else if (cfield instanceof ComputedField) {
- copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript);
- } else if (field instanceof ObjectField) {
- const docAtKey = doc[key];
- copy[key] =
- docAtKey instanceof Doc && (key.includes('layout[') || docAtKey.cloneOnCopy)
- ? new ProxyField(Doc.MakeCopy(docAtKey)) // copy the expanded render template
- : ObjectField.MakeCopy(field);
- } else if (field instanceof Promise) {
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail() : doc[key];
+ if (field instanceof Promise) {
// eslint-disable-next-line no-debugger
debugger; // This shouldn't happend...
- } else {
- copy[key] = field;
}
+ copy[key] = (() => {
+ const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
+ if (cfield instanceof ComputedField) return cfield[Copy]();
+ if (field instanceof Doc) return field.cloneOnCopy ? Doc.MakeCopy(field) : field; // copy the expanded render template
+ if (field instanceof ObjectField) return ObjectField.MakeCopy(field);
+ return field;
+ })();
}
});
if (copyProto) {
@@ -1028,6 +1017,14 @@ export namespace Doc {
return copy;
}
+ /**
+ * Makes a delegate of a prototype Doc. Delegates inherit all of the properties of the
+ * prototype Doc, but can add new properties or mask existing prototype properties.
+ * @param doc prototype Doc to make a delgate of
+ * @param id id to use for delegate
+ * @param title title to use for delegate
+ * @returns a new Doc that is a delegate of the original Doc
+ */
export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
@@ -1063,14 +1060,14 @@ export namespace Doc {
return ndoc;
}
- let _applyCount: number = 0;
export function ApplyTemplate(templateDoc: Doc) {
if (templateDoc) {
const proto = new Doc();
+ const applyCount = NumCast(templateDoc.dragFactory_count);
proto.author = ClientUtils.CurrentUserEmail();
const target = Doc.MakeDelegate(proto);
const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout');
- const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
+ const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + applyCount + ')');
target.layout_fieldKey = targetKey; //this and line above
applied && (Doc.GetProto(applied).type = templateDoc.type);
return applied;
@@ -1080,7 +1077,7 @@ export namespace Doc {
export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) {
if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
- if (target.resolvedDataDoc) {
+ if (target.rootDocument) {
target[targetKey] = new PrefetchProxy(templateDoc);
} else {
titleTarget && (Doc.GetProto(target).title = titleTarget);
@@ -1097,13 +1094,13 @@ export namespace Doc {
//
export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>, keepFieldKey = false): boolean {
// find the metadata field key that this template field doc will display (indicated by its title)
- const metadataFieldKey = keepFieldKey ? Doc.LayoutFieldKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
+ const metadataFieldKey = keepFieldKey ? Doc.LayoutDataKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutDataKey(templateField);
// update the original template to mark it as a template
templateField.isTemplateForField = metadataFieldKey;
!keepFieldKey && (templateField.title = metadataFieldKey);
- const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
+ const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutDataKey(templateField)];
// move any data that the template field had been rendering over to the template doc so that things will still be rendered
// when the template field is adjusted to point to the new metadatafield key.
// note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well.
@@ -1113,10 +1110,10 @@ export namespace Doc {
Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue);
}
// get the layout string that the template uses to specify its layout
- const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
+ const templateFieldLayoutString = StrCast(Doc.LayoutField(templateField[DocLayout]));
// change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document.
- Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
+ templateField[DocLayout].layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
return true;
}
@@ -1149,58 +1146,94 @@ export namespace Doc {
@observable _searchQuery: string = '';
}
- // 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);
- return overrideLayout || doc[DocLayout] || doc;
- }
- export function SetLayout(doc: Doc, layout: Doc | string) {
- doc[StrCast(doc.layout_fieldKey, 'layout')] = layout;
+ /**
+ * The layout Doc containing the view layout information - this will be :
+ * a) the Doc being rendered itself unless
+ * b) a template Doc stored in the field sepcified by Doc's layout_fieldKey
+ * c) or the specifeid 'template' Doc;
+ * If a template is specified, it will be expanded to create an instance specific to the rendered doc;
+ * @param doc the doc to render
+ * @param template a Doc to use as a template for the layout
+ * @returns
+ */
+ export function LayoutDoc(doc: Doc, template?: Doc): Doc {
+ const expandedTemplate = template && Cast(doc[expandedFieldName(template)], Doc, null);
+ return expandedTemplate || doc[DocLayout];
}
+ /**
+ * The JSX or Doc value defining how to render the Doc.
+ * @param doc Doc to render
+ * @returns a JSX string or Doc that describes how to render the Doc
+ */
export function LayoutField(doc: Doc) {
return doc[StrCast(doc.layout_fieldKey, 'layout')];
}
- export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
- const match = StrCast(templateLayoutString || Doc.Layout(doc).layout).match(/fieldKey={'([^']+)'}/);
- return match?.[1] || ''; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
+ /**
+ * The field key of the Doc where the primary Data can be found to render the Doc.
+ * eg., for an image, this is likely to be the 'data' field which contains an image url,
+ * and for a text doc, this is likely to be the 'text' field ontaingin the text of the doc.
+ * @param doc Doc to render
+ * @param templateLayoutString optional JSX string that specifies the doc's data field key
+ * @returns field key where data is stored on Doc
+ */
+ export function LayoutDataKey(doc: Doc, templateLayoutString?: string): string {
+ const match = StrCast(templateLayoutString || doc[DocLayout].layout).match(/fieldKey={'([^']+)'}/);
+ return match?.[1] || '';
}
export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
}
export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
- return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
+ return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
}
export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
if (!doc) return 0;
const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width
- const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
+ const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
return NumCast(doc._nativeHeight, nheight || dheight);
}
+
+ export function OutpaintingWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
+ return !doc ? 0 : NumCast(doc._outpaintingWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_outpaintingWidth'], useWidth ? NumCast(doc._width) : 0));
+ }
+
+ export function OutpaintingHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
+ if (!doc) return 0;
+ const oheight = (Doc.OutpaintingWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height);
+ const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_outpaintingHeight'], useHeight ? NumCast(doc._height) : 0);
+ return NumCast(doc._outpaintingHeight, oheight || dheight);
+ }
+
+ export function SetOutpaintingWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_outpaintingWidth'] = width;
+ }
+
+ export function SetOutpaintingHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_outpaintingHeight'] = height;
+ }
+
export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
- doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_nativeWidth'] = width;
}
export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
- doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
+ doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_nativeHeight'] = height;
}
const manager = new UserDocData();
- export function SearchQuery(): string {
+ export function SearchQuery() {
return manager._searchQuery;
}
export function SetSearchQuery(query: string) {
- runInAction(() => {
- manager._searchQuery = query;
- });
+ manager._searchQuery = query;
}
- export function UserDoc(): Doc {
+ export function UserDoc() {
return manager._user_doc;
}
- export function SharingDoc(): Doc {
+ export function SharingDoc() {
return Doc.MySharedDocs;
}
- export function LinkDBDoc(): Doc {
- return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
+ export function LinkDBDoc() {
+ return DocCast(Doc.UserDoc().myLinkDatabase);
}
export function SetUserDoc(doc: Doc) {
return (manager._user_doc = doc);
@@ -1225,7 +1258,7 @@ export namespace Doc {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate) return doc;
const result = brushManager.SearchMatchDoc.get(doc);
const num = Math.abs(result?.searchMatch || 0) + 1;
- runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
+ result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num });
return doc;
}
export function ClearSearchMatches() {
@@ -1298,12 +1331,12 @@ export namespace Doc {
highlightedDocs.add(doc);
doc[Highlight] = true;
doc[Animation] = presentationEffect;
- if (dataAndDisplayDocs && !doc.resolvedDataDoc) {
+ if (dataAndDisplayDocs && !doc.rootDocument) {
// if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field
highlightedDocs.add(doc[DocData]);
doc[DocData][Highlight] = true;
// want to highlight the targets of presentation docs explicitly since following a pres target does not highlight PDf <Annotations> which are not DocumentViews
- if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = true;
+ if (DocCast(doc.presentation_targetDoc)) DocCast(doc.presentation_targetDoc)![Highlight] = true;
}
});
}
@@ -1315,23 +1348,13 @@ export namespace Doc {
highlightedDocs.delete(doc[DocData]);
doc[Highlight] = doc[DocData][Highlight] = false;
doc[Animation] = undefined;
- if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false;
+ if (DocCast(doc.presentation_targetDoc)) DocCast(doc.presentation_targetDoc)![Highlight] = false;
});
});
}
export function getDocTemplate(doc?: Doc) {
- return !doc
- ? undefined
- : doc.isTemplateDoc
- ? doc
- : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
- ? doc.dragFactory
- : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
- ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc
- ? Doc.Layout(doc).proto
- : Doc.Layout(doc)
- : undefined;
+ return !doc ? undefined : doc.isTemplateDoc ? doc : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory : doc[DocLayout].isTemplateDoc ? (doc[DocLayout].rootDocument ? doc[DocLayout].proto : doc[DocLayout]) : undefined;
}
export function toggleLockedPosition(doc: Doc) {
@@ -1352,7 +1375,7 @@ export namespace Doc {
export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: readonly number[], modifiers?: 'remove') {
if (!container) return;
- const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []);
+ const childFiltersByRanges = StrListCast(container._childFiltersByRanges);
for (let i = 0; i < childFiltersByRanges.length; i += 3) {
if (childFiltersByRanges[i] === key) {
@@ -1360,7 +1383,7 @@ export namespace Doc {
break;
}
}
- if (range !== undefined) {
+ if (range) {
childFiltersByRanges.push(key);
childFiltersByRanges.push(range[0].toString());
childFiltersByRanges.push(range[1].toString());
@@ -1415,7 +1438,7 @@ export namespace Doc {
});
}
export function readDocRangeFilter(doc: Doc, key: string) {
- const childFiltersByRanges = Cast(doc._childFiltersByRanges, listSpec('string'), []);
+ const childFiltersByRanges = StrListCast(doc._childFiltersByRanges);
for (let i = 0; i < childFiltersByRanges.length; i += 3) {
if (childFiltersByRanges[i] === key) {
return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])];
@@ -1450,8 +1473,9 @@ export namespace Doc {
DocServer.GetRefFields(docids).then(async fieldlist => {
const list = Array.from(fieldlist.values())
.map(d => DocCast(d))
- .filter(d => d);
- const docs = clone ? (await Promise.all(Doc.MakeClones(list, false, false))).map(res => res.clone) : list;
+ .filter(d => d)
+ .map(d => d!);
+ const docs = clone ? Doc.MakeClones(list, false, false).map(res => res.clone) : list;
if (ptx !== undefined && pty !== undefined && newPoint !== undefined) {
const firstx = list.length ? NumCast(list[0].x) + ptx - newPoint[0] : 0;
const firsty = list.length ? NumCast(list[0].y) + pty - newPoint[1] : 0;
@@ -1471,16 +1495,16 @@ export namespace Doc {
* @returns
*/
export function getDescription(doc: Doc) {
- const curDescription = StrCast(doc[DocData][Doc.LayoutFieldKey(doc) + '_description']);
+ const curDescription = StrCast(doc['$' + Doc.LayoutDataKey(doc) + '_description']);
const docText = (async (tdoc:Doc) => {
switch (tdoc.type) {
case DocumentType.PDF: return curDescription || StrCast(tdoc.text).split(/\s+/).slice(0, 50).join(' '); // first 50 words of pdf text
- case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(Doc.LayoutField(tdoc), '_o') ?? '')
+ case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(tdoc[Doc.LayoutDataKey(tdoc)], '_o') ?? '')
.then(hrefBase64 => gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.'));
- case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutFieldKey(tdoc)]).Text;
+ case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutDataKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutDataKey(tdoc)]);
default: return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title);
}}); // prettier-ignore
- return docText(doc).then(text => (doc[DocData][Doc.LayoutFieldKey(doc) + '_description'] = text));
+ return docText(doc).then(text => (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text));
}
// prettier-ignore
@@ -1517,7 +1541,6 @@ export namespace Doc {
case DocumentType.MAP: return 'map-marker-alt';
case DocumentType.DATAVIZ: return 'chart-bar';
case DocumentType.EQUATION: return 'calculator';
- case DocumentType.SIMULATION: return 'rocket';
case DocumentType.CONFIG: return 'folder-closed';
default:
}
@@ -1729,12 +1752,12 @@ export function IdToDoc(id: string) {
return DocCast(DocServer.GetCachedRefField(id));
}
// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function idToDoc(id: string): Doc {
+ScriptingGlobals.add(function idToDoc(id: string): Doc | undefined {
return IdToDoc(id);
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function renameEmbedding(doc: Doc) {
- return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
+ return StrCast(doc.title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getProto(doc: Doc) {
@@ -1783,7 +1806,7 @@ ScriptingGlobals.add(function docCastAsync(doc: FieldResult): FieldResult<Doc> {
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function activePresentationItem() {
const curPres = Doc.ActivePresentation;
- return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
+ return curPres && DocListCast(curPres[Doc.LayoutDataKey(curPres)])[NumCast(curPres._itemIndex)];
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: string, modifiers: 'match' | 'check' | 'x' | 'remove') {